Added list of last orders to mod menu

This commit is contained in:
2025-10-06 15:03:22 +02:00
parent d38c5ae91b
commit 0e18760e5d
11 changed files with 185 additions and 9 deletions

Binary file not shown.

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
@@ -43,10 +44,16 @@ type Order struct {
Status string `json:"status"` Status string `json:"status"`
} }
type FinalizedSummary struct {
Pickup []string `json:"pickup"`
Kitchen []string `json:"kitchen"`
}
type App struct { type App struct {
DB *sql.DB DB *sql.DB
Broker *Broker Broker *Broker
FinalizeUpdate chan struct{} // notify scheduler to reload time FinalizeUpdate chan struct{} // notify scheduler to reload time
LastSummary *FinalizedSummary
} }
type Broker struct { type Broker struct {
@@ -875,7 +882,7 @@ func finalizeOrders(app *App) error {
} }
// Step 2: send summary (log, email, or message) // Step 2: send summary (log, email, or message)
sendSummary(summary) sendSummary(app, summary)
// Step 3: archive // Step 3: archive
_, err = app.DB.Exec("UPDATE orders SET status = 'history' WHERE status = 'active'") _, 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 { if data, err := json.Marshal(msg); err == nil {
app.Broker.broadcast <- data app.Broker.broadcast <- data
} }
time.Sleep(20 * time.Millisecond)
} }
return err return err
} }
func sendSummary(orders []Order) { func sendSummary(app *App, orders []Order) *FinalizedSummary {
// TODO: replace with email or message // Pickup view
log.Println("Daily summary:") userMap := make(map[string][]string)
for _, o := range orders { 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 { func server(app *App) *http.Server {
@@ -942,6 +996,7 @@ func server(app *App) *http.Server {
r.Get("/finalize/time", app.handleGetFinalizeTime) r.Get("/finalize/time", app.handleGetFinalizeTime)
r.Post("/finalize/time", app.handleSetFinalizeTime) r.Post("/finalize/time", app.handleSetFinalizeTime)
r.Post("/finalize/now", app.handleFinalizeNow) r.Post("/finalize/now", app.handleFinalizeNow)
r.Get("/finalize/last", app.handleGetLastSummary)
}) })
// Moderators (role <= 50) // Moderators (role <= 50)
@@ -952,6 +1007,7 @@ func server(app *App) *http.Server {
r.Get("/finalize/time", app.handleGetFinalizeTime) r.Get("/finalize/time", app.handleGetFinalizeTime)
r.Post("/finalize/time", app.handleSetFinalizeTime) r.Post("/finalize/time", app.handleSetFinalizeTime)
r.Post("/finalize/now", app.handleFinalizeNow) r.Post("/finalize/now", app.handleFinalizeNow)
r.Get("/finalize/last", app.handleGetLastSummary)
}) })
// Return configured server // Return configured server
@@ -973,12 +1029,11 @@ func main() {
// Create server // Create server
srv := server(app) srv := server(app)
go startDailyCleanup(app)
log.Println("Server listening on", srv.Addr) log.Println("Server listening on", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err) log.Fatal(err)
} }
go startDailyCleanup(app)
} }

BIN
backend/sqlite3.exe Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -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"
}
]
}

View File

