diff --git a/apps/admin-ui/app/(drawer)/products/add.tsx b/apps/admin-ui/app/(drawer)/products/add.tsx index d5e07fa..294bdd4 100644 --- a/apps/admin-ui/app/(drawer)/products/add.tsx +++ b/apps/admin-ui/app/(drawer)/products/add.tsx @@ -44,6 +44,8 @@ export default function AddProduct() { images: variantUrls, isFlashAvailable: variant.isFlashAvailable || false, flashPrice: variant.flashPrice ? parseFloat(variant.flashPrice) : undefined, + isOffer: variant.isOffer || false, + isComboOnly: variant.isComboOnly || false, features: variant.attributes.map((attr: any) => ({ featureName: attr.featureName, featureValue: attr.featureValue, @@ -85,6 +87,8 @@ export default function AddProduct() { marketPrice: '', isFlashAvailable: false, flashPrice: '', + isOffer: false, + isComboOnly: false, attributes: [{ featureName: 'quantity', featureValue: '' }], comboItems: [] as { skuId: number | string }[], }, diff --git a/apps/admin-ui/app/(drawer)/products/edit.tsx b/apps/admin-ui/app/(drawer)/products/edit.tsx index 98d1474..1012943 100644 --- a/apps/admin-ui/app/(drawer)/products/edit.tsx +++ b/apps/admin-ui/app/(drawer)/products/edit.tsx @@ -49,6 +49,8 @@ export default function EditProduct() { marketPrice: sku.marketPrice || '', isFlashAvailable: sku.isFlashAvailable || false, flashPrice: sku.flashPrice || '', + isOffer: sku.isOffer || false, + isComboOnly: sku.isComboOnly || false, attributes: (sku.features || []).map((f) => ({ featureName: f.featureName, featureValue: f.featureValue, @@ -118,6 +120,8 @@ export default function EditProduct() { images: allUrls, isFlashAvailable: variant.isFlashAvailable || false, flashPrice: variant.flashPrice ? parseFloat(variant.flashPrice) : undefined, + isOffer: variant.isOffer || false, + isComboOnly: variant.isComboOnly || false, features: variant.attributes.map((attr: any) => ({ featureName: attr.featureName, featureValue: attr.featureValue, diff --git a/apps/admin-ui/src/components/ProductForm.tsx b/apps/admin-ui/src/components/ProductForm.tsx index ba85a0b..85e5697 100644 --- a/apps/admin-ui/src/components/ProductForm.tsx +++ b/apps/admin-ui/src/components/ProductForm.tsx @@ -6,7 +6,7 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons' import { trpc } from '../trpc-client' interface Attribute { - featureName: string + featureName: string | null featureValue: string } @@ -17,6 +17,8 @@ interface Variant { marketPrice: string isFlashAvailable: boolean flashPrice: string + isOffer: boolean + isComboOnly: boolean attributes: Attribute[] comboItems: { skuId: number | string }[] } @@ -52,6 +54,8 @@ const defaultVariant = (): Variant => ({ marketPrice: '', isFlashAvailable: false, flashPrice: '', + isOffer: false, + isComboOnly: false, attributes: [defaultAttribute()], comboItems: [], }) @@ -210,7 +214,7 @@ const ProductForm = forwardRef(({ @@ -265,6 +269,24 @@ const ProductForm = forwardRef(({ Flash Available + + setFieldValue(`variants.${vIndex}.isOffer`, !variant.isOffer)} + style={tw`mr-3`} + /> + Offer SKU + + + + setFieldValue(`variants.${vIndex}.isComboOnly`, !variant.isComboOnly)} + style={tw`mr-3`} + /> + Combo Only SKU + + {variant.isFlashAvailable && ( extractKeyFromPresignedUrl(url)), isFlashAvailable: sku.isFlashAvailable, flashPrice: sku.flashPrice ?? null, + isOffer: sku.isOffer, + isComboOnly: sku.isComboOnly, features: sku.features.map((f) => ({ - featureName: f.featureName, + featureName: f.featureName?.trim() || null, featureValue: f.featureValue, })), comboItems: (sku.comboItems || []).map((ci) => ({ skuId: ci.skuId })), @@ -278,8 +282,10 @@ export const productRouter = router({ images: z.array(z.string()).optional().default([]), isFlashAvailable: z.boolean().optional().default(false), flashPrice: z.number().optional().nullable(), + isOffer: z.boolean().optional().default(false), + isComboOnly: z.boolean().optional().default(false), features: z.array(z.object({ - featureName: z.string().min(1, 'Attribute name is required'), + featureName: z.string().nullable().optional(), featureValue: z.string().min(1, 'Value is required'), })).min(1, 'At least one feature is required'), comboItems: z.array(z.object({ @@ -306,8 +312,10 @@ export const productRouter = router({ images: sku.images.map((url) => extractKeyFromPresignedUrl(url)), isFlashAvailable: sku.isFlashAvailable, flashPrice: sku.flashPrice ?? null, + isOffer: sku.isOffer, + isComboOnly: sku.isComboOnly, features: sku.features.map((f) => ({ - featureName: f.featureName, + featureName: f.featureName?.trim() || null, featureValue: f.featureValue, })), comboItems: (sku.comboItems || []).map((ci) => ({ skuId: ci.skuId })), diff --git a/packages/db_helper_sqlite/drizzle/0002_sku_split.sql b/packages/db_helper_sqlite/drizzle/0002_sku_split.sql index ef64ebb..2d30e73 100644 --- a/packages/db_helper_sqlite/drizzle/0002_sku_split.sql +++ b/packages/db_helper_sqlite/drizzle/0002_sku_split.sql @@ -16,6 +16,8 @@ CREATE TABLE `product_skus` ( `is_suspended` integer DEFAULT false NOT NULL, `is_flash_available` integer DEFAULT false NOT NULL, `flash_price` text, + `is_offer` integer DEFAULT false NOT NULL, + `is_combo_only` integer DEFAULT false NOT NULL, `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY (`product_id`) REFERENCES `product_info`(`id`) ON UPDATE no action ON DELETE no action ); @@ -23,7 +25,7 @@ CREATE TABLE `product_skus` ( CREATE TABLE `sku_features` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `sku_id` integer NOT NULL, - `feature_name` text NOT NULL, + `feature_name` text, `feature_value` text NOT NULL, FOREIGN KEY (`sku_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action ); diff --git a/packages/db_helper_sqlite/src/admin-apis/product.ts b/packages/db_helper_sqlite/src/admin-apis/product.ts index f29b117..83d0308 100644 --- a/packages/db_helper_sqlite/src/admin-apis/product.ts +++ b/packages/db_helper_sqlite/src/admin-apis/product.ts @@ -106,6 +106,8 @@ const mapSku = (sku: SkuRow, features: SkuFeatureRow[] = [], comboItems: any[] = isSuspended: sku.isSuspended, isFlashAvailable: sku.isFlashAvailable, flashPrice: sku.flashPrice ? String(sku.flashPrice) : null, + isOffer: sku.isOffer, + isComboOnly: sku.isComboOnly, createdAt: sku.createdAt, features: features.map(mapSkuFeature), comboItems: comboItems, @@ -275,6 +277,8 @@ export async function createProduct(input: CreateProductInput): Promise productSkus.id), - featureName: text('feature_name').notNull(), + featureName: text('feature_name'), featureValue: text('feature_value').notNull(), }, (t) => ({ unq_sku_feature_name: uniqueIndex('unique_sku_feature_name').on(t.skuId, t.featureName), diff --git a/packages/db_helper_sqlite/src/user-apis/product.ts b/packages/db_helper_sqlite/src/user-apis/product.ts index 3ac89b9..3fbb777 100644 --- a/packages/db_helper_sqlite/src/user-apis/product.ts +++ b/packages/db_helper_sqlite/src/user-apis/product.ts @@ -181,7 +181,7 @@ export interface ProductSummaryData { isOutOfStock: boolean unitShortNotation: string productQuantity: number - features: { featureName: string; featureValue: string }[] + features: { featureName: string | null; featureValue: string }[] } export async function getAllProductsWithUnits(tagId?: number): Promise { diff --git a/packages/shared/types/admin.ts b/packages/shared/types/admin.ts index 3607c58..f1b4932 100644 --- a/packages/shared/types/admin.ts +++ b/packages/shared/types/admin.ts @@ -171,7 +171,7 @@ export interface AdminOrderDetailsItem { isPackaged: boolean; isPackageVerified: boolean; skuName?: string | null; - features?: { featureName: string; featureValue: string }[]; + features?: { featureName: string | null; featureValue: string }[]; } export interface AdminOrderDetailsPayment { @@ -251,7 +251,7 @@ export interface AdminSlotOrderItem { isPackaged: boolean; isPackageVerified: boolean; skuName?: string | null; - features?: { featureName: string; featureValue: string }[]; + features?: { featureName: string | null; featureValue: string }[]; } export interface AdminSlotOrder { @@ -292,7 +292,7 @@ export interface AdminOrderListItemProduct { isPackaged: boolean; isPackageVerified: boolean; skuName?: string | null; - features?: { featureName: string; featureValue: string }[]; + features?: { featureName: string | null; featureValue: string }[]; } export interface AdminOrderListItem { @@ -363,7 +363,7 @@ export interface AdminUnit { export interface AdminSkuFeature { id: number; skuId: number; - featureName: string; + featureName: string | null; featureValue: string; } @@ -388,6 +388,8 @@ export interface AdminSku { isSuspended: boolean isFlashAvailable: boolean flashPrice: string | null + isOffer: boolean + isComboOnly: boolean createdAt: Date features: AdminSkuFeature[] comboItems: AdminProductComboItem[] @@ -429,6 +431,7 @@ export interface CreateSkuInput { isSuspended?: boolean isFlashAvailable?: boolean flashPrice?: number | string | null + isOffer?: boolean isComboOnly?: boolean sortOrder?: number isDefault?: boolean @@ -573,7 +576,7 @@ export interface AdminSlotProductSummary { name: string; images: string[] | null; skuName?: string | null; - features?: { featureName: string; featureValue: string }[]; + features?: { featureName: string | null; featureValue: string }[]; } export interface AdminVendorSnippet {