From f214660ab256bb7d3ca7ba07a6e4124adafebc15 Mon Sep 17 00:00:00 2001 From: zias Date: Wed, 11 Mar 2026 11:52:26 +0100 Subject: [PATCH] feat: redesign admin page with dense ops-tool aesthetic and expandable rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace generic card layout with near-black background and monospace data display - Add expandable rows for guest details, notes/questions, and performer details - Fix duplicate stat cards (artiesten/bezoekers were shown twice) - Fix gift revenue card showing wrong value (stats.today → totalGiftRevenue) - Fix mobile card view to use accessible - - - ← Terug naar website - - - - +
+
+

Login vereist

+ + + ← Terug naar website + +
); } - // Show request admin UI for non-admin users if (!isAdmin) { return ( -
- - - - Admin Toegang Vereist - - - Je hebt geen admin rechten voor dit dashboard - - - -
-
-

- Admin-toegang aanvragen? -

- -
- - - - - - - ← Terug naar website - -
-
-
-
- ); - } - - return ( -
- {/* Header */} -
-
-
- - ← Terug naar website - -

- Admin Dashboard -

-
-
- - Drinkkaart beheer - +
+
+

+ Geen toegang +

+

+ Je hebt geen admin rechten +

+ +
+
+ + ← Terug naar website + +
+
+ ); + } + + const thCls = + "px-3 py-3 text-left font-mono text-[10px] uppercase tracking-widest text-white/35 cursor-pointer select-none hover:text-white/70 whitespace-nowrap"; + + return ( +
+ {/* ── Top bar ── */} +
+
+
+ + ← site + + | + + Admin + + {pendingRequests.length > 0 && ( + + {pendingRequests.length} aanvraag + {pendingRequests.length !== 1 ? "en" : ""} + + )} +
+
+ + Drinkkaart + + +
- {/* Main Content */} -
- {/* Pending Admin Requests */} +
+ {/* ── Pending admin requests ── */} {pendingRequests.length > 0 && ( - - - - Openstaande Admin Aanvragen ({pendingRequests.length}) - - - Gebruikers die admin toegang hebben aangevraagd - - - -
- {pendingRequests.map((request) => ( -
-
-

- {request.userName} -

-

- {request.userEmail} -

-

- Aangevraagd:{" "} - {new Date(request.requestedAt).toLocaleDateString( - "nl-BE", - )} -

-
-
- - -
+
+

+ Openstaande admin aanvragen ({pendingRequests.length}) +

+
+ {pendingRequests.map((req) => ( +
+
+

{req.userName}

+

{req.userEmail}

- ))} -
- - +
+ + +
+
+ ))} +
+
)} - {/* Stats Cards */} -
- {/* Total registrations */} - - - - Totaal inschrijvingen - - - {stats?.total ?? 0} - - - - - - - - {/* Today */} - - - - Vandaag ingeschreven - - - {stats?.today ?? 0} - - - - - Nieuwe registraties vandaag + {/* ── Stats grid ── */} +
+ + {stats?.today ?? 0} vandaag nieuw - - - - {/* Performers */} - - - - Artiesten - - - {performerCount} - - - -
- {stats?.byArtForm.slice(0, 2).map((item) => ( + } + /> + + {stats?.byArtForm.slice(0, 3).map((item) => (
- + {item.artForm || "Onbekend"} - {item.count} + + {item.count} +
))} -
-
-
- - {/* Watchers + guests (real attendees count) */} - - - - Bezoekers - - - {totalWatcherAttendees} - - - -
-
- Inschrijvingen - {watcherCount} + + } + /> + +
+ Inschrijvingen + + {watcherCount} +
{totalGuestCount > 0 && ( -
- Gasten - +{totalGuestCount} +
+ Gasten + + +{totalGuestCount} +
)} -
- Drinkkaart - €{totalDrinkCardValue} +
+ Drinkkaart + + €{totalDrinkCardValue} +
-
- - - - {/* Gifts */} - - - - Vrijwillige Gifts - - - €{Math.round(totalGiftRevenue / 100)} - - - - - Totale gift opbrengst - - - + + } + /> + Vrijwillige bijdragen + } + /> + Nieuwe registraties} + />
- {/* Filters */} - - - - Filters - - - -
-
- -
- - setSearch(e.target.value)} - className="border-white/20 bg-white/10 pl-10 text-sm text-white placeholder:text-white/40 sm:text-base" - /> -
-
- -
- - -
- -
- + {/* ── Filters + export row ── */} +
+
+ {/* Search */} +
+
+ setArtForm(e.target.value)} - className="border-white/20 bg-white/10 text-sm text-white placeholder:text-white/40 sm:text-base" - /> -
- -
- - setFromDate(e.target.value)} - className="border-white/20 bg-white/10 text-sm text-white [color-scheme:dark] sm:text-base" - /> -
- -
- - setToDate(e.target.value)} - className="border-white/20 bg-white/10 text-sm text-white [color-scheme:dark] sm:text-base" + placeholder="Naam of e-mail zoeken…" + value={search} + onChange={(e) => { + setSearch(e.target.value); + setPage(1); + }} + className="border-white/15 bg-white/8 pl-9 font-mono text-sm text-white placeholder:text-white/25 focus-visible:ring-teal-500/40" />
- - - - {/* Export Button */} -
-

