Skip to content

Commit

Permalink
Finalize out of the stock variants statistic
Browse files Browse the repository at this point in the history
  • Loading branch information
radoslaw-sz committed May 31, 2024
1 parent 3d44bbf commit af9fc15
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 61 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ No configuration is needed. Everything is done through UI. You can use such opti
| Top variants | :white_check_mark: |
| Top returned variants | :white_check_mark: |
| Products sold count | :white_check_mark: |
| Out of the stock variants | BETA |
| Out of the stock variants | :white_check_mark: |

### Marketing

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rsc-labs/medusa-store-analytics",
"version": "0.13.0",
"version": "0.13.1",
"description": "Get analytics data about your store",
"author": "RSC Labs (https://rsoftcon.com)",
"main": "dist/index.js",
Expand Down
3 changes: 2 additions & 1 deletion src/api/admin/products-analytics/[kind]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export const GET = async (
);
break;
case 'out-of-the-stock-variants':
result = await productsAnalyticsService.getOutOfTheStockVariants();
const limit = req.query.limit as string;
result = await productsAnalyticsService.getOutOfTheStockVariants(limit ? parseInt(limit) : undefined);
break;
}
res.status(200).json({
Expand Down
12 changes: 11 additions & 1 deletion src/services/productsAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,21 @@ export default class ProductsAnalyticsService extends TransactionBaseService {
.andWhere('productVariant.inventory_quantity = :expectedQuantity', { expectedQuantity: 0})
.andWhere('product.is_giftcard = :isGiftCard', { isGiftCard: false});

const outOfTheStockVariants = await query
let outOfTheStockVariants;

if (limit !== undefined && limit === 0) {
outOfTheStockVariants = await query
.groupBy('productVariant.id, variant_title, product.id, product.thumbnail, product_title')
.orderBy('productVariant.updated_at', 'DESC')
.getRawMany()

} else {
outOfTheStockVariants = await query
.groupBy('productVariant.id, variant_title, product.id, product.thumbnail, product_title')
.orderBy('productVariant.updated_at', 'DESC')
.limit(limit !== undefined ? limit : this.TOP_LIMIT)
.getRawMany()
}

return {
dateRangeFrom: undefined,
Expand Down
44 changes: 44 additions & 0 deletions src/ui-components/products/out_of_the_stock_variants/helpers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export type AdminOutOfTheStockVariantsStatisticsQuery = {}

export type OutOfTheStockVariantsCount = {
productId: string,
variantId: string,
productTitle: string,
variantTitle: string,
thumbnail: string,
}

export type OutOfTheStockVariantsCountResult = {
dateRangeFrom?: number
dateRangeTo?: number,
dateRangeFromCompareTo?: number,
dateRangeToCompareTo?: number,
current: OutOfTheStockVariantsCount[],
}

export type OutOfTheStockVariantsCountResponse = {
analytics: OutOfTheStockVariantsCountResult
}
export type OutOfTheStockVariantsTableRow = {
variantId: string,
productId: string,
productTitle: string,
variantTitle: string,
thumbnail: string,
}

export function transformToVariantTopTable(result: OutOfTheStockVariantsCountResult): OutOfTheStockVariantsTableRow[] {
const currentMap = new Map<string, OutOfTheStockVariantsTableRow>();

result.current.forEach(currentItem => {
currentMap.set(currentItem.variantId, {
variantId: currentItem.variantId,
productId: currentItem.productId,
productTitle: currentItem.productTitle,
variantTitle: currentItem.variantTitle,
thumbnail: currentItem.thumbnail,
});
});

return Array.from(currentMap.values());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright 2024 RSC-Labs, https://rsoftcon.com/
*
* MIT License
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Heading, Text, FocusModal, Button, Alert } from "@medusajs/ui"
import { CircularProgress, Grid, Box } from "@mui/material";
import { useAdminCustomQuery } from "medusa-react"
import { useState } from "react";
import { Link } from "react-router-dom"

import { Table } from "@medusajs/ui"
import { useMemo } from "react"
import { AdminOutOfTheStockVariantsStatisticsQuery, OutOfTheStockVariantsCountResponse, OutOfTheStockVariantsTableRow, transformToVariantTopTable } from "./helpers";

function TablePaginated({variants} : {variants: OutOfTheStockVariantsTableRow[]}) {
const [currentPage, setCurrentPage] = useState(0)
const pageSize = 5;
const pageCount = Math.ceil(variants.length / pageSize)
const canNextPage = useMemo(
() => currentPage < pageCount - 1,
[currentPage, pageCount]
)
const canPreviousPage = useMemo(() => currentPage - 1 >= 0, [currentPage])

const nextPage = () => {
if (canNextPage) {
setCurrentPage(currentPage + 1)
}
}

const previousPage = () => {
if (canPreviousPage) {
setCurrentPage(currentPage - 1)
}
}

const currentVariants = useMemo(() => {
const offset = currentPage * pageSize
const limit = Math.min(offset + pageSize, variants.length)

return variants.slice(offset, limit)
}, [currentPage, pageSize, variants])

return (
<div className="flex gap-1 flex-col">
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Variant</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{currentVariants.map((variant) => {
return (
<Table.Row
key={variant.variantId}
className="[&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap"
>
<Table.Cell>
<Grid container justifyContent={'space-between'}>
<Grid item>
<Link to={`../products/${variant.productId}`}>
<Grid container alignItems={'center'} spacing={2}>
{variant.thumbnail && <Grid item>
<Box
sx={{
width: 30,
height: 40
}}
component="img"
alt={`Thumbnail for ${variant.productTitle}`}
src={variant.thumbnail}
/>
</Grid>}
<Grid item>
{variant.productTitle} - {variant.variantTitle}
</Grid>
</Grid>
</Link>
</Grid>
</Grid>
</Table.Cell>
</Table.Row>
)
})}
</Table.Body>
</Table>
<Table.Pagination
count={variants.length}
pageSize={pageSize}
pageIndex={currentPage}
pageCount={variants.length}
canPreviousPage={canPreviousPage}
canNextPage={canNextPage}
previousPage={previousPage}
nextPage={nextPage}
/>
</div>
)
}

const OutOfTheStockVariantsModalContent = () => {

const { data, isError, isLoading, error } = useAdminCustomQuery<
AdminOutOfTheStockVariantsStatisticsQuery,
OutOfTheStockVariantsCountResponse
>(
`/products-analytics/out-of-the-stock-variants`,
[],
{
limit: 0
}
)

if (isLoading) {
return (
<FocusModal.Body>
<CircularProgress/>
</FocusModal.Body>
)
}

if (isError) {
const trueError = error as any;
const errorText = `Error when loading data. It shouldn't have happened - please raise an issue. For developer: ${trueError?.response?.data?.message}`
return (
<FocusModal.Body>
<Alert variant="error">{errorText}</Alert>
</FocusModal.Body>
);
}

return (
<FocusModal.Body>
<Grid container direction={'column'} alignContent={'center'} paddingTop={8}>
<Grid item>
<Heading>All out of the stock variants</Heading>
</Grid>
<Grid item>
<Text>
You can click on the row to go to the product.
</Text>
</Grid>
<Grid item paddingTop={5}>
<TablePaginated variants={transformToVariantTopTable(data.analytics)}/>
</Grid>
</Grid>
</FocusModal.Body>
)
}

export const OutOfTheStockVariantsModal = () => {
const [open, setOpen] = useState(false)

return (
<FocusModal
open={open}
onOpenChange={setOpen}
>
<FocusModal.Trigger asChild>
<Button size="small" variant="secondary">See all</Button>
</FocusModal.Trigger>
<FocusModal.Content>
<FocusModal.Header/>
<OutOfTheStockVariantsModalContent/>
</FocusModal.Content>
</FocusModal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,9 @@ import { Heading, Alert, Tooltip, Badge } from "@medusajs/ui";
import { ArrowRightOnRectangle, InformationCircle } from "@medusajs/icons";
import { CircularProgress, Grid } from "@mui/material";
import { useAdminCustomQuery } from "medusa-react"
import { OutOfTheStockVariantsTable, OutOfTheStockVariantsTableRow } from "./out-of-the-stock-variants-table";
import { AdminOutOfTheStockVariantsStatisticsQuery, OutOfTheStockVariantsCountResponse, OutOfTheStockVariantsCountResult } from "./types";

function transformToVariantTopTable(result: OutOfTheStockVariantsCountResult): OutOfTheStockVariantsTableRow[] {
const currentMap = new Map<string, OutOfTheStockVariantsTableRow>();

result.current.forEach(currentItem => {
currentMap.set(currentItem.variantId, {
variantId: currentItem.variantId,
productId: currentItem.productId,
productTitle: currentItem.productTitle,
variantTitle: currentItem.variantTitle,
thumbnail: currentItem.thumbnail,
});
});

return Array.from(currentMap.values());
}
import { OutOfTheStockVariantsTable } from "./out-of-the-stock-variants-table";
import { OutOfTheStockVariantsModal } from "./out-of-the-stock-variants-all";
import { AdminOutOfTheStockVariantsStatisticsQuery, OutOfTheStockVariantsCountResponse, transformToVariantTopTable } from "./helpers";

const OutOfTheStockVariants = () => {
const { data, isError, isLoading, error } = useAdminCustomQuery<
Expand All @@ -40,7 +25,9 @@ const OutOfTheStockVariants = () => {
>(
`/products-analytics/out-of-the-stock-variants`,
[],
{}
{
limit: 5
}
)

if (isLoading) {
Expand All @@ -63,7 +50,7 @@ const OutOfTheStockVariants = () => {
export const OutOfTheStockVariantsCard = () => {
return (
<Grid container paddingBottom={2} spacing={3}>
<Grid item>
<Grid item xs={12} md={12}>
<Grid container spacing={2} alignItems={'center'}>
<Grid item>
<ArrowRightOnRectangle/>
Expand All @@ -78,17 +65,19 @@ export const OutOfTheStockVariantsCard = () => {
<InformationCircle />
</Tooltip>
</Grid>
<Grid item>
<Tooltip content='This feature might be changed or improved in the future'>
<Badge rounded="full" size="small" color="green">Beta</Badge>
</Tooltip>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} md={12}>
<Heading level="h3">
Showing last 5 variants
</Heading>
<Grid container direction="row" spacing={2} alignItems="center">
<Grid item>
<Heading level="h3">
Last 5 variants
</Heading>
</Grid>
<Grid item>
<OutOfTheStockVariantsModal/>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} md={12}>
<OutOfTheStockVariants/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,7 @@
import { Heading, Text } from "@medusajs/ui";
import { Box, Divider, Grid } from "@mui/material";
import { Link } from "react-router-dom"

export type OutOfTheStockVariantsTableRow = {
variantId: string,
productId: string,
productTitle: string,
variantTitle: string,
thumbnail: string,
}
import { OutOfTheStockVariantsTableRow } from "./helpers";

export const OutOfTheStockVariantsTable = ({tableRows} : {tableRows: OutOfTheStockVariantsTableRow[]}) => {
return (
Expand Down
21 changes: 0 additions & 21 deletions src/ui-components/products/out_of_the_stock_variants/types.ts

This file was deleted.

0 comments on commit af9fc15

Please sign in to comment.