feat(admin): implement variant management — list, create, edit, preview, activate/deactivate, delete (Plan 05)
- Extend addVariant with dimension fields and SKU uniqueness check; expand updateVariant to full field set; update getByIdForAdmin to return all variants (active + inactive) - Add generateSku utility to @repo/utils; auto-generates SKU from brand, product name, attributes, and weight with manual-override support - Move ProductSearchSection to components/shared and fix nav link /variants → /variant - Variants page: product search, loading skeleton, variants table, toolbar with create button - VariantsTable: 8 columns, activate/deactivate toggle, delete with AlertDialog confirmation - VariantPreviewDialog: read-only full variant details with sections for pricing, inventory, shipping, attributes - VariantForm: zod schema with superRefine for dimension and on-sale validation, auto-SKU generation - CreateVariantDialog and EditVariantDialog wiring dollarsToCents on submit - Install sonner and add Toaster to root layout; install ShadCN Switch component Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -166,6 +166,44 @@ export function getTotalPages(total: number, limit: number): number {
|
||||
return Math.ceil(total / limit);
|
||||
}
|
||||
|
||||
// ─── SKU Generation ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Generate a SKU from product metadata.
|
||||
* e.g. Royal Canin, Adult Dog Food, flavor: Chicken, 5kg → "ROY-CANI-ADUL-DOG-CHIC-5KG"
|
||||
*/
|
||||
export function generateSku(
|
||||
brand: string,
|
||||
productName: string,
|
||||
attributes?: {
|
||||
size?: string;
|
||||
flavor?: string;
|
||||
color?: string;
|
||||
},
|
||||
weight?: number,
|
||||
weightUnit?: string
|
||||
): string {
|
||||
const clean = (str: string) =>
|
||||
str
|
||||
.toUpperCase()
|
||||
.trim()
|
||||
.replace(/[^A-Z0-9\s]/g, "")
|
||||
.split(/\s+/)
|
||||
.map((w) => w.slice(0, 4))
|
||||
.join("-");
|
||||
|
||||
const parts = [
|
||||
brand ? clean(brand) : null,
|
||||
productName ? clean(productName) : null,
|
||||
attributes?.flavor ? clean(attributes.flavor) : null,
|
||||
attributes?.size ? clean(attributes.size) : null,
|
||||
attributes?.color ? clean(attributes.color) : null,
|
||||
weight && weightUnit ? `${weight}${weightUnit.toUpperCase()}` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
return parts.join("-");
|
||||
}
|
||||
|
||||
// ─── Misc ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user