Add store-product Excel bulk edit and polish the business dashboard.

Export/import price and stock from Store Products, keep card order after saves, localize variations, and drop unused website nav items.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-19 01:02:43 +03:30
co-authored by Cursor
parent 7b51dda82a
commit 832ac32a09
16 changed files with 617 additions and 181 deletions
+48 -40
View File
@@ -63,7 +63,10 @@ function isVariantInStock(variant: StoreItem) {
return variant.stockQuantity === null || variant.stockQuantity > 0
}
export function groupStoreItemsByProduct(items: StoreItem[]): StoreProductListing[] {
export function groupStoreItemsByProduct(
items: StoreItem[],
productOrder?: string[],
): StoreProductListing[] {
const byProduct = new Map<string, StoreItem[]>()
for (const item of items) {
@@ -72,48 +75,53 @@ export function groupStoreItemsByProduct(items: StoreItem[]): StoreProductListin
byProduct.set(item.productId, variants)
}
return [...byProduct.values()]
.map((variants) => {
const sorted = [...variants].sort((a, b) => {
const aInStock = isVariantInStock(a)
const bInStock = isVariantInStock(b)
if (aInStock !== bInStock) return aInStock ? -1 : 1
const listings = [...byProduct.values()].map((variants) => {
const sorted = [...variants].sort((a, b) => {
const aInStock = isVariantInStock(a)
const bInStock = isVariantInStock(b)
if (aInStock !== bInStock) return aInStock ? -1 : 1
const updatedDiff = parseDateMs(b.updatedAt) - parseDateMs(a.updatedAt)
if (updatedDiff !== 0) return updatedDiff
return parseDateMs(b.createdAt) - parseDateMs(a.createdAt)
})
const representative = sorted[0]!
const { displayPrice, displayDiscountedPrice } = pickDisplayPrice(sorted)
return {
productId: representative.productId,
productTitle: representative.productTitle,
productNameFa: representative.productNameFa,
productImage: representative.productImage,
productTotalStock: representative.productTotalStock,
variantCount: sorted.length,
variants: sorted,
representative,
displayPrice,
displayDiscountedPrice,
showFestival: sorted.some(
(variant) => variant.isFestival || (variant.rewardPoints ?? 0) > 0,
),
}
const updatedDiff = parseDateMs(b.updatedAt) - parseDateMs(a.updatedAt)
if (updatedDiff !== 0) return updatedDiff
return parseDateMs(b.createdAt) - parseDateMs(a.createdAt)
})
.sort(
(a, b) => {
const aInStock = a.variants.some(isVariantInStock)
const bInStock = b.variants.some(isVariantInStock)
if (aInStock !== bInStock) return aInStock ? -1 : 1
return (
parseDateMs(b.representative.updatedAt) - parseDateMs(a.representative.updatedAt)
)
},
)
const representative = sorted[0]!
const { displayPrice, displayDiscountedPrice } = pickDisplayPrice(sorted)
return {
productId: representative.productId,
productTitle: representative.productTitle,
productNameFa: representative.productNameFa,
productImage: representative.productImage,
productTotalStock: representative.productTotalStock,
variantCount: sorted.length,
variants: sorted,
representative,
displayPrice,
displayDiscountedPrice,
showFestival: sorted.some(
(variant) => variant.isFestival || (variant.rewardPoints ?? 0) > 0,
),
}
})
if (productOrder && productOrder.length > 0) {
const indexById = new Map(productOrder.map((id, index) => [id, index]))
return listings.sort((a, b) => {
const ai = indexById.get(a.productId) ?? Number.MAX_SAFE_INTEGER
const bi = indexById.get(b.productId) ?? Number.MAX_SAFE_INTEGER
return ai - bi
})
}
return listings.sort((a, b) => {
const aInStock = a.variants.some(isVariantInStock)
const bInStock = b.variants.some(isVariantInStock)
if (aInStock !== bInStock) return aInStock ? -1 : 1
return parseDateMs(b.representative.updatedAt) - parseDateMs(a.representative.updatedAt)
})
}
export function formatVariantCount(count: number, locale: 'en' | 'fa' = 'en'): string {