@@ -29,6 +29,9 @@ type Order = {
status: string; status: string;
}; };
type FinalizedSummary = { pickup: string[]; kitchen: string[] };
type MenuItem = { type MenuItem = {
id: number; id: number;
category: string; category: string;
@@ -49,6 +52,8 @@ export default function AdminPage() {
const [editingItem, setEditingItem] = useState<MenuItem | null>(null); const [editingItem, setEditingItem] = useState<MenuItem | null>(null);
const router = useRouter(); const router = useRouter();
const [finalizeTime, setFinalizeTime] = useState("10:30"); const [finalizeTime, setFinalizeTime] = useState("10:30");
const [lastSummary, setLastSummary] = useState<FinalizedSummary | null>(null);
const auth = (): Record<string, string> => { const auth = (): Record<string, string> => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
return token ? { Authorization: `Bearer ${token}` } : {}; return token ? { Authorization: `Bearer ${token}` } : {};
@@ -97,6 +102,10 @@ export default function AdminPage() {
.catch(() => setMenuItems([])); .catch(() => setMenuItems([]));
}, []); }, []);
useEffect(() => {
loadLastSummary();
}, []);
async function refreshOrders() { async function refreshOrders() {
try { try {
const res = await fetch(`${API_URL}/api/admin/orders`, { headers: auth() }); 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() { async function triggerFinalizeNow() {
await fetch(`${API_URL}/api/admin/finalize/now`, { await fetch(`${API_URL}/api/admin/finalize/now`, {
method: "POST", method: "POST",
headers: auth(), headers: auth(),
}); });
await loadLastSummary();
} }
return ( return (
@@ -537,7 +554,36 @@ export default function AdminPage() {
</div> </div>
{/* Finalized Order Summary Panel */}
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
Legutóbbi összesítés
</h2>
<div className="glass-panel w-full max-w-4xl p-4 space-y-4">
{lastSummary ? (
<>
<div>
<h3 className="text-xl font-semibold text-white mb-2">Átvételi nézet</h3>
<ul className="list-disc list-inside text-white space-y-1">
{lastSummary.pickup.map((line, idx) => (
<li key={idx}>{line}</li>
))}
</ul>
</div>
<div>
<h3 className="text-xl font-semibold text-white mb-2">Konyha nézet</h3>
<ul className="list-disc list-inside text-white space-y-1">
{lastSummary.kitchen.map((line, idx) => (
<li key={idx}>{line}</li>
))}
</ul>
</div>
</>
) : (
<p className="text-white italic">Nincs elérhető összesítés.</p>
)}
</div>
</main> </main>
); );

View File

@@ -161,7 +161,7 @@ export default function LandingPage() {
{/* Global orders */} {/* Global orders */}
<hr className="w-full border-t border-white/30 my-6" /> <hr className="w-full border-t border-white/30 my-6" />
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-4"> <h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-4">
Minden rendelés Aktív rendelések
</h2> </h2>
<Card className="glass-panel w-full max-w-3xl pt-2 pb-2"> <Card className="glass-panel w-full max-w-3xl pt-2 pb-2">
@@ -176,7 +176,7 @@ export default function LandingPage() {
)) ))
) : ( ) : (
<p className="text-center text-white/70">Még senki sem rendelt :(</p> <p className="text-center text-white/70">Nincs aktív rendelés</p>
)} )}
</CardContent> </CardContent>

View File

@@ -15,6 +15,10 @@ const geistMono = Geist_Mono({
export const metadata = { export const metadata = {
title: "FeedMe", title: "FeedMe",
description: "Food ordering app", description: "Food ordering app",
manifest: "/manifest.json",
icons: {
apple: "/icons/icon-192.png",
},
}; };
export default function RootLayout({ export default function RootLayout({

View File

@@ -15,6 +15,9 @@ type MenuItem = {
name: string name: string
}; };
type FinalizedSummary = { pickup: string[]; kitchen: string[] };
export default function ModPage() { export default function ModPage() {
const [menuItems, setMenuItems] = useState<MenuItem[]>([]); const [menuItems, setMenuItems] = useState<MenuItem[]>([]);
const [newItem, setNewItem] = useState(""); const [newItem, setNewItem] = useState("");
@@ -22,6 +25,8 @@ export default function ModPage() {
const [editingItem, setEditingItem] = useState<MenuItem | null>(null); const [editingItem, setEditingItem] = useState<MenuItem | null>(null);
const [finalizeTime, setFinalizeTime] = useState("10:30"); const [finalizeTime, setFinalizeTime] = useState("10:30");
const router = useRouter(); const router = useRouter();
const [lastSummary, setLastSummary] = useState<FinalizedSummary | null>(null);
const auth = (): Record<string, string> => { const auth = (): Record<string, string> => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
@@ -61,6 +66,10 @@ export default function ModPage() {
.then((data) => setFinalizeTime(data.time)); .then((data) => setFinalizeTime(data.time));
}, []); }, []);
useEffect(() => {
loadLastSummary();
}, []);
async function refreshMenu() { async function refreshMenu() {
const res = await fetch(`${API_URL}/api/mod/menu`, { headers: auth() }); const res = await fetch(`${API_URL}/api/mod/menu`, { headers: auth() });
const data = await res.json(); const data = await res.json();
@@ -110,8 +119,17 @@ export default function ModPage() {
method: "POST", method: "POST",
headers: auth(), 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 ( return (
<main className="relative flex flex-col min-h-screen items-center p-4 pb-16 space-y-6"> <main className="relative flex flex-col min-h-screen items-center p-4 pb-16 space-y-6">
<TopBar /> <TopBar />
@@ -238,6 +256,39 @@ export default function ModPage() {
</Button> </Button>
</div> </div>
</div> </div>
{/* Finalized Order Summary Panel */}
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
Legutóbbi összesítés
</h2>
<div className="glass-panel w-full max-w-4xl p-4 space-y-4">
{lastSummary ? (
<>
<div>
<h3 className="text-xl font-semibold text-white mb-2">Pickup nézet</h3>
<ul className="list-disc list-inside text-white space-y-1">
{lastSummary.pickup.map((line, idx) => (
<li key={idx}>{line}</li>
))}
</ul>
</div>
<div>
<h3 className="text-xl font-semibold text-white mb-2">Konyha nézet</h3>
<ul className="list-disc list-inside text-white space-y-1">
{lastSummary.kitchen.map((line, idx) => (
<li key={idx}>{line}</li>
))}
</ul>
</div>
</>
) : (
<p className="text-white italic">Nincs elérhető összesítés.</p>
)}
</div>
</main> </main>
); );
} }