diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 893eef0..5c268a5 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -34,9 +34,11 @@ const NAV = [
{ id: 'private-events', label: 'Private Events', icon: '๐' },
{ id: 'rooms', label: 'Rooms', icon: '๐จ' },
{ id: 'room-status', label: 'Room Status', icon: '๐งน' },
+ { id: 'room-assignments', label: 'Assignments', icon: '๐' },
{ id: 'room-reservations', label: 'Room Bookings', icon: '๐๏ธ' },
{ id: 'restaurant-reservations', label: 'Restaurant', icon: '๐ฝ๏ธ' },
{ id: 'orders', label: 'Food Orders', icon: '๐ฆ' },
+ { id: 'kitchen', label: 'Kitchen', icon: '๐จโ๐ณ' },
{ id: 'amenities', label: 'Amenities', icon: 'โจ' },
{ id: 'trips', label: 'Trips', icon: 'โ๏ธ' },
{ id: 'messages', label: 'Messages', icon: '๐ฌ' },
@@ -1287,6 +1289,7 @@ function OrdersSection({ onToast }: { onToast: (msg: string, type: 'success' | '
{formatPrice(o.total)} โข {formatDisplayDateTime(o.created_at)}
+ {o.username && ๐ค {o.first_name || o.username}
}
void }) {
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [autoRefresh, setAutoRefresh] = useState(true);
+
+ const loadOrders = async () => {
+ try {
+ const res = await fetch('/api/orders?status=all');
+ const data = await res.json();
+ const active = (data.orders || []).filter((o: Order) => ['pending', 'preparing', 'ready'].includes(o.status));
+ setOrders(active);
+ } catch { onToast('Error loading orders', 'error'); }
+ setLoading(false);
+ };
+
+ useEffect(() => {
+ loadOrders();
+ if (autoRefresh) {
+ const interval = setInterval(loadOrders, 30000);
+ return () => clearInterval(interval);
+ }
+ }, [autoRefresh]);
+
+ const updateStatus = async (id: number, status: string) => {
+ try {
+ const res = await fetch('/api/orders', {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ order_id: id, status }),
+ });
+ if (res.ok) {
+ if (status === 'delivered' || status === 'cancelled') {
+ setOrders(o => o.filter(x => x.id !== id));
+ } else {
+ setOrders(o => o.map(x => x.id === id ? { ...x, status } : x));
+ }
+ onToast(`Order ${status}`, 'success');
+ }
+ } catch { onToast('Error updating order', 'error'); }
+ };
+
+ const formatPrice = (p: string) => `$${parseFloat(p).toFixed(2)}`;
+ const statusColor = (s: string) => s === 'pending' ? '#f59e0b' : s === 'preparing' ? '#3b82f6' : '#10b981';
+
+ const pending = orders.filter(o => o.status === 'pending');
+ const preparing = orders.filter(o => o.status === 'preparing');
+ const ready = orders.filter(o => o.status === 'ready');
+
+ const OrderCard = ({ o }: { o: Order }) => (
+
+
+
+
+ {o.order_type === 'room_delivery' ? `Room ${o.room_name || o.room_id}` : o.order_type === 'dine_in' ? 'Dine-in' : 'Pickup'}
+
+
+ {formatPrice(o.total)} - {formatDisplayDateTime(o.created_at)}
+
+ {o.username &&
User: {o.first_name || o.username}
}
+
+
{o.status.toUpperCase()}
+
+
+ {o.items?.map((item, i) => (
+
+ {item.quantity}x {item.name}
+ {formatPrice(item.total_price)}
+
+ ))}
+
+ {o.notes &&
Note: "{o.notes}"
}
+
+ {o.status === 'pending' && (
+ <>
+ updateStatus(o.id, 'preparing')}>Start Preparing
+ updateStatus(o.id, 'cancelled')}>Cancel
+ >
+ )}
+ {o.status === 'preparing' && updateStatus(o.id, 'ready')}>Mark Ready}
+ {o.status === 'ready' && updateStatus(o.id, 'delivered')}>Delivered}
+
+
+ );
+
+ return (
+
+
+
Kitchen Dashboard
+
+
+
+ {loading ?
: orders.length === 0 ? (
+
+ ) : (
+
+ {pending.length > 0 && (
+
+
Pending ({pending.length})
+
+ {pending.map(o => )}
+
+
+ )}
+ {preparing.length > 0 && (
+
+
Preparing ({preparing.length})
+
+ {preparing.map(o => )}
+
+
+ )}
+ {ready.length > 0 && (
+
+
Ready for Delivery ({ready.length})
+
+ {ready.map(o => )}
+
+
+ )}
+
+ )}
+
+ );
+}
+
+/* Room Assignments Section */
+type RoomAssignment = { id: number; room_id: number; room_name: string; user_id: number; username: string; first_name: string | null; last_name: string | null; email: string; check_in: string; check_out: string; notes: string | null };
+
+function RoomAssignmentsSection({ onToast }: { onToast: (msg: string, type: 'success' | 'error') => void }) {
+ const [assignments, setAssignments] = useState([]);
+ const [rooms, setRooms] = useState([]);
+ const [users, setUsers] = useState<{ id: number; username: string; first_name: string | null; last_name: string | null }[]>([]);
+ const [loading, setLoading] = useState(true);
+ const [showForm, setShowForm] = useState(false);
+ const [form, setForm] = useState({ room_id: '', user_id: '', check_in: '', check_out: '', notes: '' });
+
+ useEffect(() => {
+ Promise.all([
+ fetch('/api/room-assignments').then(r => r.json()),
+ fetch('/api/rooms').then(r => r.json()),
+ fetch('/api/admin/users').then(r => r.json()),
+ ]).then(([assignData, roomData, userData]) => {
+ setAssignments(assignData.assignments || []);
+ setRooms(roomData.rooms || []);
+ setUsers(userData.users || []);
+ }).catch(() => onToast('Error loading data', 'error'))
+ .finally(() => setLoading(false));
+ }, []);
+
+ const today = new Date().toISOString().split('T')[0];
+ const active = assignments.filter(a => a.check_in <= today && a.check_out >= today);
+ const upcoming = assignments.filter(a => a.check_in > today);
+
+ const saveAssignment = async () => {
+ if (!form.room_id || !form.user_id || !form.check_in || !form.check_out) {
+ onToast('All fields required', 'error');
+ return;
+ }
+ try {
+ const res = await fetch('/api/room-assignments', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(form),
+ });
+ if (res.ok) {
+ const data = await res.json();
+ setAssignments(a => [...a, data.assignment]);
+ setForm({ room_id: '', user_id: '', check_in: '', check_out: '', notes: '' });
+ setShowForm(false);
+ onToast('Assignment created', 'success');
+ } else {
+ const err = await res.json();
+ onToast(err.error || 'Failed to create assignment', 'error');
+ }
+ } catch { onToast('Error saving assignment', 'error'); }
+ };
+
+ const deleteAssignment = async (id: number) => {
+ if (!confirm('Delete this assignment?')) return;
+ try {
+ await fetch(`/api/room-assignments?id=${id}`, { method: 'DELETE' });
+ setAssignments(a => a.filter(x => x.id !== id));
+ onToast('Assignment deleted', 'success');
+ } catch { onToast('Error deleting assignment', 'error'); }
+ };
+
+ return (
+
+
setShowForm(true)}>+ Assign Room} />
+
+ {showForm && (
+
+
New Assignment
+
+ setForm(f => ({ ...f, room_id: v }))} options={rooms.map(r => ({ value: r.id.toString(), label: r.name }))} />
+ setForm(f => ({ ...f, user_id: v }))} options={users.map(u => ({ value: u.id.toString(), label: `${u.username} (${u.first_name || ''} ${u.last_name || ''})`.trim() }))} />
+ setForm(f => ({ ...f, check_in: v }))} />
+ setForm(f => ({ ...f, check_out: v }))} />
+
+
setForm(f => ({ ...f, notes: v }))} rows={2} />
+
+ Save Assignment
+ { setShowForm(false); setForm({ room_id: '', user_id: '', check_in: '', check_out: '', notes: '' }); }}>Cancel
+
+
+ )}
+
+ {loading ? : (
+
+
+
Currently Active ({active.length})
+ {active.length === 0 ? (
+
No active assignments
+ ) : (
+
+ {active.map(a => (
+
+
+
{a.room_name}
+
User: {a.first_name || a.username} {a.last_name || ''} ({a.email})
+
Dates: {a.check_in} to {a.check_out}
+ {a.notes &&
Note: {a.notes}
}
+
+
deleteAssignment(a.id)}>Remove
+
+ ))}
+
+ )}
+
+
+ {upcoming.length > 0 && (
+
+
Upcoming ({upcoming.length})
+
+ {upcoming.map(a => (
+
+
+
{a.room_name}
+
User: {a.first_name || a.username} {a.last_name || ''}
+
Dates: {a.check_in} to {a.check_out}
+
+
deleteAssignment(a.id)}>Remove
+
+ ))}
+
+
+ )}
+
+ )}
+
+ );
+}
+
function RoomsSection({ rooms, setRooms, images, onToast }: any) {
const [editing, setEditing] = useState(null);
const [confirmDel, setConfirmDel] = useState(null);
@@ -3388,6 +3652,8 @@ export default function AdminPage() {
case 'room-reservations': return ;
case 'restaurant-reservations': return ;
case 'orders': return ;
+ case 'kitchen': return ;
+ case 'room-assignments': return ;
case 'amenities': return ;
case 'menu': return ;
case 'places': return ;
diff --git a/src/app/api/orders/route.ts b/src/app/api/orders/route.ts
index a7bc8ed..eb8c026 100644
--- a/src/app/api/orders/route.ts
+++ b/src/app/api/orders/route.ts
@@ -122,6 +122,7 @@ export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const status = searchParams.get('status') || 'all';
const limit = parseInt(searchParams.get('limit') || '50');
+ const kitchen = searchParams.get('kitchen') === 'true';
let query = `
SELECT o.*, r.name as room_name, u.username, u.first_name, u.last_name
diff --git a/src/app/api/room-assignments/route.ts b/src/app/api/room-assignments/route.ts
new file mode 100644
index 0000000..483c34e
--- /dev/null
+++ b/src/app/api/room-assignments/route.ts
@@ -0,0 +1,111 @@
+import { NextResponse } from 'next/server';
+import { db } from '@/lib/db';
+import { getSession } from '@/lib/auth';
+
+// GET - list all room assignments (admin) or current user's assignment
+export async function GET(request: Request) {
+ try {
+ const user = await getSession();
+ if (!user) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const { searchParams } = new URL(request.url);
+ const onlyActive = searchParams.get('active') === 'true';
+
+ if (user.role !== 'admin') {
+ // Regular user - return their current active assignment
+ const today = new Date().toISOString().split('T')[0];
+ const { rows } = await db.query(`
+ SELECT ra.*, r.name as room_name, r.price
+ FROM room_assignments ra
+ JOIN rooms r ON ra.room_id = r.id
+ WHERE ra.user_id = $1 AND ra.check_in <= $2 AND ra.check_out >= $2
+ `, [user.id, today]);
+ return NextResponse.json({ assignments: rows });
+ }
+
+ // Admin - return all assignments
+ let query = `
+ SELECT ra.*, r.name as room_name, u.username, u.first_name, u.last_name, u.email
+ FROM room_assignments ra
+ JOIN rooms r ON ra.room_id = r.id
+ JOIN users u ON ra.user_id = u.id
+ `;
+ const params: any[] = [];
+
+ if (onlyActive) {
+ const today = new Date().toISOString().split('T')[0];
+ query += ' WHERE ra.check_in <= $1 AND ra.check_out >= $1';
+ params.push(today);
+ }
+
+ query += ' ORDER BY ra.check_in DESC';
+
+ const { rows } = await db.query(query, params);
+ return NextResponse.json({ assignments: rows });
+ } catch (error) {
+ console.error('Get room assignments error:', error);
+ return NextResponse.json({ error: 'Failed to get room assignments' }, { status: 500 });
+ }
+}
+
+// POST - create new room assignment (admin only)
+export async function POST(request: Request) {
+ try {
+ const user = await getSession();
+ if (!user || user.role !== 'admin') {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const { room_id, user_id, check_in, check_out, notes } = await request.json();
+
+ if (!room_id || !user_id || !check_in || !check_out) {
+ return NextResponse.json({ error: 'Room, user, check-in, and check-out are required' }, { status: 400 });
+ }
+
+ // Check for conflicting assignments
+ const { rows: conflicts } = await db.query(`
+ SELECT id FROM room_assignments
+ WHERE room_id = $1 AND check_in < $3 AND check_out > $2
+ `, [room_id, check_in, check_out]);
+
+ if (conflicts.length > 0) {
+ return NextResponse.json({ error: 'Room has conflicting assignment for these dates' }, { status: 400 });
+ }
+
+ const { rows } = await db.query(`
+ INSERT INTO room_assignments (room_id, user_id, check_in, check_out, notes, created_by)
+ VALUES ($1, $2, $3, $4, $5, $6)
+ RETURNING *
+ `, [room_id, user_id, check_in, check_out, notes || null, user.id]);
+
+ return NextResponse.json({ success: true, assignment: rows[0] });
+ } catch (error) {
+ console.error('Create room assignment error:', error);
+ return NextResponse.json({ error: 'Failed to create room assignment' }, { status: 500 });
+ }
+}
+
+// DELETE - remove room assignment (admin only)
+export async function DELETE(request: Request) {
+ try {
+ const user = await getSession();
+ if (!user || user.role !== 'admin') {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const { searchParams } = new URL(request.url);
+ const id = searchParams.get('id');
+
+ if (!id) {
+ return NextResponse.json({ error: 'Assignment ID required' }, { status: 400 });
+ }
+
+ await db.query('DELETE FROM room_assignments WHERE id = $1', [id]);
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ console.error('Delete room assignment error:', error);
+ return NextResponse.json({ error: 'Failed to delete room assignment' }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/coffee/page.tsx b/src/app/coffee/page.tsx
index f6c13c3..a8fb403 100644
--- a/src/app/coffee/page.tsx
+++ b/src/app/coffee/page.tsx
@@ -35,6 +35,7 @@ export default function CoffeePage() {
const [items, setItems] = useState