diff --git a/waiter_pwa/src/components/ItemOptionsModal.jsx b/waiter_pwa/src/components/ItemOptionsModal.jsx index 3377a88..f3d3e2b 100644 --- a/waiter_pwa/src/components/ItemOptionsModal.jsx +++ b/waiter_pwa/src/components/ItemOptionsModal.jsx @@ -10,40 +10,164 @@ export default function ItemOptionsModal({ product, onAdd, onClose }) { const ingredients = product.ingredients || [] const preferenceSets = product.preference_sets || [] - const [selectedPreferences, setSelectedPreferences] = useState( - Object.fromEntries(preferenceSets.map(ps => [ps.id, null])) + // selectedPreferences: { [setId]: choice | null } + const [selectedPreferences, setSelectedPreferences] = useState(() => + Object.fromEntries( + preferenceSets.map(ps => { + const def = ps.default_choice_id != null + ? ps.choices.find(c => c.id === ps.default_choice_id) ?? null + : null + return [ps.id, def] + }) + ) ) + // Per-preference-choice inline sub-choices: { [choiceId]: subChoice | null } + const [selectedSubChoices, setSelectedSubChoices] = useState(() => { + const init = {} + preferenceSets.forEach(ps => { + const def = ps.default_choice_id != null + ? ps.choices.find(c => c.id === ps.default_choice_id) ?? null + : null + if (def && def.sub_choices?.length > 0) { + const subDef = def.sub_choices.find(s => s.is_default) ?? def.sub_choices[0] + init[def.id] = subDef + } + }) + return init + }) + + // Shared-subset selections: { [setId]: subChoice | null } + const [selectedSharedSubs, setSelectedSharedSubs] = useState(() => { + const init = {} + preferenceSets.forEach(ps => { + if (ps.shared_subset?.choices?.length > 0) { + const selectedChoice = ps.default_choice_id != null + ? ps.choices.find(c => c.id === ps.default_choice_id) ?? null + : null + if (!selectedChoice || !selectedChoice.disables_subset) { + const subDef = ps.shared_subset.choices.find(s => s.is_default) ?? ps.shared_subset.choices[0] + init[ps.id] = subDef + } + } + }) + return init + }) + + // Option sub-choices: { [optionId]: subChoice | null } + // Initialise with any option that has a default sub-choice pre-selected — but only + // if the option itself is checked by default (options are all unchecked initially). + const [selectedOptionSubs, setSelectedOptionSubs] = useState({}) + function selectPreference(setId, choice) { setSelectedPreferences(prev => ({ ...prev, [setId]: choice })) + if (choice && choice.sub_choices?.length > 0) { + const subDef = choice.sub_choices.find(s => s.is_default) ?? choice.sub_choices[0] + setSelectedSubChoices(prev => ({ ...prev, [choice.id]: subDef })) + } + const ps = preferenceSets.find(p => p.id === setId) + if (ps?.shared_subset?.choices?.length > 0 && !choice?.disables_subset) { + setSelectedSharedSubs(prev => { + if (prev[setId] != null) return prev + const subDef = ps.shared_subset.choices.find(s => s.is_default) ?? ps.shared_subset.choices[0] + return { ...prev, [setId]: subDef } + }) + } + } + + function selectSubChoice(parentChoiceId, sub) { + setSelectedSubChoices(prev => ({ ...prev, [parentChoiceId]: sub })) + } + + function selectSharedSub(setId, sub) { + setSelectedSharedSubs(prev => ({ ...prev, [setId]: sub })) } function toggleOption(opt) { setSelectedOptions(prev => { const exists = prev.find(o => o.id === opt.id) - if (exists) return prev.filter(o => o.id !== opt.id) + if (exists) { + // Deselecting: also clear its sub-choice selection + setSelectedOptionSubs(s => { const n = { ...s }; delete n[opt.id]; return n }) + return prev.filter(o => o.id !== opt.id) + } + // Selecting: pre-select default sub-choice if any + if (opt.sub_choices?.length > 0) { + const subDef = opt.sub_choices.find(s => s.is_default) ?? opt.sub_choices[0] + setSelectedOptionSubs(s => ({ ...s, [opt.id]: subDef })) + } return [...prev, { id: opt.id, name: opt.name, price_delta: opt.extra_cost ?? 0 }] }) } + function selectOptionSub(optId, sub) { + setSelectedOptionSubs(prev => ({ ...prev, [optId]: sub })) + } + function toggleIngredient(ing) { setRemovedIngredients(prev => prev.includes(ing.name) ? prev.filter(n => n !== ing.name) : [...prev, ing.name] ) } - const prefExtra = Object.values(selectedPreferences).reduce((s, ch) => s + (ch?.extra_cost ?? 0), 0) - const extraPrice = selectedOptions.reduce((s, o) => s + (o.price_delta ?? o.extra_cost ?? 0), 0) + prefExtra - const totalPrice = (product.base_price + extraPrice) * quantity + // Check whether any checked option with sub_choices is missing its sub-choice selection + const optionSubsMissing = selectedOptions.some(o => { + const full = options.find(opt => opt.id === o.id) + return full?.sub_choices?.length > 0 && selectedOptionSubs[o.id] == null + }) + + function isPrefSetComplete(ps) { + const choice = selectedPreferences[ps.id] + if (choice == null) return false + if (choice.sub_choices?.length > 0 && selectedSubChoices[choice.id] == null) return false + if (ps.shared_subset?.choices?.length > 0 && !choice.disables_subset && selectedSharedSubs[ps.id] == null) return false + return true + } + + const allPrefsSelected = preferenceSets.every(isPrefSetComplete) + const unselectedPrefs = preferenceSets.filter(ps => !isPrefSetComplete(ps)) + const canAdd = allPrefsSelected && !optionSubsMissing + + const prefExtra = preferenceSets.reduce((s, ps) => { + const choice = selectedPreferences[ps.id] + if (!choice) return s + const inlineSub = choice.sub_choices?.length > 0 ? (selectedSubChoices[choice.id] ?? null) : null + const sharedSub = (ps.shared_subset?.choices?.length > 0 && !choice.disables_subset) + ? (selectedSharedSubs[ps.id] ?? null) : null + return s + (choice.extra_cost ?? 0) + (inlineSub?.extra_cost ?? 0) + (sharedSub?.extra_cost ?? 0) + }, 0) + const optionExtra = selectedOptions.reduce((s, o) => { + const subExtra = selectedOptionSubs[o.id]?.extra_cost ?? 0 + return s + (o.price_delta ?? 0) + subExtra + }, 0) + const totalPrice = (product.base_price + optionExtra + prefExtra) * quantity function handleAdd() { - const prefChoices = Object.values(selectedPreferences) - .filter(Boolean) - .map(ch => ({ id: ch.id, name: ch.name, price_delta: ch.extra_cost ?? 0 })) + if (!canAdd) return + const prefChoices = preferenceSets.flatMap(ps => { + const choice = selectedPreferences[ps.id] + if (!choice) return [] + const entries = [{ id: choice.id, name: choice.name, price_delta: choice.extra_cost ?? 0 }] + const inlineSub = choice.sub_choices?.length > 0 ? (selectedSubChoices[choice.id] ?? null) : null + if (inlineSub) entries.push({ id: null, name: inlineSub.name, price_delta: inlineSub.extra_cost ?? 0 }) + if (ps.shared_subset?.choices?.length > 0 && !choice.disables_subset) { + const sharedSub = selectedSharedSubs[ps.id] ?? null + if (sharedSub) entries.push({ id: null, name: sharedSub.name, price_delta: sharedSub.extra_cost ?? 0 }) + } + return entries + }) + + const optionEntries = selectedOptions.flatMap(o => { + const entries = [{ id: o.id, name: o.name, price_delta: o.price_delta ?? 0 }] + const sub = selectedOptionSubs[o.id] + if (sub) entries.push({ id: null, name: sub.name, price_delta: sub.extra_cost ?? 0 }) + return entries + }) + onAdd({ product_id: product.id, quantity, - selected_options: [...selectedOptions, ...prefChoices], + selected_options: [...optionEntries, ...prefChoices], removed_ingredients: removedIngredients, notes, }) @@ -57,51 +181,138 @@ export default function ItemOptionsModal({ product, onAdd, onClose }) {

{product.name}

{Number(totalPrice).toFixed(2)} €

+ {/* ── Checkbox options with optional sub-choices ── */} {options.length > 0 && (

Επιλογές

- {options.map(opt => ( - - ))} + {options.map(opt => { + const isChecked = !!selectedOptions.find(o => o.id === opt.id) + const hasSubs = opt.sub_choices?.length > 0 + const subMissing = isChecked && hasSubs && selectedOptionSubs[opt.id] == null + return ( +
+ + {isChecked && hasSubs && ( +
+ {subMissing &&

— απαιτείται επιλογή

} + {opt.sub_choices.map((sub, si) => ( + + ))} +
+ )} +
+ ) + })}
)} - {preferenceSets.map(ps => ( -
-

{ps.name}

- {ps.choices.map(ch => ( - - ))} -
- ))} + {/* ── Preference sets ── */} + {preferenceSets.map(ps => { + const missing = !isPrefSetComplete(ps) + const selectedChoice = selectedPreferences[ps.id] ?? null + const showSharedSubset = ps.shared_subset?.choices?.length > 0 + && selectedChoice != null + && !selectedChoice.disables_subset + const sharedMissing = showSharedSubset && selectedSharedSubs[ps.id] == null + return ( +
+

+ {ps.name} + {missing && — απαιτείται επιλογή} +

+ + {ps.choices.map(ch => { + const isSelected = selectedPreferences[ps.id]?.id === ch.id + const hasSubs = ch.sub_choices?.length > 0 + const subMissing = isSelected && hasSubs && selectedSubChoices[ch.id] == null + return ( +
+ + {isSelected && hasSubs && ( +
+ {subMissing &&

— απαιτείται επιλογή

} + {ch.sub_choices.map((sub, si) => ( + + ))} +
+ )} +
+ ) + })} + + {showSharedSubset && ( +
+

+ {ps.shared_subset.name} + {sharedMissing && — απαιτείται επιλογή} +

+ {ps.shared_subset.choices.map((sub, si) => ( + + ))} +
+ )} +
+ ) + })} + + {/* ── Remove ingredients ── */} {ingredients.length > 0 && (

Αφαίρεση υλικών

{ingredients.map(ing => ( ))} @@ -110,13 +321,8 @@ export default function ItemOptionsModal({ product, onAdd, onClose }) {

Σημείωση

-