- {pagination?.total ?? 0} registraties gevonden + {/* Type */} +

+ +
+ {/* Art form */} +
+ { + setArtForm(e.target.value); + setPage(1); + }} + className="border-white/15 bg-white/8 font-mono text-sm text-white placeholder:text-white/25 focus-visible:ring-teal-500/40" + /> +
+ {/* Date from */} +
+ { + setFromDate(e.target.value); + setPage(1); + }} + className="border-white/15 bg-white/8 font-mono text-sm text-white [color-scheme:dark] focus-visible:ring-teal-500/40" + /> +
+ {/* Date to + export */} +
+ { + setToDate(e.target.value); + setPage(1); + }} + className="min-w-0 flex-1 border-white/15 bg-white/8 font-mono text-sm text-white [color-scheme:dark] focus-visible:ring-teal-500/40" + /> + +
+
+

+ {pagination?.total ?? 0} registraties {totalGuestCount > 0 && ( - - (+{totalGuestCount} gasten ={" "} - {(pagination?.total ?? 0) + totalGuestCount} aanwezigen totaal) + + · {totalWatcherAttendees + performerCount} aanwezigen totaal + (incl. {totalGuestCount} gasten) )}

-
- {/* Registrations Table / Cards */} - - - {/* Desktop Table */} -
- - - - - - - - + + + ) : ( + sortedRegistrations.map((reg) => { const isPerformer = reg.registrationType === "performer"; const guests = parseGuests(reg.guests); const guestCount = guests.length; - const isExpanded = expandedGuests.has(reg.id); + const isExpanded = expandedRow === reg.id; + const hasNotes = !!reg.extraQuestions?.trim(); + const hasExtra = guestCount > 0 || hasNotes; const detailLabel = isPerformer - ? reg.artForm || "-" + ? reg.artForm || "—" : `€${reg.drinkCardValue ?? 5} drinkkaart`; const dateLabel = (() => { try { return new Date(reg.createdAt).toLocaleDateString( "nl-BE", - { - day: "2-digit", - month: "2-digit", - year: "2-digit", - }, + { day: "2-digit", month: "2-digit", year: "numeric" }, ); } catch { - return "-"; + return "—"; } })(); - return ( -
-
-
-
-
- - {reg.firstName} {reg.lastName} - -
-
- {reg.email} - {reg.phone && ( - - • {reg.phone} - - )} -
-
-
- - {isPerformer ? "Artiest" : "Bezoeker"} - - {reg.managementToken && ( - - )} -
-
+ const giftFmt = formatCents(reg.giftAmount); -
-
- Details:{" "} - {detailLabel} -
- {guestCount > 0 && ( + return ( + <> +
hasExtra && toggleRow(reg.id)} + className={`border-white/5 border-b transition-colors ${hasExtra ? "cursor-pointer" : ""} ${isExpanded ? "bg-white/5" : "hover:bg-white/3"}`} + > + {/* Expand toggle */} + + + + + + + + + + + + + + + {/* Expanded detail row */} + {isExpanded && ( + + + + )} + + ); + }) + )} + +
handleSort("naam")}> - Naam - handleSort("email")}> - Email - - Telefoon - handleSort("type")}> - Type - handleSort("details")} + {/* ── Table ── */} +
+ {/* Desktop table */} +
+ + + + + + + + + + + + + + + + + + {registrationsQuery.isLoading ? ( + + - - - - + laden... + - - - {registrationsQuery.isLoading ? ( - - - - ) : sortedRegistrations.length === 0 ? ( - - - - ) : ( - sortedRegistrations.map((reg) => { - const isPerformer = reg.registrationType === "performer"; - const guests = parseGuests(reg.guests); - const guestCount = guests.length; - const isExpanded = expandedGuests.has(reg.id); - - const detailLabel = isPerformer - ? reg.artForm || "-" - : `€${reg.drinkCardValue ?? 5} drinkkaart`; - - const dateLabel = (() => { - try { - return new Date(reg.createdAt).toLocaleDateString( - "nl-BE", - { - day: "2-digit", - month: "2-digit", - year: "numeric", - }, - ); - } catch { - return "-"; - } - })(); - - return ( - <> - - - - - - - - - - - - - {/* Expandable guest details row */} - {isExpanded && guestCount > 0 && ( - - - - )} - - ); - }) - )} - -
+ handleSort("naam")}> + Naam + handleSort("email")}> + Email + handleSort("type")}> + Type + handleSort("details")}> + Details + handleSort("gasten")}> + Gasten + Notities handleSort("gift")}> + Gift + handleSort("betaling")}> + Betaling + handleSort("datum")}> + Datum + Link
- Details - - handleSort("gasten")} - > - Gasten - handleSort("gift")}> - Gift - handleSort("betaling")} - > - Betaling - handleSort("datum")}> - Datum - - Link -
- Laden... -
- Geen registraties gevonden -
- {reg.firstName} {reg.lastName} - - {reg.email} - - {reg.phone || "-"} - - - {isPerformer ? "Artiest" : "Bezoeker"} - - - {detailLabel} - - {guestCount > 0 ? ( - - ) : ( - "-" - )} - - {formatCents(reg.giftAmount)} - - {isPerformer ? ( - - - ) : reg.paymentStatus === "paid" ? ( - - - Betaald - - ) : reg.paymentStatus === - "extra_payment_pending" ? ( - - - Extra (€ - {((reg.paymentAmount ?? 0) / 100).toFixed(0)}) - - ) : ( - - Open - - )} - - {dateLabel} - - {reg.managementToken ? ( - - ) : ( - - )} -
-
-

