Added list of last orders to mod menu
This commit is contained in:
BIN
backend/app.db
BIN
backend/app.db
Binary file not shown.
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
BIN
backend/sqlite3.exe
Normal file
BIN
backend/sqlite3.exe
Normal file
Binary file not shown.
BIN
feedmee/public/icons/icon-192.png
Normal file
BIN
feedmee/public/icons/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
BIN
feedmee/public/icons/icon-512.png
Normal file
BIN
feedmee/public/icons/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
feedmee/public/icons/logo.png
Normal file
BIN
feedmee/public/icons/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
20
feedmee/public/manifest.json
Normal file
20
feedmee/public/manifest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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<MenuItem | null>(null);
|
||||
const router = useRouter();
|
||||
const [finalizeTime, setFinalizeTime] = useState("10:30");
|
||||
const [lastSummary, setLastSummary] = useState<FinalizedSummary | null>(null);
|
||||
|
||||
const auth = (): Record<string, string> => {
|
||||
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() {
|
||||
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function LandingPage() {
|
||||
{/* Global orders */}
|
||||
<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">
|
||||
Minden rendelés
|
||||
Aktív rendelések
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -15,6 +15,9 @@ type MenuItem = {
|
||||
name: string
|
||||
};
|
||||
|
||||
type FinalizedSummary = { pickup: string[]; kitchen: string[] };
|
||||
|
||||
|
||||
export default function ModPage() {
|
||||
const [menuItems, setMenuItems] = useState<MenuItem[]>([]);
|
||||
const [newItem, setNewItem] = useState("");
|
||||
@@ -22,6 +25,8 @@ export default function ModPage() {
|
||||
const [editingItem, setEditingItem] = useState<MenuItem | null>(null);
|
||||
const [finalizeTime, setFinalizeTime] = useState("10:30");
|
||||
const router = useRouter();
|
||||
const [lastSummary, setLastSummary] = useState<FinalizedSummary | null>(null);
|
||||
|
||||
|
||||
const auth = (): Record<string, string> => {
|
||||
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 (
|
||||
<main className="relative flex flex-col min-h-screen items-center p-4 pb-16 space-y-6">
|
||||
<TopBar />
|
||||
@@ -238,6 +256,39 @@ export default function ModPage() {
|
||||
</Button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user