Fix formatting, add types
This commit is contained in:
@@ -1,18 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { TopBar } from "@/components/ui/topbar";
|
import { TopBar } from "@/components/ui/topbar";
|
||||||
import { RefreshCw } from "lucide-react";
|
import { RefreshCw } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const API_URL = "";
|
const API_URL = "";
|
||||||
|
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
@@ -31,11 +28,10 @@ type Order = {
|
|||||||
|
|
||||||
type FinalizedSummary = { pickup: string[]; kitchen: string[] };
|
type FinalizedSummary = { pickup: string[]; kitchen: string[] };
|
||||||
|
|
||||||
|
type MenuItem = {
|
||||||
type MenuItem = {
|
id: number;
|
||||||
id: number;
|
category: string;
|
||||||
category: string;
|
name: string;
|
||||||
name: string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
@@ -55,40 +51,40 @@ export default function AdminPage() {
|
|||||||
const [lastSummary, setLastSummary] = useState<FinalizedSummary | null>(null);
|
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}` } : {};
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${API_URL}/api/admin/finalize/time`, { headers: auth() })
|
fetch(`${API_URL}/api/admin/finalize/time`, { headers: auth() })
|
||||||
.then(r => r.json())
|
.then((r) => r.json())
|
||||||
.then(data => setFinalizeTime(data.time));
|
.then((data) => setFinalizeTime(data.time));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
router.push("/auth");
|
router.push("/auth");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`${API_URL}/api/me`, {
|
fetch(`${API_URL}/api/me`, {
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
if (!data.role || data.role > 1) {
|
if (!data.role || data.role > 1) {
|
||||||
router.push("/landing");
|
router.push("/landing");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => router.push("/auth"));
|
.catch(() => router.push("/auth"));
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${API_URL}/api/admin/users`, { headers: auth() })
|
fetch(`${API_URL}/api/admin/users`, { headers: auth() })
|
||||||
.then(r => r.ok ? r.json() : [])
|
.then((r) => (r.ok ? r.json() : []))
|
||||||
.then(setUsers)
|
.then(setUsers)
|
||||||
.catch(() => setUsers([]));
|
.catch(() => setUsers([]));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -97,9 +93,9 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${API_URL}/api/admin/menu`, { headers: auth() })
|
fetch(`${API_URL}/api/admin/menu`, { headers: auth() })
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data: MenuItem[]) => setMenuItems(data || []))
|
.then((data: MenuItem[]) => setMenuItems(data || []))
|
||||||
.catch(() => setMenuItems([]));
|
.catch(() => setMenuItems([]));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -108,15 +104,17 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
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`, {
|
||||||
if (res.ok) {
|
headers: auth(),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
const data: Order[] = await res.json();
|
const data: Order[] = await res.json();
|
||||||
setOrders(data || []);
|
setOrders(data || []);
|
||||||
} else {
|
} else {
|
||||||
setOrders([]);
|
setOrders([]);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setOrders([]);
|
setOrders([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +126,13 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
async function addMenuItem() {
|
async function addMenuItem() {
|
||||||
await fetch(`${API_URL}/api/admin/menu`, {
|
await fetch(`${API_URL}/api/admin/menu`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", ...auth() },
|
headers: { "Content-Type": "application/json", ...auth() },
|
||||||
body: JSON.stringify({ action: "add", category: newCategory, name: newItem }),
|
body: JSON.stringify({
|
||||||
|
action: "add",
|
||||||
|
category: newCategory,
|
||||||
|
name: newItem,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
setNewItem("");
|
setNewItem("");
|
||||||
await refreshMenu();
|
await refreshMenu();
|
||||||
@@ -139,9 +141,13 @@ export default function AdminPage() {
|
|||||||
async function updateMenuItem() {
|
async function updateMenuItem() {
|
||||||
if (!editingItem) return;
|
if (!editingItem) return;
|
||||||
await fetch(`${API_URL}/api/admin/menu`, {
|
await fetch(`${API_URL}/api/admin/menu`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", ...auth() },
|
headers: { "Content-Type": "application/json", ...auth() },
|
||||||
body: JSON.stringify({ action: "update", id: editingItem.id, name: editingItem.name }),
|
body: JSON.stringify({
|
||||||
|
action: "update",
|
||||||
|
id: editingItem.id,
|
||||||
|
name: editingItem.name,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
setEditingItem(null);
|
setEditingItem(null);
|
||||||
await refreshMenu();
|
await refreshMenu();
|
||||||
@@ -149,47 +155,55 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
async function deleteMenuItem(id: number) {
|
async function deleteMenuItem(id: number) {
|
||||||
await fetch(`${API_URL}/api/admin/menu`, {
|
await fetch(`${API_URL}/api/admin/menu`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", ...auth() },
|
headers: { "Content-Type": "application/json", ...auth() },
|
||||||
body: JSON.stringify({ action: "delete", id }),
|
body: JSON.stringify({ action: "delete", id }),
|
||||||
});
|
});
|
||||||
await refreshMenu();
|
await refreshMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteUser(id: number) {
|
async function deleteUser(id: number) {
|
||||||
if (!confirm("Biztos törlöd a felhasználót?")) return;
|
if (!confirm("Biztos törlöd a felhasználót?")) return;
|
||||||
await fetch(`${API_URL}/api/admin/users/${id}`, { method: "DELETE", headers: auth() });
|
await fetch(`${API_URL}/api/admin/users/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: auth(),
|
||||||
|
});
|
||||||
setUsers((prev) => prev.filter((u) => u.id !== id));
|
setUsers((prev) => prev.filter((u) => u.id !== id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateUser(id: number) {
|
async function updateUser(id: number) {
|
||||||
const body: any = {};
|
const body: {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
role?: number;
|
||||||
|
} = {};
|
||||||
|
|
||||||
if (newUsername) body.username = newUsername;
|
if (newUsername) body.username = newUsername;
|
||||||
if (newPassword) body.password = newPassword;
|
if (newPassword) body.password = newPassword;
|
||||||
if (newRole !== null) body.role = newRole;
|
if (newRole !== null) body.role = newRole;
|
||||||
|
|
||||||
await fetch(`${API_URL}/api/admin/users/${id}`, {
|
await fetch(`${API_URL}/api/admin/users/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json", ...auth() },
|
headers: { "Content-Type": "application/json", ...auth() },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
setUsers((prev) =>
|
setUsers((prev) =>
|
||||||
prev.map((u) =>
|
prev.map((u) =>
|
||||||
u.id === id ? { ...u, username: body.username || u.username } : u
|
u.id === id ? { ...u, username: body.username || u.username } : u
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
setUsers((prev) =>
|
setUsers((prev) =>
|
||||||
prev.map((u) =>
|
prev.map((u) =>
|
||||||
u.id === id
|
u.id === id
|
||||||
? {
|
? {
|
||||||
...u,
|
...u,
|
||||||
username: body.username ?? u.username,
|
username: body.username ?? u.username,
|
||||||
role: body.role ?? u.role,
|
role: body.role ?? u.role,
|
||||||
}
|
}
|
||||||
: u
|
: u
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
setEditing(null);
|
setEditing(null);
|
||||||
@@ -200,17 +214,17 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
function toggleSelect(id: number) {
|
function toggleSelect(id: number) {
|
||||||
setSelectedOrders((prev) =>
|
setSelectedOrders((prev) =>
|
||||||
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateStatusSelected(status: string) {
|
async function updateStatusSelected(status: string) {
|
||||||
for (const id of selectedOrders) {
|
for (const id of selectedOrders) {
|
||||||
await fetch(`${API_URL}/api/admin/orders/${id}/status`, {
|
await fetch(`${API_URL}/api/admin/orders/${id}/status`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json", ...auth() },
|
headers: { "Content-Type": "application/json", ...auth() },
|
||||||
body: JSON.stringify({ status }),
|
body: JSON.stringify({ status }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedOrders([]);
|
setSelectedOrders([]);
|
||||||
@@ -219,14 +233,17 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
selectedOrders.map(async (id) => {
|
selectedOrders.map(async (id) => {
|
||||||
await fetch(`${API_URL}/api/admin/orders/${id}/status`, {
|
await fetch(`${API_URL}/api/admin/orders/${id}/status`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ status: "history" }),
|
body: JSON.stringify({ status: "history" }),
|
||||||
});
|
});
|
||||||
await fetch(`${API_URL}/api/admin/orders/${id}`, { method: "DELETE", headers: auth() });
|
await fetch(`${API_URL}/api/admin/orders/${id}`, {
|
||||||
})
|
method: "DELETE",
|
||||||
|
headers: auth(),
|
||||||
|
});
|
||||||
|
})
|
||||||
);
|
);
|
||||||
const res = await fetch(`${API_URL}/api/admin/orders`);
|
const res = await fetch(`${API_URL}/api/admin/orders`);
|
||||||
setSelectedOrders([]);
|
setSelectedOrders([]);
|
||||||
@@ -235,14 +252,16 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
async function saveFinalizeTime() {
|
async function saveFinalizeTime() {
|
||||||
await fetch(`${API_URL}/api/admin/finalize/time`, {
|
await fetch(`${API_URL}/api/admin/finalize/time`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", ...auth() },
|
headers: { "Content-Type": "application/json", ...auth() },
|
||||||
body: JSON.stringify({ time: finalizeTime }),
|
body: JSON.stringify({ time: finalizeTime }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadLastSummary() {
|
async function loadLastSummary() {
|
||||||
const res = await fetch(`${API_URL}/api/mod/finalize/last`, { headers: auth() });
|
const res = await fetch(`${API_URL}/api/mod/finalize/last`, {
|
||||||
|
headers: auth(),
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.status !== "none") setLastSummary(data);
|
if (data.status !== "none") setLastSummary(data);
|
||||||
else setLastSummary(null);
|
else setLastSummary(null);
|
||||||
@@ -250,341 +269,360 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
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();
|
await loadLastSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 />
|
||||||
<div className="h-10" />
|
<div className="h-10" />
|
||||||
|
|
||||||
<h1 className="text-white text-3xl sm:text-4xl font-bold text-center">
|
<h1 className="text-white text-3xl sm:text-4xl font-bold text-center">
|
||||||
Users
|
Users
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="w-full max-w-3xl">
|
<div className="w-full max-w-3xl">
|
||||||
<div className="glass-panel max-h-[400px] overflow-y-auto space-y-4 p-4">
|
<div className="glass-panel max-h-[400px] overflow-y-auto space-y-4 p-4">
|
||||||
{users.length > 0 ? (
|
{users.length > 0 ? (
|
||||||
users.map((u) => (
|
users.map((u) => (
|
||||||
<Card
|
<Card
|
||||||
key={u.id}
|
key={u.id}
|
||||||
className="glass-panel flex flex-col sm:flex-row sm:items-center sm:justify-between p-4 gap-3"
|
className="glass-panel flex flex-col sm:flex-row sm:items-center sm:justify-between p-4 gap-3"
|
||||||
>
|
>
|
||||||
{editing === u.id ? (
|
{editing === u.id ? (
|
||||||
<div className="flex flex-col sm:flex-row gap-2 w-full items-center justify-between">
|
<div className="flex flex-col sm:flex-row gap-2 w-full items-center justify-between">
|
||||||
{/* Keep current username visible */}
|
{/* Keep current username visible */}
|
||||||
<span className="text-white font-medium">
|
<span className="text-white font-medium">
|
||||||
{u.username} <span className="text-sm text-white/60">(role: {u.role})</span>
|
{u.username}{" "}
|
||||||
</span>
|
<span className="text-sm text-white/60">
|
||||||
|
(role: {u.role})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
{/* Inputs + buttons */}
|
{/* Inputs + buttons */}
|
||||||
<div className="flex flex-col sm:flex-row gap-2">
|
<div className="flex flex-col sm:flex-row gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder="New Username"
|
placeholder="New Username"
|
||||||
value={newUsername}
|
value={newUsername}
|
||||||
onChange={(e) => setNewUsername(e.target.value)}
|
onChange={(e) => setNewUsername(e.target.value)}
|
||||||
className="bg-white/70 text-black sm:w-40 border-white"
|
className="bg-white/70 text-black sm:w-40 border-white"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="New Password"
|
placeholder="New Password"
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
className="bg-white/70 text-black sm:w-40 border-white"
|
className="bg-white/70 text-black sm:w-40 border-white"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Role"
|
placeholder="Role"
|
||||||
value={newRole !== null ? newRole : ""}
|
value={newRole !== null ? newRole : ""}
|
||||||
onChange={(e) => setNewRole(Number(e.target.value))}
|
onChange={(e) => setNewRole(Number(e.target.value))}
|
||||||
className="bg-white/70 text-black sm:w-20 border-white"
|
className="bg-white/70 text-black sm:w-20 border-white"
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => updateUser(u.id)}
|
onClick={() => updateUser(u.id)}
|
||||||
className="bg-black/35 hover:bg-green-700/35 px-3"
|
className="bg-black/35 hover:bg-green-700/35 px-3"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setEditing(null)}
|
onClick={() => setEditing(null)}
|
||||||
className="bg-black/35 hover:bg-blue-700/35 px-3"
|
className="bg-black/35 hover:bg-blue-700/35 px-3"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<>
|
) : (
|
||||||
<span className="text-white font-medium">
|
<>
|
||||||
{u.username} <span className="text-sm text-white/60">(role: {u.role})</span>
|
<span className="text-white font-medium">
|
||||||
</span>
|
{u.username}{" "}
|
||||||
<div className="flex gap-2">
|
<span className="text-sm text-white/60">
|
||||||
<Button
|
(role: {u.role})
|
||||||
size="sm"
|
</span>
|
||||||
onClick={() => setEditing(u.id)}
|
</span>
|
||||||
className="bg-black/35 hover:bg-blue-700/35 px-3"
|
<div className="flex gap-2">
|
||||||
>
|
<Button
|
||||||
Edit
|
size="sm"
|
||||||
</Button>
|
onClick={() => setEditing(u.id)}
|
||||||
<Button
|
className="bg-black/35 hover:bg-blue-700/35 px-3"
|
||||||
size="sm"
|
>
|
||||||
onClick={() => deleteUser(u.id)}
|
Edit
|
||||||
className="bg-red-600 hover:bg-red-700 px-3"
|
</Button>
|
||||||
>
|
<Button
|
||||||
Delete
|
size="sm"
|
||||||
</Button>
|
onClick={() => deleteUser(u.id)}
|
||||||
</div>
|
className="bg-red-600 hover:bg-red-700 px-3"
|
||||||
</>
|
>
|
||||||
)}
|
Delete
|
||||||
|
</Button>
|
||||||
</Card>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-white/70 text-center">No registered users.</p>
|
<p className="text-white/70 text-center">No registered users.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Orders panel */}
|
{/* Orders panel */}
|
||||||
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
||||||
Orders
|
Orders
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="glass-panel w-full max-w-3xl">
|
<div className="glass-panel w-full max-w-3xl">
|
||||||
<div className="max-h-[400px] overflow-y-auto space-y-2 p-4">
|
<div className="max-h-[400px] overflow-y-auto space-y-2 p-4">
|
||||||
{orders.length > 0 ? (
|
{orders.length > 0 ? (
|
||||||
orders.map((o) => (
|
orders.map((o) => (
|
||||||
<Card
|
<Card
|
||||||
key={o.id}
|
key={o.id}
|
||||||
onClick={() => toggleSelect(o.id)}
|
onClick={() => toggleSelect(o.id)}
|
||||||
className={`glass-panel p-3 cursor-pointer ${
|
className={`glass-panel p-3 cursor-pointer ${
|
||||||
selectedOrders.includes(o.id) ? "ring-2 ring-orange-400" : ""
|
selectedOrders.includes(o.id) ? "ring-2 ring-orange-400" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<CardContent className="text-white flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2">
|
<CardContent className="text-white flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
<strong>{o.username}:</strong>{" "}
|
<strong>{o.username}:</strong>{" "}
|
||||||
{o.soup && o.soup.trim() !== "" ? `${o.soup}, ` : ""}
|
{o.soup && o.soup.trim() !== "" ? `${o.soup}, ` : ""}
|
||||||
{o.main}, {o.side}
|
{o.main}, {o.side}
|
||||||
<span
|
<span
|
||||||
className={`ml-2 px-2 py-0.5 rounded text-xs ${
|
className={`ml-2 px-2 py-0.5 rounded text-xs ${
|
||||||
o.status === "active"
|
o.status === "active"
|
||||||
? "bg-green-600/70"
|
? "bg-green-600/70"
|
||||||
: o.status === "history"
|
: o.status === "history"
|
||||||
? "bg-gray-600/70"
|
? "bg-gray-600/70"
|
||||||
: "bg-red-600/70"
|
: "bg-red-600/70"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{o.status}
|
{o.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-white/60">
|
<span className="text-sm text-white/60">
|
||||||
{new Date(o.created_at).toLocaleString()}
|
{new Date(o.created_at).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-white/70 text-center">No orders to display.</p>
|
<p className="text-white/70 text-center">No orders to display.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<div className="flex gap-3 justify-center mt-4 pb-4">
|
<div className="flex gap-3 justify-center mt-4 pb-4">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={refreshOrders}
|
onClick={refreshOrders}
|
||||||
className="text-white hover:bg-blue-700/40 rounded-full p-2"
|
className="text-white hover:bg-blue-700/40 rounded-full p-2"
|
||||||
title="Frissítés"
|
title="Frissítés"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-4 w-4" />
|
<RefreshCw className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-green-600 hover:bg-green-700 px-4"
|
className="bg-green-600 hover:bg-green-700 px-4"
|
||||||
onClick={() => updateStatusSelected("active")}
|
onClick={() => updateStatusSelected("active")}
|
||||||
>
|
>
|
||||||
Set Active
|
Set Active
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-gray-600 hover:bg-gray-700 px-4"
|
className="bg-gray-600 hover:bg-gray-700 px-4"
|
||||||
onClick={() => updateStatusSelected("history")}
|
onClick={() => updateStatusSelected("history")}
|
||||||
>
|
>
|
||||||
Set History
|
Set History
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-red-600 hover:bg-red-700 px-4"
|
className="bg-red-600 hover:bg-red-700 px-4"
|
||||||
onClick={deleteSelected}
|
onClick={deleteSelected}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Menu Management Panel */}
|
|
||||||
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
|
||||||
Menu Editor
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="glass-panel w-full max-w-6xl p-4">
|
{/* Menu Management Panel */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
||||||
{["soup", "main", "side"].map((cat) => (
|
Menu Editor
|
||||||
<div key={cat} className="flex flex-col">
|
</h2>
|
||||||
<h3 className="text-center text-xl font-semibold text-white mb-2">
|
|
||||||
{cat === "soup" ? "Soups" : cat === "main" ? "Main courses" : "Sides"}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="max-h-[300px] overflow-y-auto space-y-2">
|
<div className="glass-panel w-full max-w-6xl p-4">
|
||||||
{menuItems.filter((i) => i.category === cat).map((item) => (
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<Card
|
{["soup", "main", "side"].map((cat) => (
|
||||||
key={item.id}
|
<div key={cat} className="flex flex-col">
|
||||||
className="glass-panel flex flex-col lg:flex-row md:items-center sm:justify-between p-2 gap-2"
|
<h3 className="text-center text-xl font-semibold text-white mb-2">
|
||||||
>
|
{cat === "soup"
|
||||||
{editingItem?.id === item.id ? (
|
? "Soups"
|
||||||
<div className="flex flex-col lg:flex-row gap-2 w-full items-center justify-between">
|
: cat === "main"
|
||||||
<Input
|
? "Main courses"
|
||||||
value={editingItem.name}
|
: "Sides"}
|
||||||
onChange={(e) =>
|
</h3>
|
||||||
setEditingItem({ ...editingItem, name: e.target.value })
|
|
||||||
}
|
|
||||||
className="bg-white/70 text-black sm:w-40 border-white"
|
|
||||||
/>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={updateMenuItem}
|
|
||||||
className="bg-green-600 hover:bg-green-700 px-3"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setEditingItem(null)}
|
|
||||||
className="bg-gray-600 hover:bg-gray-700 px-3"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="text-white font-medium">{item.name}</span>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setEditingItem(item)}
|
|
||||||
className="bg-black/35 hover:bg-blue-700/35 px-3"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={() => deleteMenuItem(item.id)}
|
|
||||||
className="bg-red-600 hover:bg-red-700 px-3"
|
|
||||||
>
|
|
||||||
Del
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add new item for this category */}
|
<div className="max-h-[300px] overflow-y-auto space-y-2">
|
||||||
<div className="flex flex-col sm:flex-row gap-2 mt-3">
|
{menuItems
|
||||||
<Input
|
.filter((i) => i.category === cat)
|
||||||
placeholder={`Új ${cat === "soup" ? "Soup" : cat === "main" ? "Main" : "Side"}`}
|
.map((item) => (
|
||||||
value={newCategory === cat ? newItem : ""}
|
<Card
|
||||||
onChange={(e) => {
|
key={item.id}
|
||||||
setNewItem(e.target.value);
|
className="glass-panel flex flex-col lg:flex-row md:items-center sm:justify-between p-2 gap-2"
|
||||||
setNewCategory(cat);
|
|
||||||
}}
|
|
||||||
className="bg-white/70 text-black sm:w-2/3 border-white"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={addMenuItem}
|
|
||||||
className="bg-green-600 hover:bg-green-700 px-4"
|
|
||||||
>
|
>
|
||||||
Add
|
{editingItem?.id === item.id ? (
|
||||||
</Button>
|
<div className="flex flex-col lg:flex-row gap-2 w-full items-center justify-between">
|
||||||
</div>
|
<Input
|
||||||
</div>
|
value={editingItem.name}
|
||||||
))}
|
onChange={(e) =>
|
||||||
</div>
|
setEditingItem({
|
||||||
</div>
|
...editingItem,
|
||||||
|
name: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="bg-white/70 text-black sm:w-40 border-white"
|
||||||
|
/>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={updateMenuItem}
|
||||||
|
className="bg-green-600 hover:bg-green-700 px-3"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setEditingItem(null)}
|
||||||
|
className="bg-gray-600 hover:bg-gray-700 px-3"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="text-white font-medium">
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setEditingItem(item)}
|
||||||
|
className="bg-black/35 hover:bg-blue-700/35 px-3"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => deleteMenuItem(item.id)}
|
||||||
|
className="bg-red-600 hover:bg-red-700 px-3"
|
||||||
|
>
|
||||||
|
Del
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
{/* Add new item for this category */}
|
||||||
Order Finalization
|
<div className="flex flex-col sm:flex-row gap-2 mt-3">
|
||||||
</h2>
|
<Input
|
||||||
|
placeholder={`Új ${
|
||||||
<div className="glass-panel flex flex-col sm:items-center w-full max-w-3xl p-4 space-y-4">
|
cat === "soup" ? "Soup" : cat === "main" ? "Main" : "Side"
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center gap-3">
|
}`}
|
||||||
<input
|
value={newCategory === cat ? newItem : ""}
|
||||||
type="time"
|
onChange={(e) => {
|
||||||
value={finalizeTime}
|
setNewItem(e.target.value);
|
||||||
onChange={(e) => setFinalizeTime(e.target.value)}
|
setNewCategory(cat);
|
||||||
className="bg-white/70 text-black rounded px-2 py-1 border border-white w-full sm:w-40"
|
}}
|
||||||
|
className="bg-white/70 text-black sm:w-2/3 border-white"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={saveFinalizeTime}
|
size="sm"
|
||||||
className="bg-green-600 hover:bg-green-700 px-4 w-full sm:w-auto"
|
onClick={addMenuItem}
|
||||||
|
className="bg-green-600 hover:bg-green-700 px-4"
|
||||||
>
|
>
|
||||||
Save Time
|
Add
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={triggerFinalizeNow}
|
|
||||||
className="bg-red-600 hover:bg-red-700 px-4 w-full sm:w-auto"
|
|
||||||
>
|
|
||||||
Finalize Orders Now
|
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
||||||
|
Order Finalization
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="glass-panel flex flex-col sm:items-center w-full max-w-3xl p-4 space-y-4">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center gap-3">
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
value={finalizeTime}
|
||||||
|
onChange={(e) => setFinalizeTime(e.target.value)}
|
||||||
|
className="bg-white/70 text-black rounded px-2 py-1 border border-white w-full sm:w-40"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={saveFinalizeTime}
|
||||||
|
className="bg-green-600 hover:bg-green-700 px-4 w-full sm:w-auto"
|
||||||
|
>
|
||||||
|
Save Time
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={triggerFinalizeNow}
|
||||||
|
className="bg-red-600 hover:bg-red-700 px-4 w-full sm:w-auto"
|
||||||
|
>
|
||||||
|
Finalize Orders Now
|
||||||
|
</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">
|
||||||
|
Á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>
|
||||||
|
|
||||||
</div>
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold text-white mb-2">
|
||||||
{/* Finalized Order Summary Panel */}
|
Konyha nézet
|
||||||
<h2 className="text-white text-2xl sm:text-3xl font-semibold text-center mt-8">
|
</h3>
|
||||||
Legutóbbi összesítés
|
<ul className="list-disc list-inside text-white space-y-1">
|
||||||
</h2>
|
{lastSummary.kitchen.map((line, idx) => (
|
||||||
|
<li key={idx}>{line}</li>
|
||||||
<div className="glass-panel w-full max-w-4xl p-4 space-y-4">
|
))}
|
||||||
{lastSummary ? (
|
</ul>
|
||||||
<>
|
</div>
|
||||||
<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">
|
<p className="text-white italic">Nincs elérhető összesítés.</p>
|
||||||
{lastSummary.pickup.map((line, idx) => (
|
)}
|
||||||
<li key={idx}>{line}</li>
|
</div>
|
||||||
))}
|
|
||||||
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user