diff --git a/backend/app.db b/backend/app.db index 45094e0..79d03bf 100644 Binary files a/backend/app.db and b/backend/app.db differ diff --git a/backend/main.go b/backend/main.go index a92dd58..3bea6f7 100644 --- a/backend/main.go +++ b/backend/main.go @@ -3,6 +3,7 @@ package main import ( "database/sql" "encoding/json" + "fmt" "log" "net/http" "strconv" @@ -43,10 +44,16 @@ type Order struct { Status string `json:"status"` } +type FinalizedSummary struct { + Pickup []string `json:"pickup"` + Kitchen []string `json:"kitchen"` +} + type App struct { DB *sql.DB Broker *Broker FinalizeUpdate chan struct{} // notify scheduler to reload time + LastSummary *FinalizedSummary } type Broker struct { @@ -875,7 +882,7 @@ func finalizeOrders(app *App) error { } // Step 2: send summary (log, email, or message) - sendSummary(summary) + sendSummary(app, summary) // Step 3: archive _, err = app.DB.Exec("UPDATE orders SET status = 'history' WHERE status = 'active'") @@ -886,17 +893,64 @@ func finalizeOrders(app *App) error { if data, err := json.Marshal(msg); err == nil { app.Broker.broadcast <- data } + time.Sleep(20 * time.Millisecond) } return err } -func sendSummary(orders []Order) { - // TODO: replace with email or message - log.Println("Daily summary:") +func sendSummary(app *App, orders []Order) *FinalizedSummary { + // Pickup view + userMap := make(map[string][]string) for _, o := range orders { - log.Printf("%s: %s, %s, %s", o.Username, o.Soup, o.Main, o.Side) + items := []string{} + if o.Soup != "" { + items = append(items, o.Soup) + } + items = append(items, o.Main, o.Side) + userMap[o.Username] = append(userMap[o.Username], items...) } + pickup := []string{} + for user, items := range userMap { + pickup = append(pickup, fmt.Sprintf("%s: [%s]", user, strings.Join(items, ", "))) + } + + // Kitchen view + kitchenMap := make(map[string]int) + for _, o := range orders { + if o.Soup != "" { + kitchenMap[o.Soup]++ + } + kitchenMap[o.Main]++ + kitchenMap[o.Side]++ + } + kitchen := []string{} + for item, count := range kitchenMap { + kitchen = append(kitchen, fmt.Sprintf("%s: %d", item, count)) + } + + summary := &FinalizedSummary{Pickup: pickup, Kitchen: kitchen} + app.LastSummary = summary + + // Log + log.Println("=== Pickup view ===") + for _, line := range pickup { + log.Println(line) + } + log.Println("=== Kitchen view ===") + for _, line := range kitchen { + log.Println(line) + } + + return summary +} + +func (app *App) handleGetLastSummary(w http.ResponseWriter, r *http.Request) { + if app.LastSummary == nil { + writeJSON(w, map[string]string{"status": "none"}) + return + } + writeJSON(w, app.LastSummary) } func server(app *App) *http.Server { @@ -942,6 +996,7 @@ func server(app *App) *http.Server { r.Get("/finalize/time", app.handleGetFinalizeTime) r.Post("/finalize/time", app.handleSetFinalizeTime) r.Post("/finalize/now", app.handleFinalizeNow) + r.Get("/finalize/last", app.handleGetLastSummary) }) // Moderators (role <= 50) @@ -952,6 +1007,7 @@ func server(app *App) *http.Server { r.Get("/finalize/time", app.handleGetFinalizeTime) r.Post("/finalize/time", app.handleSetFinalizeTime) r.Post("/finalize/now", app.handleFinalizeNow) + r.Get("/finalize/last", app.handleGetLastSummary) }) // Return configured server @@ -973,12 +1029,11 @@ func main() { // Create server srv := server(app) + go startDailyCleanup(app) log.Println("Server listening on", srv.Addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } - go startDailyCleanup(app) - } diff --git a/backend/sqlite3.exe b/backend/sqlite3.exe new file mode 100644 index 0000000..51537af Binary files /dev/null and b/backend/sqlite3.exe differ diff --git a/feedmee/public/icons/icon-192.png b/feedmee/public/icons/icon-192.png new file mode 100644 index 0000000..f12f636 Binary files /dev/null and b/feedmee/public/icons/icon-192.png differ diff --git a/feedmee/public/icons/icon-512.png b/feedmee/public/icons/icon-512.png new file mode 100644 index 0000000..4464d59 Binary files /dev/null and b/feedmee/public/icons/icon-512.png differ diff --git a/feedmee/public/icons/logo.png b/feedmee/public/icons/logo.png new file mode 100644 index 0000000..c3d5101 Binary files /dev/null and b/feedmee/public/icons/logo.png differ diff --git a/feedmee/public/manifest.json b/feedmee/public/manifest.json new file mode 100644 index 0000000..776b69e --- /dev/null +++ b/feedmee/public/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "FeedMe", + "short_name": "FeedMe", + "start_url": "/", + "display": "standalone", + "background_color": "#000000", + "theme_color": "#000000", + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/feedmee/src/app/admin/page.tsx b/feedmee/src/app/admin/page.tsx index cc6f17e..3532668 100644 --- a/feedmee/src/app/admin/page.tsx +++ b/feedmee/src/app/admin/page.tsx @@ -29,6 +29,9 @@ type Order = { status: string; }; +type FinalizedSummary = { pickup: string[]; kitchen: string[] }; + + type MenuItem = { id: number; category: string; @@ -49,6 +52,8 @@ export default function AdminPage() { const [editingItem, setEditingItem] = useState(null); const router = useRouter(); const [finalizeTime, setFinalizeTime] = useState("10:30"); + const [lastSummary, setLastSummary] = useState(null); + const auth = (): Record => { const token = localStorage.getItem("token"); return token ? { Authorization: `Bearer ${token}` } : {}; @@ -97,6 +102,10 @@ export default function AdminPage() { .catch(() => setMenuItems([])); }, []); + useEffect(() => { + loadLastSummary(); + }, []); + async function refreshOrders() { try { const res = await fetch(`${API_URL}/api/admin/orders`, { headers: auth() }); @@ -232,11 +241,19 @@ export default function AdminPage() { }); } + async function loadLastSummary() { + const res = await fetch(`${API_URL}/api/mod/finalize/last`, { headers: auth() }); + const data = await res.json(); + if (data.status !== "none") setLastSummary(data); + else setLastSummary(null); + } + async function triggerFinalizeNow() { await fetch(`${API_URL}/api/admin/finalize/now`, { method: "POST", headers: auth(), }); + await loadLastSummary(); } return ( @@ -537,7 +554,36 @@ export default function AdminPage() { + {/* Finalized Order Summary Panel */} +

+ Legutóbbi összesítés +

+
+ {lastSummary ? ( + <> +
+

Átvételi nézet

+
    + {lastSummary.pickup.map((line, idx) => ( +
  • {line}
  • + ))} +
+
+ +
+

Konyha nézet

+
    + {lastSummary.kitchen.map((line, idx) => ( +
  • {line}
  • + ))} +
+
+ + ) : ( +

Nincs elérhető összesítés.

+ )} +
); diff --git a/feedmee/src/app/landing/page.tsx b/feedmee/src/app/landing/page.tsx index 5e59c7d..76e1135 100644 --- a/feedmee/src/app/landing/page.tsx +++ b/feedmee/src/app/landing/page.tsx @@ -161,7 +161,7 @@ export default function LandingPage() { {/* Global orders */}

- Minden rendelés + Aktív rendelések

@@ -176,7 +176,7 @@ export default function LandingPage() { )) ) : ( -

Még senki sem rendelt :(

+

Nincs aktív rendelés

)} diff --git a/feedmee/src/app/layout.tsx b/feedmee/src/app/layout.tsx index 08a68ec..104eb47 100644 --- a/feedmee/src/app/layout.tsx +++ b/feedmee/src/app/layout.tsx @@ -15,6 +15,10 @@ const geistMono = Geist_Mono({ export const metadata = { title: "FeedMe", description: "Food ordering app", + manifest: "/manifest.json", + icons: { + apple: "/icons/icon-192.png", + }, }; export default function RootLayout({ diff --git a/feedmee/src/app/mod/page.tsx b/feedmee/src/app/mod/page.tsx index 22f9ffc..03e48fe 100644 --- a/feedmee/src/app/mod/page.tsx +++ b/feedmee/src/app/mod/page.tsx @@ -15,6 +15,9 @@ type MenuItem = { name: string }; +type FinalizedSummary = { pickup: string[]; kitchen: string[] }; + + export default function ModPage() { const [menuItems, setMenuItems] = useState([]); const [newItem, setNewItem] = useState(""); @@ -22,6 +25,8 @@ export default function ModPage() { const [editingItem, setEditingItem] = useState(null); const [finalizeTime, setFinalizeTime] = useState("10:30"); const router = useRouter(); + const [lastSummary, setLastSummary] = useState(null); + const auth = (): Record => { const token = localStorage.getItem("token"); @@ -61,6 +66,10 @@ export default function ModPage() { .then((data) => setFinalizeTime(data.time)); }, []); + useEffect(() => { + loadLastSummary(); + }, []); + async function refreshMenu() { const res = await fetch(`${API_URL}/api/mod/menu`, { headers: auth() }); const data = await res.json(); @@ -110,8 +119,17 @@ export default function ModPage() { method: "POST", headers: auth(), }); + await loadLastSummary(); } + async function loadLastSummary() { + const res = await fetch(`${API_URL}/api/mod/finalize/last`, { headers: auth() }); + const data = await res.json(); + if (data.status !== "none") setLastSummary(data); + else setLastSummary(null); + } + + return (
@@ -238,6 +256,39 @@ export default function ModPage() { + + {/* Finalized Order Summary Panel */} +

+ Legutóbbi összesítés +

+ +
+ {lastSummary ? ( + <> +
+

Pickup nézet

+
    + {lastSummary.pickup.map((line, idx) => ( +
  • {line}
  • + ))} +
+
+ +
+

Konyha nézet

+
    + {lastSummary.kitchen.map((line, idx) => ( +
  • {line}
  • + ))} +
+
+ + ) : ( +

Nincs elérhető összesítés.

+ )} +
+ +
); }