Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ dist-ssr
*.sln
*.sw?
.vercel
.yarn
.yarnrc.yml
vite.config.mts.timestamp-1745566967628-8377405002526.mjs
yarn.lock
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@
"i18next": "23.7.11",
"i18next-browser-languagedetector": "7.2.0",
"i18next-http-backend": "2.4.2",
"jsbarcode": "^3.11.6",
"lodash": "^4.17.21",
"match-sorter": "^6.3.4",
"motion": "^11.15.0",
"qrcode": "^1.5.4",
"qs": "^6.12.0",
"radix-ui": "1.1.2",
"react": "^18.2.0",
Expand All @@ -84,6 +86,7 @@
"@medusajs/admin-vite-plugin": "^2.5.0",
"@medusajs/types": "^2.5.0",
"@medusajs/ui-preset": "^2.5.0",
"@types/jsbarcode": "^3.11.4",
"@types/node": "^20.11.15",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useTranslation } from "react-i18next"
import { useState, useEffect } from "react"
import QRCode from "qrcode"
import { Badge, Tooltip } from "@medusajs/ui"

import { PlaceholderCell } from "../../common/placeholder-cell"
import { HttpTypes } from "@medusajs/types"
Expand All @@ -9,16 +12,88 @@ type VariantCellProps = {

export const VariantCell = ({ variants }: VariantCellProps) => {
const { t } = useTranslation()
const [qrCodes, setQrCodes] = useState<Record<string, string>>({})

// Generate QR codes for variant barcodes
useEffect(() => {
if (!variants?.length) return

const generateQRCodes = async () => {
const codes: Record<string, string> = {}

for (const variant of variants) {
if (variant.barcode) {
try {
// Use existing QR code from metadata if available
if (variant.metadata?.qr_code) {
codes[variant.id] = variant.metadata.qr_code as string
} else {
// Generate new QR code
const url = await QRCode.toDataURL(variant.barcode, {
errorCorrectionLevel: 'H'
})
codes[variant.id] = url
}
} catch (error) {
console.error("Failed to generate QR code:", error)
}
}
}

setQrCodes(codes)
}

generateQRCodes()
}, [variants])

if (!variants || !variants.length) {
return <PlaceholderCell />
}

// Find the first variant with a barcode to display as a preview
const variantWithBarcode = variants.find(v => v.barcode && qrCodes[v.id])

return (
<div className="flex h-full w-full items-center overflow-hidden">
<span className="truncate">
{t("products.variantCount", { count: variants.length })}
</span>
<Tooltip
content={
<div className="max-w-xs">
<p className="mb-2 font-medium">{t("products.variantCount", { count: variants.length })}</p>
{variants.filter(v => v.barcode).length > 0 && (
<div className="mt-2">
<p className="text-xs mb-1">{t("fields.barcodes")}:</p>
<div className="max-h-40 overflow-auto">
{variants.map(v => v.barcode && (
<div key={v.id} className="flex items-center gap-2 mb-1">
<Badge size="2xsmall">{v.barcode}</Badge>
{qrCodes[v.id] && (
<img
src={qrCodes[v.id]}
alt="QR Code"
className="h-8 w-8"
/>
)}
</div>
))}
</div>
</div>
)}
</div>
}
>
<div className="flex items-center gap-2">
<span className="truncate">
{t("products.variantCount", { count: variants.length })}
</span>
{variantWithBarcode && (
<img
src={qrCodes[variantWithBarcode.id]}
alt="QR Code"
className="h-6 w-6"
/>
)}
</div>
</Tooltip>
</div>
)
}
Expand Down
13 changes: 9 additions & 4 deletions src/hooks/api/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export const useSignInWithEmailPass = (
options?: UseMutationOptions<
| string
| {
location: string
},
location: string
},
FetchError,
HttpTypes.AdminSignUpWithEmailPassword
>
Expand All @@ -29,22 +29,27 @@ export const useSignUpWithEmailPass = (
HttpTypes.AdminSignInWithEmailPassword & {
confirmPassword: string
name: string
phone: string
type: "manufacturer" | "reseller"
}
>
) => {
return useMutation({
mutationFn: (payload) => sdk.auth.register("seller", "emailpass", payload),
onSuccess: async (_, variables) => {
const seller = {
const sellerPayload = {
name: variables.name,
phone: variables.phone,
type: variables.type,
email: variables.email,
member: {
name: variables.name,
email: variables.email,
},
}
await fetchQuery("/vendor/sellers", {
method: "POST",
body: seller,
body: sellerPayload,
})
},
...options,
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/api/categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const useProductCategory = (
queryFn: () =>
fetchQuery(`/vendor/product-categories/${id}`, {
method: "GET",
query: query as Record<string, string | number>,
}),
...options,
})
Expand All @@ -57,6 +58,7 @@ export const useProductCategories = (
queryFn: () =>
fetchQuery("/vendor/product-categories", {
method: "GET",
query: query as Record<string, string | number>,
}),
...options,
})
Expand Down
26 changes: 20 additions & 6 deletions src/hooks/api/products.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ export const useProductVariants = (
>
) => {
const { data, ...rest } = useQuery({
queryFn: () => sdk.admin.product.listVariants(productId, query),
queryFn: () => sdk.client.fetch(`/vendor/products/${productId}/variants`, {
method: "GET",
query: query
}) as Promise<HttpTypes.AdminProductVariantListResponse>,
queryKey: variantsQueryKeys.list({
productId,
...query,
Expand Down Expand Up @@ -227,9 +230,12 @@ export const useUpdateProductVariantsBatch = (
mutationFn: (
payload: HttpTypes.AdminBatchProductVariantRequest["update"]
) =>
sdk.admin.product.batchVariants(productId, {
update: payload,
}),
sdk.client.fetch(`/vendor/products/${productId}/variants/batch`, {
method: "POST",
body: {
update: payload,
},
}) as Promise<HttpTypes.AdminBatchProductVariantResponse>,
onSuccess: (data: any, variables: any, context: any) => {
queryClient.invalidateQueries({
queryKey: variantsQueryKeys.lists(),
Expand Down Expand Up @@ -408,13 +414,21 @@ export const useProducts = (
sort?: string
}
) => {
// Ensure barcode and metadata are included in the query
const queryWithFields = {
...query,
fields: query?.fields
? `${query.fields},*variants.barcode,*variants.metadata`
: "*variants.barcode,*variants.metadata",
}

const { data, ...rest } = useQuery({
queryFn: () =>
fetchQuery("/vendor/products", {
method: "GET",
query: query as Record<string, string | number>,
query: queryWithFields as Record<string, string | number>,
}),
queryKey: productsQueryKeys.list(query),
queryKey: productsQueryKeys.list(queryWithFields),
...options,
})

Expand Down
2 changes: 1 addition & 1 deletion src/hooks/api/users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const useMe = (
method: "GET",
query: {
fields:
"id,name,description,phone,email,media,address_line,postal_code,country_code,city,region,metadata,tax_id,photo",
"id,name,description,phone,photo,email,media,address_line,postal_code,country_code,city,state,region,metadata,gstin,type,verification_status,handle",
},
}),
queryKey: usersQueryKeys.me(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { InventoryTypes } from "@medusajs/types"
import { Container, Heading, Text } from "@medusajs/ui"
import { Button, Container, Heading, Text } from "@medusajs/ui"

import { RowSelectionState } from "@tanstack/react-table"
import { useState } from "react"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { Link, useNavigate } from "react-router-dom"
import { _DataTable } from "../../../../components/table/data-table"
import { useInventoryItems } from "../../../../hooks/api/inventory"
import { useDataTable } from "../../../../hooks/use-data-table"
import { INVENTORY_ITEM_IDS_KEY } from "../../common/constants"
import { useInventoryTableColumns } from "./use-inventory-table-columns"
import { useInventoryTableFilters } from "./use-inventory-table-filters"
import { useInventoryTableQuery } from "./use-inventory-table-query"

const PAGE_SIZE = 20
Expand All @@ -20,7 +21,7 @@ export const InventoryListTable = () => {

const [selection, setSelection] = useState<RowSelectionState>({})

const { raw, searchParams } = useInventoryTableQuery({
const { searchParams, raw } = useInventoryTableQuery({
pageSize: PAGE_SIZE,
})

Expand All @@ -36,6 +37,7 @@ export const InventoryListTable = () => {
fields: "id,title,sku,*location_levels",
})

const filters = useInventoryTableFilters()
const columns = useInventoryTableColumns()

const { table } = useDataTable({
Expand Down Expand Up @@ -65,6 +67,9 @@ export const InventoryListTable = () => {
{t("inventory.subtitle")}
</Text>
</div>
<Button size="small" variant="secondary" asChild>
<Link to="create">{t("actions.create")}</Link>
</Button>
</div>
<_DataTable
table={table}
Expand All @@ -73,7 +78,15 @@ export const InventoryListTable = () => {
count={count}
isLoading={isLoading}
pagination
search
filters={filters}
queryObject={raw}
orderBy={[
{ key: "title", label: t("fields.title") },
{ key: "sku", label: t("fields.sku") },
{ key: "stocked_quantity", label: t("fields.inStock") },
{ key: "reserved_quantity", label: t("inventory.reserved") },
]}
navigateTo={(row) => `${row.id}`}
commands={[
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ export const useInventoryTableQuery = ({
mid_code: params.mid_code,
hs_code: params.hs_code,
material: params.material,
location_levels: {
location_id: params.location_id || [],
},
// location_levels: {
// location_id: params.location_id || [],
// },
id: params.id ? params.id.split(",") : undefined,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { HttpTypes } from "@medusajs/types"
import { Badge, Container, Heading, usePrompt } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { useState, useEffect } from "react"
import QRCode from "qrcode"

import { ActionMenu } from "../../../../../components/common/action-menu"
import { SectionRow } from "../../../../../components/common/section"
Expand All @@ -19,6 +21,16 @@ export function VariantGeneralSection({ variant }: VariantGeneralSectionProps) {

const hasInventoryKit = variant.inventory?.length > 1

// Dynamically generate QR code image for the barcode
const [qrImage, setQrImage] = useState<string>("")
useEffect(() => {
if (variant.barcode) {
QRCode.toDataURL(variant.barcode, { errorCorrectionLevel: 'H' })
.then((url) => setQrImage(url))
.catch(() => {})
}
}, [variant.barcode])

const { mutateAsync } = useDeleteVariant(variant.product_id!, variant.id)

const handleDelete = async () => {
Expand Down Expand Up @@ -85,6 +97,23 @@ export function VariantGeneralSection({ variant }: VariantGeneralSectionProps) {
</div>

<SectionRow title={t("fields.sku")} value={variant.sku} />
<SectionRow title={t("fields.barcode")} value={
variant.barcode ? (
<div className="flex items-center gap-2">
<span>{variant.barcode}</span>
{qrImage && (
<img
src={qrImage}
alt="barcode"
style={{ height: 40 }}
className="ml-2"
/>
)}
</div>
) : (
<span className="text-ui-fg-muted">-</span>
)
} />
{variant.options?.map((o) => (
<SectionRow
key={o.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { VariantPricesSection } from "./components/variant-prices-section"
export const ProductVariantDetail = () => {
const { id, variant_id } = useParams()
const { product, isLoading, isError, error } = useProduct(id!, {
fields: "*variants.inventory_items",
fields: "*variants.inventory_items,*variants.barcode,*variants.metadata",
})

const variant = product?.variants
Expand Down
Loading