- Gasten van {reg.firstName} {reg.lastName} -

- {guests.map((g, i) => ( -
- - {i + 1}. - - - {g.firstName} {g.lastName} - - {g.email && ( - - {g.email} - - )} - {g.phone && ( - - {g.phone} - - )} - {!g.email && !g.phone && ( - - geen contactgegevens - - )} -
- ))} -
-
-
- - {/* Mobile Cards */} -
- {registrationsQuery.isLoading ? ( -
- Laden... -
- ) : sortedRegistrations.length === 0 ? ( -
- Geen registraties gevonden -
- ) : ( -
- {sortedRegistrations.map((reg) => { + ) : sortedRegistrations.length === 0 ? ( +
+ Geen registraties gevonden +
+ {hasExtra ? ( + isExpanded ? ( + + ) : ( + + ) + ) : null} + + {reg.firstName} {reg.lastName} + + {reg.email} + + {reg.phone || "—"} + + + {isPerformer ? "Artiest" : "Bezoeker"} + + + {detailLabel} + + {guestCount > 0 ? ( + + {guestCount}× + + ) : ( + + )} + + {hasNotes ? ( + + ) : ( + + )} + + {giftFmt ?? ( + + )} + + + + {dateLabel} + e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + > + {reg.managementToken ? ( + ) : ( + + )} +
+
+ {/* Guests */} + {guestCount > 0 && ( +
+

+ Gasten ({guestCount}) +

+
+ {guests.map((g, i) => ( +
+ + {i + 1}. + + + {g.firstName} {g.lastName} + + {g.email && ( + + {g.email} + + )} + {g.phone && ( + + {g.phone} + + )} + {!g.email && !g.phone && ( + + geen contactgegevens + + )} +
+ ))} +
+
+ )} + + {/* Notes */} + {hasNotes && ( +
+

+ Vragen / opmerkingen +

+
+ {reg.extraQuestions} +
+
+ )} + + {/* Performer extras */} + {isPerformer && + (reg.experience || reg.artForm) && ( +
+

+ Optreden details +

+
+ {reg.artForm && ( +

+ + Kunstvorm:{" "} + + {reg.artForm} +

+ )} + {reg.experience && ( +

+ + Ervaring:{" "} + + {reg.experience} +

+ )} +

+ + 16+:{" "} + + {reg.isOver16 ? "ja" : "nee"} +

+
+
+ )} +
+
+
+ + {/* Mobile cards */} +
+ {registrationsQuery.isLoading ? ( +
+ laden... +
+ ) : sortedRegistrations.length === 0 ? ( +
+ Geen registraties +
+ ) : ( + sortedRegistrations.map((reg) => { + const isPerformer = reg.registrationType === "performer"; + const guests = parseGuests(reg.guests); + const guestCount = guests.length; + const isExpanded = expandedRow === reg.id; + const hasNotes = !!reg.extraQuestions?.trim(); + const hasExtra = guestCount > 0 || hasNotes; + const giftFmt = formatCents(reg.giftAmount); + + return ( +
+ {hasExtra ? ( + )} - {(reg.giftAmount ?? 0) > 0 && ( -
- Gift:{" "} - {formatCents(reg.giftAmount)} -
- )} - {!isPerformer && ( -
- Betaling:{" "} - {reg.paymentStatus === "paid" ? ( - - - Betaald - - ) : reg.paymentStatus === - "extra_payment_pending" ? ( - - - Extra (€ - {((reg.paymentAmount ?? 0) / 100).toFixed( - 0, - )} - ) - - ) : ( - Open - )} -
- )} -
{dateLabel}
+
+ {isPerformer ? ( + {reg.artForm || "—"} + ) : ( + €{reg.drinkCardValue ?? 5} drinkkaart + )} + {guestCount > 0 && ( + + {guestCount} gast{guestCount !== 1 ? "en" : ""} + + )} + {giftFmt && ( + + gift {giftFmt} + + )} + {hasNotes && ( + + + notitie + + )} + + {(() => { + try { + return new Date( + reg.createdAt, + ).toLocaleDateString("nl-BE", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + }); + } catch { + return "—"; + } + })()} + +
+ + ) : ( +
+
+
+ + {reg.firstName} {reg.lastName} + +

+ {reg.email} +

+ {reg.phone && ( +

+ {reg.phone} +

+ )} +
+
+ + {isPerformer ? "Artiest" : "Bezoeker"} + + + {reg.managementToken && ( + + )} +
+
+
+ {isPerformer ? ( + {reg.artForm || "—"} + ) : ( + €{reg.drinkCardValue ?? 5} drinkkaart + )} + {giftFmt && ( + + gift {giftFmt} + + )} + + {(() => { + try { + return new Date( + reg.createdAt, + ).toLocaleDateString("nl-BE", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + }); + } catch { + return "—"; + } + })()} + +
+
+ )} - {/* Expandable guest details — mobile */} - {isExpanded && guestCount > 0 && ( -
-

+ {/* Mobile expanded */} + {isExpanded && ( +

+ {guestCount > 0 && ( +
+

Gasten

{guests.map((g, i) => (
- - {i + 1}. - - - {g.firstName} {g.lastName} - +

+ {i + 1}. {g.firstName} {g.lastName} +

{g.email && ( -
+

{g.email} -

+

)} {g.phone && ( -
+

{g.phone} -

+

)}
))}
)} + {hasNotes && ( +
+

+ Vragen / opmerkingen +

+

+ {reg.extraQuestions} +

+
+ )} + {isPerformer && (reg.experience || reg.artForm) && ( +
+

+ Optreden details +

+
+ {reg.artForm && ( +

+ + Kunstvorm:{" "} + + {reg.artForm} +

+ )} + {reg.experience && ( +

+ + Ervaring:{" "} + + {reg.experience} +

+ )} +

+ 16+: + {reg.isOver16 ? "ja" : "nee"} +

+
+
+ )}
- ); - })} -
- )} -
- - - - {/* Pagination */} + )} +
+ ); + }) + )} +
+
{pagination && pagination.totalPages > 1 && ( -
- - - - {page}/{pagination.totalPages} - - - Pagina {page} van {pagination.totalPages} - + ← vorige + + + {page} / {pagination.totalPages} - + volgende → +
)}