feat:multiple bezoekers
This commit is contained in:
@@ -37,6 +37,9 @@ export const Route = createFileRoute("/admin")({
|
||||
function AdminPage() {
|
||||
const navigate = useNavigate();
|
||||
const [search, setSearch] = useState("");
|
||||
const [registrationType, setRegistrationType] = useState<
|
||||
"performer" | "watcher" | ""
|
||||
>("");
|
||||
const [artForm, setArtForm] = useState("");
|
||||
const [fromDate, setFromDate] = useState("");
|
||||
const [toDate, setToDate] = useState("");
|
||||
@@ -49,6 +52,7 @@ function AdminPage() {
|
||||
orpc.getRegistrations.queryOptions({
|
||||
input: {
|
||||
search: search || undefined,
|
||||
registrationType: registrationType || undefined,
|
||||
artForm: artForm || undefined,
|
||||
fromDate: fromDate || undefined,
|
||||
toDate: toDate || undefined,
|
||||
@@ -120,6 +124,29 @@ function AdminPage() {
|
||||
const adminRequests = adminRequestsQuery.data ?? [];
|
||||
const pendingRequests = adminRequests.filter((r) => r.status === "pending");
|
||||
|
||||
const performerCount =
|
||||
stats?.byType.find((t) => t.registrationType === "performer")?.count ?? 0;
|
||||
const watcherCount =
|
||||
stats?.byType.find((t) => t.registrationType === "watcher")?.count ?? 0;
|
||||
|
||||
// Calculate total attendees including guests
|
||||
const totalRegistrations = registrationsQuery.data?.data ?? [];
|
||||
const watcherWithGuests = totalRegistrations.filter(
|
||||
(r) => r.registrationType === "watcher" && r.guests,
|
||||
);
|
||||
const totalGuestCount = watcherWithGuests.reduce((sum, r) => {
|
||||
try {
|
||||
const guests = JSON.parse(r.guests as string);
|
||||
return sum + (Array.isArray(guests) ? guests.length : 0);
|
||||
} catch {
|
||||
return sum;
|
||||
}
|
||||
}, 0);
|
||||
const totalWatcherAttendees = watcherCount + totalGuestCount;
|
||||
const totalDrinkCardValue = totalRegistrations
|
||||
.filter((r) => r.registrationType === "watcher")
|
||||
.reduce((sum, r) => sum + (r.drinkCardValue ?? 0), 0);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#214e51]">
|
||||
{/* Header */}
|
||||
@@ -207,7 +234,7 @@ function AdminPage() {
|
||||
)}
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="mb-8 grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
<div className="mb-8 grid grid-cols-1 gap-6 md:grid-cols-4">
|
||||
<Card className="border-white/10 bg-white/5">
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription className="text-white/60">
|
||||
@@ -238,23 +265,61 @@ function AdminPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-white/10 bg-white/5">
|
||||
<Card className="border-amber-400/20 bg-amber-400/5">
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription className="text-white/60">
|
||||
Per kunstvorm
|
||||
<CardDescription className="text-amber-300/70">
|
||||
Artiesten
|
||||
</CardDescription>
|
||||
<div className="mt-2 space-y-1">
|
||||
{stats?.byArtForm.slice(0, 5).map((item) => (
|
||||
<CardTitle className="font-['Intro',sans-serif] text-4xl text-amber-300">
|
||||
{performerCount}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-1">
|
||||
{stats?.byArtForm.slice(0, 4).map((item) => (
|
||||
<div
|
||||
key={item.artForm}
|
||||
className="flex items-center justify-between text-sm"
|
||||
className="flex items-center justify-between text-xs"
|
||||
>
|
||||
<span className="text-white/80">{item.artForm}</span>
|
||||
<span className="text-white">{item.count}</span>
|
||||
<span className="text-amber-300/70">
|
||||
{item.artForm || "Onbekend"}
|
||||
</span>
|
||||
<span className="text-amber-300">{item.count}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-teal-400/20 bg-teal-400/5">
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription className="text-teal-300/70">
|
||||
Bezoekers
|
||||
</CardDescription>
|
||||
<CardTitle className="font-['Intro',sans-serif] text-4xl text-teal-300">
|
||||
{watcherCount}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-1 text-xs">
|
||||
{totalGuestCount > 0 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-teal-300/70">Inclusief gasten</span>
|
||||
<span className="text-teal-300">+{totalGuestCount}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-teal-300/70">Totaal aanwezig</span>
|
||||
<span className="font-semibold text-teal-300">
|
||||
{totalWatcherAttendees}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-teal-300/70">Drinkkaart</span>
|
||||
<span className="text-teal-300">€{totalDrinkCardValue}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -266,7 +331,7 @@ function AdminPage() {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-5">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="search"
|
||||
@@ -286,6 +351,35 @@ function AdminPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="typeFilter"
|
||||
className="mb-2 block text-sm text-white/60"
|
||||
>
|
||||
Type
|
||||
</label>
|
||||
<select
|
||||
id="typeFilter"
|
||||
value={registrationType}
|
||||
onChange={(e) =>
|
||||
setRegistrationType(
|
||||
e.target.value as "performer" | "watcher" | "",
|
||||
)
|
||||
}
|
||||
className="w-full rounded-md border border-white/20 bg-white/10 px-3 py-2 text-white focus:outline-none focus:ring-2 focus:ring-white/20"
|
||||
>
|
||||
<option value="" className="bg-[#214e51]">
|
||||
Alle types
|
||||
</option>
|
||||
<option value="performer" className="bg-[#214e51]">
|
||||
Artiesten
|
||||
</option>
|
||||
<option value="watcher" className="bg-[#214e51]">
|
||||
Bezoekers
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="artForm"
|
||||
@@ -369,11 +463,20 @@ function AdminPage() {
|
||||
Telefoon
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left font-medium text-sm text-white/60">
|
||||
Kunstvorm
|
||||
Type
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left font-medium text-sm text-white/60">
|
||||
Kunstvorm / Drinkkaart
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left font-medium text-sm text-white/60">
|
||||
Gezelschap
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left font-medium text-sm text-white/60">
|
||||
Ervaring
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left font-medium text-sm text-white/60">
|
||||
16+
|
||||
</th>
|
||||
<th className="px-6 py-4 text-left font-medium text-sm text-white/60">
|
||||
Datum
|
||||
</th>
|
||||
@@ -383,7 +486,7 @@ function AdminPage() {
|
||||
{registrationsQuery.isLoading ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
colSpan={9}
|
||||
className="px-6 py-8 text-center text-white/60"
|
||||
>
|
||||
Laden...
|
||||
@@ -392,36 +495,83 @@ function AdminPage() {
|
||||
) : registrations.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
colSpan={9}
|
||||
className="px-6 py-8 text-center text-white/60"
|
||||
>
|
||||
Geen registraties gevonden
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
registrations.map((reg) => (
|
||||
<tr
|
||||
key={reg.id}
|
||||
className="border-white/5 border-b hover:bg-white/5"
|
||||
>
|
||||
<td className="px-6 py-4 text-white">
|
||||
{reg.firstName} {reg.lastName}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">{reg.email}</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{reg.phone || "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{reg.artForm}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{reg.experience || "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/60">
|
||||
{new Date(reg.createdAt).toLocaleDateString("nl-BE")}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
registrations.map((reg) => {
|
||||
const isPerformer = reg.registrationType === "performer";
|
||||
return (
|
||||
<tr
|
||||
key={reg.id}
|
||||
className="border-white/5 border-b hover:bg-white/5"
|
||||
>
|
||||
<td className="px-6 py-4 text-white">
|
||||
{reg.firstName} {reg.lastName}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{reg.email}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{reg.phone || "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full px-2.5 py-0.5 font-semibold text-xs ${isPerformer ? "bg-amber-400/15 text-amber-300" : "bg-teal-400/15 text-teal-300"}`}
|
||||
>
|
||||
{isPerformer ? "Artiest" : "Bezoeker"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{isPerformer
|
||||
? reg.artForm || "-"
|
||||
: `€${reg.drinkCardValue ?? 5} drinkkaart`}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{isPerformer
|
||||
? "-"
|
||||
: (() => {
|
||||
if (!reg.guests) return "-";
|
||||
try {
|
||||
const guests = JSON.parse(
|
||||
reg.guests as string,
|
||||
);
|
||||
const count = Array.isArray(guests)
|
||||
? guests.length
|
||||
: 0;
|
||||
return count > 0
|
||||
? `${count} gast${count === 1 ? "" : "en"}`
|
||||
: "-";
|
||||
} catch {
|
||||
return "-";
|
||||
}
|
||||
})()}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{isPerformer ? reg.experience || "-" : "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/80">
|
||||
{isPerformer ? (
|
||||
reg.isOver16 ? (
|
||||
<span className="text-green-400">✓</span>
|
||||
) : (
|
||||
<span className="text-red-400">✗</span>
|
||||
)
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-white/60">
|
||||
{new Date(reg.createdAt).toLocaleDateString(
|
||||
"nl-BE",
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user