feat(manager): add Digital Menu tab to ProductFormModal

Adds a new "Digital Menu" tab with three sections:
- Visibility: digital_visible / digital_available toggles
- Display overrides: digital_name, digital_description, digital_image_url
- Pricing: digital_price / digital_discount with live customer-price preview

All 7 fields are wired into form state (buildFormFromProduct) and
included in the PUT request body (buildBody).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 09:43:42 +03:00
parent 7958083fd8
commit 37fb25c8c2

View File

@@ -139,6 +139,13 @@ function buildFormFromProduct(product) {
} : null,
is_favorite: ps.is_favorite ?? false, favorite_sort_order: ps.favorite_sort_order ?? 0,
})) ?? [],
digital_visible: product.digital_visible ?? true,
digital_available: product.digital_available ?? true,
digital_name: product.digital_name ?? '',
digital_description: product.digital_description ?? '',
digital_image_url: product.digital_image_url ?? '',
digital_price: product.digital_price ?? '',
digital_discount: product.digital_discount ?? '',
}
}
@@ -425,6 +432,13 @@ export default function ProductFormModal({ product, categories, printers, onSave
})),
is_favorite: ps.is_favorite ?? false, favorite_sort_order: ps.favorite_sort_order ?? 0,
})),
digital_visible: form.digital_visible,
digital_available: form.digital_available,
digital_name: form.digital_name || null,
digital_description: form.digital_description || null,
digital_image_url: form.digital_image_url || null,
digital_price: form.digital_price !== '' ? parseFloat(form.digital_price) : null,
digital_discount: form.digital_discount !== '' ? parseFloat(form.digital_discount) : null,
}
}
@@ -463,6 +477,7 @@ export default function ProductFormModal({ product, categories, printers, onSave
{ key: 'options', label: 'Έξτρα', count: form.options.length },
...form.preference_sets.map((ps, i) => ({ key: i, label: ps.name || `Προτ. ${i + 1}`, count: ps.choices.length })),
{ key: '__add_pref__', label: ' Προτίμηση', isAdd: true },
{ key: 'digital_menu', label: 'Digital Menu' },
]
const favList = buildFavoritesList(form)
@@ -699,6 +714,94 @@ export default function ProductFormModal({ product, categories, printers, onSave
</div>
)}
{/* Digital Menu tab */}
{activeTab === 'digital_menu' && (() => {
const basePrice = parseFloat(form.base_price) || 0
const dp = form.digital_price !== '' ? parseFloat(form.digital_price) : null
const disc = form.digital_discount !== '' ? parseFloat(form.digital_discount) : null
let customerPrice = basePrice
if (dp !== null && !isNaN(dp)) customerPrice = dp
else if (disc !== null && !isNaN(disc) && disc > 0) customerPrice = Math.round(basePrice * (1 - disc / 100) * 100) / 100
return (
<div className="space-y-6">
{/* Section 1 — Visibility */}
<div>
<p className="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-3">Ορατότητα</p>
<div className="space-y-3">
<label className="flex items-center gap-3 cursor-pointer select-none">
<input type="checkbox" checked={form.digital_visible}
onChange={e => setField('digital_visible', e.target.checked)}
className="w-4 h-4 accent-primary-700" />
<span className="text-sm font-medium text-gray-700">Show on digital menu</span>
</label>
<div>
<label className="flex items-center gap-3 cursor-pointer select-none">
<input type="checkbox" checked={form.digital_available}
onChange={e => setField('digital_available', e.target.checked)}
className="w-4 h-4 accent-primary-700" />
<span className="text-sm font-medium text-gray-700">Available for ordering</span>
</label>
<p className="text-xs text-gray-400 mt-1 ml-7">Uncheck to show as &lsquo;Out of stock&rsquo;</p>
</div>
</div>
</div>
{/* Section 2 — Display overrides */}
<div>
<p className="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-1">Display overrides</p>
<p className="text-xs text-gray-400 mb-3">Leave blank to use the standard product value</p>
<div className="space-y-3">
<div>
<label className="label">Display name (override)</label>
<input className="input" value={form.digital_name}
onChange={e => setField('digital_name', e.target.value)}
placeholder={form.name || 'Leave blank to use product name'} />
</div>
<div>
<label className="label">Description</label>
<textarea className="input resize-none" rows={3}
value={form.digital_description}
onChange={e => setField('digital_description', e.target.value)}
placeholder="Leave blank to use product description"
style={{ lineHeight: 1.5, fontSize: 13 }} />
</div>
<div>
<label className="label">Image URL</label>
<input className="input" value={form.digital_image_url}
onChange={e => setField('digital_image_url', e.target.value)}
placeholder="https://…" />
</div>
</div>
</div>
{/* Section 3 — Pricing */}
<div>
<p className="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-3">Pricing</p>
<div className="space-y-3">
<div>
<label className="label">Online price (override)</label>
<input type="number" step="0.01" min="0" className="input w-40"
value={form.digital_price}
onChange={e => setField('digital_price', e.target.value)}
placeholder="e.g. 9.50" />
</div>
<div>
<label className="label">Discount %</label>
<input type="number" step="1" min="0" max="100" className="input w-40"
value={form.digital_discount}
onChange={e => setField('digital_discount', e.target.value)}
placeholder="e.g. 10 for 10% off" />
</div>
<div className="rounded-lg bg-green-50 border border-green-200 px-4 py-3 text-sm text-green-800">
Customer sees: <span className="font-bold">{customerPrice.toFixed(2)}</span>
</div>
</div>
</div>
</div>
)
})()}
{/* Preference set tab */}
{typeof activeTab === 'number' && form.preference_sets[activeTab] && (() => {
const si = activeTab