Files
shokuninmarche/client/src/components/RsvpSummaryCard.tsx
airewit-developer b65f018a8b feat: Guest Management & RSVP system (7ead7758)
Backend:
- POST /api/events/:id/guests — add guest, auto-generate RSVP token (128-bit
  crypto.randomBytes), build wa.me WhatsApp deep-link, store in invitations
- GET /api/events/:id/guests — list with pg_trgm fuzzy Hebrew name search,
  status filter, pagination; returns RSVP summary + capacity warning at 90%
- GET /api/events/:id/guests/export — CSV export with UTF-8 BOM for Excel
  Hebrew support (json2csv)
- PUT /api/guests/:id — PATCH-style update (COALESCE), organizer ownership check
- DELETE /api/guests/:id — hard delete per Israeli Privacy Law 2023
- GET /api/rsvp/:token — public (no auth), marks opened_at on first visit
- POST /api/rsvp/:token — idempotent RSVP submit, supports dietary update,
  handles cancelled events (410)
- Israeli phone normalization: 05X-XXXXXXX → +972XXXXXXXXX E.164
- Capacity warning returned in add/list/update responses when confirmed ≥ 90%
  of venue_capacity (fire safety compliance)

Frontend:
- GuestListPage — sortable/filterable table, inline RSVP status override,
  WhatsApp send links, delete with confirmation, 30s polling for real-time updates
- AddGuestForm — RTL Hebrew form, all guest fields, shows WhatsApp link on success
- RsvpSummaryCard — 4-metric summary (total/confirmed/declined/pending) + capacity warning
- RsvpPage — public page at /rsvp/:token, shows event details, confirm/decline,
  dietary preference update; no login required
- New UI components: Badge, Select (shadcn/ui compatible)
- App.tsx: added /events/:eventId/guests (protected) and /rsvp/:token (public) routes

Build: 0 TS errors, all routes wired in server.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 18:31:08 +00:00

68 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
interface Summary {
total: string | number;
confirmed: string | number;
declined: string | number;
pending: string | number;
}
interface CapacityWarning {
message: string;
percent: number;
confirmed: number;
capacity: number;
}
interface RsvpSummaryCardProps {
summary: Summary;
warning?: CapacityWarning | null;
}
export function RsvpSummaryCard({ summary, warning }: RsvpSummaryCardProps) {
return (
<div className="space-y-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<Card>
<CardHeader className="pb-1 pt-4 px-4">
<CardTitle className="text-sm font-medium text-muted-foreground">סה״כ מוזמנים</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<p className="text-2xl font-bold">{summary.total}</p>
</CardContent>
</Card>
<Card className="border-green-200">
<CardHeader className="pb-1 pt-4 px-4">
<CardTitle className="text-sm font-medium text-green-700">מאושרים</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<p className="text-2xl font-bold text-green-700">{summary.confirmed}</p>
</CardContent>
</Card>
<Card className="border-red-200">
<CardHeader className="pb-1 pt-4 px-4">
<CardTitle className="text-sm font-medium text-red-700">לא מגיעים</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<p className="text-2xl font-bold text-red-700">{summary.declined}</p>
</CardContent>
</Card>
<Card className="border-yellow-200">
<CardHeader className="pb-1 pt-4 px-4">
<CardTitle className="text-sm font-medium text-yellow-700">ממתינים</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<p className="text-2xl font-bold text-yellow-700">{summary.pending}</p>
</CardContent>
</Card>
</div>
{warning && (
<div className="rounded-md border border-orange-300 bg-orange-50 p-3 text-sm text-orange-800" dir="rtl">
{warning.message}
</div>
)}
</div>
);
}