From 30c487942fb1f667ce210523ea26e769012d7a77 Mon Sep 17 00:00:00 2001 From: goro Date: Sun, 7 Jun 2026 04:14:37 +0300 Subject: [PATCH] make the create menu items as a modal --- src/pages/AddLocation/MenuItemsModal.tsx | 114 +++++++++++++++++++++++ src/pages/AddLocation/index.tsx | 70 +++++--------- src/pages/AddLocation/style.css | 55 +++++++++++ 3 files changed, 190 insertions(+), 49 deletions(-) create mode 100644 src/pages/AddLocation/MenuItemsModal.tsx diff --git a/src/pages/AddLocation/MenuItemsModal.tsx b/src/pages/AddLocation/MenuItemsModal.tsx new file mode 100644 index 0000000..62eeea4 --- /dev/null +++ b/src/pages/AddLocation/MenuItemsModal.tsx @@ -0,0 +1,114 @@ +import { useState } from "preact/compat"; +import { MenuItem } from "./types"; + +const CATEGORIES = [ + { value: 'appetizer', label: 'Appetizer' }, + { value: 'main_course', label: 'Main Course' }, + { value: 'dessert', label: 'Dessert' }, + { value: 'beverages', label: 'Beverages' }, + { value: 'snack', label: 'Snack' }, +]; + +const emptyItem = (): MenuItem => ({ name: '', price: 0, category: '', description: '' }); + +interface MenuItemsModalProps { + selected: Array; + onSave: (items: Array) => void; + onClose: () => void; +} + +export default function MenuItemsModal({ selected, onSave, onClose }: MenuItemsModalProps) { + const [items, setItems] = useState>(selected.length > 0 ? selected : [emptyItem()]); + + function onChange(idx: number, field: keyof MenuItem, value: string) { + setItems(prev => prev.map((item, i) => + i === idx ? { ...item, [field]: field === 'price' ? parseInt(value) || 0 : value } : item + )); + } + + function onAdd() { + setItems(prev => [...prev, emptyItem()]); + } + + function onDelete(idx: number) { + setItems(prev => prev.filter((_, i) => i !== idx)); + } + + function handleSave() { + const valid = items.filter(item => item.name.trim() !== ''); + onSave(valid); + } + + return ( +
+
e.stopPropagation()}> +
+ Restaurant Menu + +
+ +
+ {items.map((item, idx) => ( +
+
+
+ Name * + onChange(idx, 'name', (e.target as HTMLInputElement).value)} + placeholder="e.g. Nasi Goreng" + /> +
+
+ Price (IDR) + onChange(idx, 'price', (e.target as HTMLInputElement).value)} + /> +
+
+ Category + +
+
+ Description + onChange(idx, 'description', (e.target as HTMLInputElement).value)} + placeholder="Optional" + /> +
+ +
+
+ ))} + +
+ +
+ {items.filter(i => i.name.trim()).length} item(s) +
+ + +
+
+
+
+ ); +} diff --git a/src/pages/AddLocation/index.tsx b/src/pages/AddLocation/index.tsx index 63d596f..399fce1 100755 --- a/src/pages/AddLocation/index.tsx +++ b/src/pages/AddLocation/index.tsx @@ -4,7 +4,7 @@ import { LocationInfo } from "../../domains/LocationInfo"; import { DropdownInput } from "../../components"; import { IndonesiaRegionsInfo, LocationType } from "../../types/common"; import { Regency, emptyRegency } from "../../domains"; -import { Form, MenuItem } from './types'; +import { Form } from './types'; import { enumKeys } from "../../utils"; import './style.css'; import { createLocationService } from "../../services/locations"; @@ -13,6 +13,7 @@ import { UserRootState } from "../../store/type"; import DefaultLoadingAnimation from "../../components/LoadingAnimation/Default"; import FallbackImage from "../../../src/components/Img/FallbackImage"; import AmenitiesModal from "./AmenitiesModal"; +import MenuItemsModal from "./MenuItemsModal"; function AddLocation() { const [recentLocations, setRecentLocations] = useState>() @@ -30,6 +31,7 @@ function AddLocation() { restaurant_menu: [], }) const [showAmenitiesModal, setShowAmenitiesModal] = useState(false) + const [showMenuModal, setShowMenuModal] = useState(false) const [submitLoading, setSubmitLoading ] = useState(false) const user = useSelector((state: UserRootState) => state.auth) @@ -70,23 +72,6 @@ function AddLocation() { setForm({ ...form, location_type: value, amenities: [], restaurant_menu: [] }) } - function onAddMenuItem(e: TargetedEvent) { - e.preventDefault() - setForm({ ...form, restaurant_menu: [...form.restaurant_menu, { name: '', price: 0, category: '', description: '' }] }) - } - - function onChangeMenuItem(idx: number, field: keyof MenuItem, value: string) { - const updated = form.restaurant_menu.map((item, i) => - i === idx ? { ...item, [field]: field === 'price' ? parseInt(value) || 0 : value } : item - ) - setForm({ ...form, restaurant_menu: updated }) - } - - function onDeleteMenuItem(e: TargetedEvent, idx: number) { - e.preventDefault() - setForm({ ...form, restaurant_menu: form.restaurant_menu.filter((_, i) => i !== idx) }) - } - async function onSubmitForm(e: TargetedEvent) { e.preventDefault(); @@ -222,38 +207,18 @@ function AddLocation() { {form.location_type === LocationType.Culinary && (
- Restaurant Menu - {form.restaurant_menu.map((item, idx) => ( -
-
-
- Name * - onChangeMenuItem(idx, 'name', (e.target as HTMLInputElement).value)} /> -
-
- Price (IDR) * - onChangeMenuItem(idx, 'price', (e.target as HTMLInputElement).value)} /> -
-
- Category - -
-
- Description - onChangeMenuItem(idx, 'description', (e.target as HTMLInputElement).value)} /> -
- onDeleteMenuItem(e, idx)} style={{ cursor: 'pointer', color: '#ed4245', fontWeight: 'bold', paddingBottom: 4 }}>✕ -
+ + {form.restaurant_menu.length > 0 && ( +
+ {form.restaurant_menu.map(item => ( + {item.name}{item.category ? ` · ${item.category}` : ''} + ))}
- ))} - + )}
)} {(form.location_type === LocationType.Accommodation || form.location_type === LocationType.Culinary || form.location_type === LocationType.Mall) && ( @@ -340,6 +305,13 @@ function AddLocation() { onClose={() => setShowAmenitiesModal(false)} /> )} + {showMenuModal && ( + { setForm({ ...form, restaurant_menu }); setShowMenuModal(false); }} + onClose={() => setShowMenuModal(false)} + /> + )} ) } diff --git a/src/pages/AddLocation/style.css b/src/pages/AddLocation/style.css index 60941d9..0a720c3 100755 --- a/src/pages/AddLocation/style.css +++ b/src/pages/AddLocation/style.css @@ -218,4 +218,59 @@ border-radius: 20px; font-size: 12px; opacity: 0.8; +} + +/* Menu Items Modal */ +.menu-item-row { + border-bottom: 1px solid #202225; + padding-bottom: 12px; + margin-bottom: 12px; +} + +.menu-item-row:last-of-type { + border-bottom: none; + margin-bottom: 0; +} + +.menu-item-fields { + display: flex; + gap: 8px; + align-items: flex-end; + flex-wrap: wrap; +} + +.menu-item-field { + display: flex; + flex-direction: column; + gap: 4px; + flex: 1 1 140px; +} + +.menu-item-label { + font-size: 11px; + opacity: 0.6; + text-transform: uppercase; + letter-spacing: 0.8px; +} + +.menu-item-input { + padding: 5px 8px; + border-radius: 5px; + width: 100%; +} + +.menu-item-delete-btn { + background: none; + border: none; + color: #ed4245; + cursor: pointer; + font-size: 15px; + padding: 0 4px; + align-self: flex-end; + margin-bottom: 2px; + opacity: 0.7; +} + +.menu-item-delete-btn:hover { + opacity: 1; } \ No newline at end of file