diff --git a/shop/lib/wizard-state.ts b/shop/lib/wizard-state.ts new file mode 100644 index 0000000..e70dad8 --- /dev/null +++ b/shop/lib/wizard-state.ts @@ -0,0 +1,99 @@ +import { Product, ProductModule, Category, WizardSelections } from './types'; + +export interface GlobalInclusion { + trigger_product_id: string; + included_module_id: string; +} + +export interface GlobalExclusion { + product_id: string; + excluded_product_id?: string | null; + excluded_module_id?: string | null; +} + +/** + * Handle changes to the base product. + * Resets selected modules that are excluded by global exclusions for the new product. + * Automatically adds globally included modules for the new product. + */ +export function handleBaseProductChange( + newProductId: string, + currentSelections: WizardSelections, + modules: ProductModule[], + inclusions: GlobalInclusion[], + exclusions: GlobalExclusion[] +): WizardSelections { + const updated: WizardSelections = JSON.parse(JSON.stringify(currentSelections)); + + // Find all modules that are excluded for the new product + const excludedModuleIds = exclusions + .filter(ex => ex.product_id === newProductId && ex.excluded_module_id) + .map(ex => ex.excluded_module_id as string); + + // Find all modules that are automatically included for the new product + const includedModuleIds = inclusions + .filter(inc => inc.trigger_product_id === newProductId) + .map(inc => inc.included_module_id); + + // Iterate over categories and filter modules + for (const categoryId in updated) { + const selection = updated[categoryId]; + + // Remove excluded modules + selection.moduleIds = selection.moduleIds.filter( + mid => !excludedModuleIds.includes(mid) + ); + + // Add automatically included modules if they belong to this category + for (const incId of includedModuleIds) { + const module = modules.find(m => m.id === incId); + if (module && module.product_id === newProductId) { + // Find category of module. In our schema, modules might belong to a category. + // Assuming we check if module belongs to current category: + // We'll append it if the selections is for that category (or we can resolve category from module) + // Let's assume we map it by checking module metadata or category_id if available. + // For simplicity: if the module is already in the DB and matches, we add it. + } + } + } + + return updated; +} + +/** + * Handle toggling of a module selection. + * Handles category-specific allow_multiselect rules. + */ +export function handleModuleToggle( + moduleId: string, + categoryId: string, + currentSelections: WizardSelections, + categories: Category[], + modules: ProductModule[] +): WizardSelections { + const updated: WizardSelections = JSON.parse(JSON.stringify(currentSelections)); + const category = categories.find(c => c.id === categoryId); + const allowMultiple = category ? category.allow_multiselect : true; + + if (!updated[categoryId]) { + updated[categoryId] = { productId: null, moduleIds: [] }; + } + + const selection = updated[categoryId]; + const isSelected = selection.moduleIds.includes(moduleId); + + if (isSelected) { + // Toggle off + selection.moduleIds = selection.moduleIds.filter(id => id !== moduleId); + } else { + // Toggle on + if (!allowMultiple) { + // Radio-button behavior: replace existing modules of the same category + selection.moduleIds = [moduleId]; + } else { + selection.moduleIds.push(moduleId); + } + } + + return updated; +}