Skip to content

Commit fd3795f

Browse files
committed
new student, kitchen and admin demo pages — chalkboard menu, KDS queue board, daily cash book with shared demo store
1 parent 4cb8359 commit fd3795f

12 files changed

Lines changed: 3286 additions & 0 deletions

File tree

src/app/demo/_lib/data.ts

Lines changed: 409 additions & 0 deletions
Large diffs are not rendered by default.

src/app/demo/_lib/fonts.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
Rozha_One,
3+
Mukta,
4+
Spline_Sans_Mono,
5+
Caveat,
6+
Barlow,
7+
Barlow_Semi_Condensed,
8+
IBM_Plex_Mono,
9+
Alegreya,
10+
Familjen_Grotesk,
11+
Courier_Prime,
12+
} from "next/font/google";
13+
14+
const rozha = Rozha_One({ subsets: ["latin"], weight: "400", variable: "--font-rozha", display: "swap" });
15+
const mukta = Mukta({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--font-mukta", display: "swap" });
16+
const splineMono = Spline_Sans_Mono({
17+
subsets: ["latin"],
18+
weight: ["400", "500", "600", "700"],
19+
variable: "--font-spline-mono",
20+
display: "swap",
21+
});
22+
const caveat = Caveat({ subsets: ["latin"], weight: ["500", "600", "700"], variable: "--font-caveat", display: "swap" });
23+
24+
const barlow = Barlow({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--font-barlow", display: "swap" });
25+
const barlowSC = Barlow_Semi_Condensed({
26+
subsets: ["latin"],
27+
weight: ["600", "700", "800"],
28+
variable: "--font-barlow-sc",
29+
display: "swap",
30+
});
31+
const plexMono = IBM_Plex_Mono({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--font-plex-mono", display: "swap" });
32+
33+
const alegreya = Alegreya({ subsets: ["latin"], weight: ["500", "700"], style: ["normal", "italic"], variable: "--font-alegreya", display: "swap" });
34+
const familjen = Familjen_Grotesk({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--font-familjen", display: "swap" });
35+
const courierPrime = Courier_Prime({ subsets: ["latin"], weight: ["400", "700"], variable: "--font-courier", display: "swap" });
36+
37+
export const studentFontVars = [rozha.variable, mukta.variable, splineMono.variable, caveat.variable].join(" ");
38+
export const kitchenFontVars = [barlow.variable, barlowSC.variable, plexMono.variable].join(" ");
39+
export const adminFontVars = [alegreya.variable, familjen.variable, courierPrime.variable].join(" ");

src/app/demo/_lib/store.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"use client";
2+
3+
/**
4+
* localStorage-backed store shared by the three demo pages. Keys match the
5+
* static demos in /public/demo, so orders placed here land in the kitchen
6+
* demo and roll up in the admin demo. Production replaces this with
7+
* Supabase tables + Realtime channels.
8+
*/
9+
10+
import { CANTEENS, DEFAULT_ID, DEMO_CANTEEN_IDS, type Special, type TicketDiet, type TicketStatus } from "./data";
11+
12+
export const STORAGE_CANTEEN = "tray_canteen";
13+
export const INBOX_KEY = "tray_kitchen_inbox";
14+
15+
export function specialsKey(canteenId: string) {
16+
return `tray_specials_${canteenId || "aditya"}`;
17+
}
18+
19+
function emit(key: string, newValue: string) {
20+
try {
21+
window.dispatchEvent(new StorageEvent("storage", { key, newValue }));
22+
} catch {
23+
/* older browsers */
24+
}
25+
}
26+
27+
export function getSelectedCanteenId(): string {
28+
if (typeof window === "undefined") return DEFAULT_ID;
29+
const id = localStorage.getItem(STORAGE_CANTEEN);
30+
if (id && (DEMO_CANTEEN_IDS as readonly string[]).includes(id) && CANTEENS[id]) return id;
31+
return DEFAULT_ID;
32+
}
33+
34+
export function setSelectedCanteenId(id: string) {
35+
if (!CANTEENS[id] || !(DEMO_CANTEEN_IDS as readonly string[]).includes(id)) return;
36+
if (localStorage.getItem(STORAGE_CANTEEN) === id) return;
37+
localStorage.setItem(STORAGE_CANTEEN, id);
38+
emit(STORAGE_CANTEEN, id);
39+
}
40+
41+
export function getSpecials(canteenId: string): Special[] {
42+
if (typeof window === "undefined") return [];
43+
try {
44+
const list = JSON.parse(localStorage.getItem(specialsKey(canteenId)) || "[]");
45+
if (!Array.isArray(list)) return [];
46+
const nowTs = Date.now();
47+
return list.map((s, i) => {
48+
let addedAt = Number(s?.addedAt);
49+
if (!addedAt || nowTs - addedAt > 90 * 60 * 1000 || addedAt > nowTs) {
50+
addedAt = nowTs - (6 + i * 8) * 60 * 1000;
51+
}
52+
return {
53+
id: String(s?.id || `sp-${i}`),
54+
name: String(s?.name || "Chef special"),
55+
desc: String(s?.desc || "Fresh counter special"),
56+
price: Number(s?.price || 120),
57+
prep: Number(s?.prep || 6),
58+
diet: (s?.diet === "nonveg" ? "nonveg" : "veg") as TicketDiet,
59+
icon: String(s?.icon || (s?.name ? String(s.name).charAt(0) : "S")).slice(0, 2).toUpperCase(),
60+
addedAt,
61+
};
62+
});
63+
} catch {
64+
return [];
65+
}
66+
}
67+
68+
export function setSpecials(canteenId: string, list: Special[]) {
69+
const key = specialsKey(canteenId);
70+
const val = JSON.stringify(list);
71+
localStorage.setItem(key, val);
72+
emit(key, val);
73+
}
74+
75+
export interface InboxTicket {
76+
id: string;
77+
student: string;
78+
status: TicketStatus;
79+
placedAt: number;
80+
total: number;
81+
otp: string;
82+
canteenId: string;
83+
items: { name: string; diet: TicketDiet; tgt: number; q: number; special?: boolean }[];
84+
}
85+
86+
export function readInbox(): InboxTicket[] {
87+
if (typeof window === "undefined") return [];
88+
try {
89+
const list = JSON.parse(localStorage.getItem(INBOX_KEY) || "[]");
90+
return Array.isArray(list) ? list : [];
91+
} catch {
92+
return [];
93+
}
94+
}
95+
96+
export function writeInbox(list: InboxTicket[]) {
97+
localStorage.setItem(INBOX_KEY, JSON.stringify(list));
98+
emit(INBOX_KEY, "1");
99+
}
100+
101+
export function pushInbox(ticket: InboxTicket) {
102+
const inbox = readInbox();
103+
inbox.push(ticket);
104+
writeInbox(inbox);
105+
}
106+
107+
export function updateInboxStatus(id: string, status: TicketStatus) {
108+
const inbox = readInbox();
109+
const t = inbox.find((x) => x.id === id);
110+
if (!t) return;
111+
t.status = status;
112+
writeInbox(inbox);
113+
}
114+
115+
/** Re-render on cross-tab storage changes for the given key prefixes. */
116+
export function subscribeStorage(keys: string[], cb: () => void) {
117+
const handler = (e: StorageEvent) => {
118+
if (!e.key || keys.some((k) => e.key === k || e.key!.startsWith(k))) cb();
119+
};
120+
window.addEventListener("storage", handler);
121+
return () => window.removeEventListener("storage", handler);
122+
}
123+
124+
export function fmtClock(ts: number) {
125+
const d = new Date(ts);
126+
let h = d.getHours();
127+
const ampm = h >= 12 ? "PM" : "AM";
128+
h = h % 12 || 12;
129+
return `${h}:${String(d.getMinutes()).padStart(2, "0")} ${ampm}`;
130+
}

0 commit comments

Comments
 (0)