diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 22b3044..10ea5d5 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -8,7 +8,7 @@ type Slide = { id: number; title: string; subtitle: string; image_url: string | type Room = { id: number; name: string; description: string; price: string; photos: string[]; featured_photo: string | null; active: boolean; sort_order: number; amenity_ids?: number[] }; type CalendarEvent = { id: number; date: string; title: string; type: string; color: string; description: string | null; active: boolean }; type SocialEvent = { id: number; name: string; description: string; price: string | null; date: string | null; photos: string[]; featured_photo: string | null; active: boolean; sort_order: number }; -type MenuItem = { id: number; category: string; name: string; description: string; price: string; is_daily_special: boolean; active: boolean; sort_order: number }; +type MenuItem = { id: number; category: string; name: string; description: string; price: string; is_daily_special: boolean; active: boolean; sort_order: number; photos: string[]; featured_photo: string | null }; type Place = { id: number; name: string; description: string; photos: string[]; featured_photo: string | null; distance_km: string; visit_time: string; suggestion: string; active: boolean; sort_order: number }; type Review = { id: number; author_name: string; author_email: string | null; rating: number; text: string; approved: boolean }; type FooterConfig = { logo: string; favicon: string; tagline: string; facebook_url: string; instagram_url: string; whatsapp_url: string; address: string; phone: string; email: string }; @@ -1408,7 +1408,7 @@ function MenuSection({ items, setItems, images, onToast }: any) { return (
setEditing({ id: 0, category: '', name: '', description: '', price: '', is_daily_special: false, active: true, sort_order: items.length })} size="sm">+ New Item} /> + action={ setEditing({ id: 0, category: '', name: '', description: '', price: '', is_daily_special: false, active: true, sort_order: items.length, photos: [], featured_photo: null })} size="sm">+ New Item} /> {sorted.length === 0 ? : (
{/* Drag handle instruction */} @@ -1523,16 +1523,62 @@ function MenuSection({ items, setItems, images, onToast }: any) { } function MenuEditor({ item, setItem, onSave, onCancel }: any) { - const i = { ...item }; + const [localPhotos, setLocalPhotos] = useState(item.photos || []); + const i = { ...item, photos: localPhotos }; + + const addPhotos = async (files: FileList) => { + const newPhotos: string[] = []; + for (const f of Array.from(files)) { + const b64 = await readFileBase64(f); + try { + const res = await fetch('/api/admin/images', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ filename: f.name, data: b64, category: 'menu', room_id: null, location_target: `menu-${i.id || 'new'}` }), + }); + if (res.ok) { const d = await res.json(); newPhotos.push(d.data.data); } + } catch { const b = await readFileBase64(f); newPhotos.push(b); } + } + setLocalPhotos(prev => [...prev, ...newPhotos]); + }; + return (
-
+

{i.id ? 'Edit Item' : 'New Item'}

setItem({ ...i, category: v })} /> setItem({ ...i, name: v })} /> setItem({ ...i, description: v })} /> setItem({ ...i, price: v })} /> + + {/* Photo upload */} +
+ +
+ document.getElementById(`menu-photos-${i.id}`)?.click()} size="sm" variant="secondary">+ Add Photos + { addPhotos(e.target.files!); e.target.value = ''; }} /> +
+
+ {localPhotos.map((ph, idx) => ( +
+ setItem({ ...i, featured_photo: ph })} + style={{ + width: 64, height: 48, objectFit: 'cover', borderRadius: 6, border: i.featured_photo === ph ? `2px solid ${C.gold}` : '1px solid #eee', + cursor: 'pointer', + }} + /> + +
+ ))} +
+ {localPhotos.length > 0 &&
Click a photo to set as featured
} +
+ setItem({ ...i, is_daily_special: v })} /> setItem({ ...i, active: v })} />
diff --git a/src/app/api/menu/[id]/route.ts b/src/app/api/menu/[id]/route.ts index 746c7da..a4f178a 100644 --- a/src/app/api/menu/[id]/route.ts +++ b/src/app/api/menu/[id]/route.ts @@ -8,12 +8,12 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ const { id: idStr } = await params; const id = parseInt(idStr, 10); const body = await request.json(); - const { category, name, description, price, is_daily_special, active, sort_order } = body; + const { category, name, description, price, is_daily_special, active, sort_order, photos, featured_photo } = body; const { rows } = await db.query( - `UPDATE menu_items SET category = $1, name = $2, description = $3, price = $4, is_daily_special = $5, active = $6, sort_order = $7 - WHERE id = $8 RETURNING *`, - [category, name, description || '', price, is_daily_special, active, sort_order || 0, id] + `UPDATE menu_items SET category = $1, name = $2, description = $3, price = $4, is_daily_special = $5, active = $6, sort_order = $7, photos = $8, featured_photo = $9 + WHERE id = $10 RETURNING *`, + [category, name, description || '', price, is_daily_special, active, sort_order || 0, photos || [], featured_photo || null, id] ); if (rows.length === 0) { diff --git a/src/app/api/menu/route.ts b/src/app/api/menu/route.ts index f47f860..4a5d96a 100644 --- a/src/app/api/menu/route.ts +++ b/src/app/api/menu/route.ts @@ -18,17 +18,17 @@ export async function POST(request: NextRequest) { try { await requireRole('admin'); const body = await request.json(); - const { category, name, description, price, is_daily_special, sort_order } = body; + const { category, name, description, price, is_daily_special, sort_order, photos, featured_photo } = body; if (!category || !name || price === undefined || price === '') { return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); } const { rows } = await db.query( - `INSERT INTO menu_items (category, name, description, price, is_daily_special, sort_order) - VALUES ($1, $2, $3, $4, $5, $6) + `INSERT INTO menu_items (category, name, description, price, is_daily_special, sort_order, photos, featured_photo) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, - [category, name, description || '', price, is_daily_special || false, sort_order || 0] + [category, name, description || '', price, is_daily_special || false, sort_order || 0, photos || [], featured_photo || null] ); return NextResponse.json({ data: rows[0] }); diff --git a/src/lib/schema.ts b/src/lib/schema.ts index a26103f..bb4726d 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -100,6 +100,8 @@ export async function initSchema() { is_daily_special BOOLEAN DEFAULT FALSE, active BOOLEAN DEFAULT TRUE, sort_order INTEGER DEFAULT 0, + photos TEXT[] DEFAULT '{}', + featured_photo VARCHAR(500), created_at TIMESTAMP DEFAULT NOW() ); `);