reservation-front/src/pages/EventReservations.tsx
2026-06-21 11:17:05 +03:30

211 lines
6.5 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 { useMemo, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { CafeEvent, mockCafeEvents } from "../data/mockCafeEvents";
import { mockReservations, Reservation } from "../data/mockReservations";
import { formatPersianNumber } from "../utils/formatNumber";
function ChevronIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15.0898 4.07992L8.56984 10.5999C7.79984 11.3699 7.79984 12.6299 8.56984 13.3999L15.0898 19.9199" />
</svg>
);
}
function SearchIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.3835}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z" />
<path d="M22 22L20 20" />
</svg>
);
}
function CheckIcon() {
return (
<svg
className="h-3 w-3 text-white"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2.5 6.5L4.5 8.5L9.5 3.5" />
</svg>
);
}
function toEnglishDigits(value: string) {
const persianDigits = "۰۱۲۳۴۵۶۷۸۹";
return value.replace(/[۰-۹]/g, (digit) =>
String(persianDigits.indexOf(digit)),
);
}
function reservationMatches(reservation: Reservation, query: string) {
if (!query) return true;
const haystack = toEnglishDigits(
`${reservation.name} ${reservation.phone} ${reservation.count} ${formatPersianNumber(reservation.count)} ${reservation.code}`,
).toLowerCase();
return haystack.includes(toEnglishDigits(query).toLowerCase());
}
function downloadReservationsCsv(reservations: Reservation[]) {
const header = ["کد رزرو", "تعداد", "نام", "شماره تماس"];
const rows = reservations.map((r) => [
r.code,
String(r.count),
r.name,
toEnglishDigits(r.phone),
]);
const csvContent = [header, ...rows]
.map((row) => row.map((cell) => `"${cell.replace(/"/g, '""')}"`).join(","))
.join("\n");
const blob = new Blob(["" + csvContent], {
type: "text/csv;charset=utf-8;",
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "reservations.csv";
link.click();
URL.revokeObjectURL(url);
}
function ReservationCard({
reservation,
selected,
onToggle,
}: {
reservation: Reservation;
selected: boolean;
onToggle: () => void;
}) {
return (
<div className="flex items-center justify-between rounded-2xl shadow-[0px_4px_12px_0px_#0000001F] p-6">
<div className="flex flex-col items-start gap-2 text-right">
<div className="flex items-center gap-2">
<span className="font-medium rounded-full bg-[#ECF2F8] px-3 py-1 text-sm text-gray-700 h-9 flex items-center">
{reservation.code}
</span>
<span className="font-medium rounded-full bg-[#ECF2F8] px-3 py-1 text-sm text-gray-700 h-9 flex items-center">
{formatPersianNumber(reservation.count)} نفر
</span>
</div>
<p className="text-gray-900">{reservation.name}</p>
<p className="text-gray-500">{reservation.phone}</p>
</div>
<button
type="button"
onClick={onToggle}
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 border-[#223D5D] ${selected ? "bg-[#223D5D]" : "bg-transparent"}`}
>
{selected && <CheckIcon />}
</button>
</div>
);
}
export default function EventReservations() {
const { id } = useParams();
const location = useLocation();
const navigate = useNavigate();
const [query, setQuery] = useState("");
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const event: CafeEvent | undefined =
(location.state as { event?: CafeEvent } | null)?.event ??
mockCafeEvents.find((e) => e.id === Number(id));
const filteredReservations = useMemo(
() =>
mockReservations.filter((reservation) =>
reservationMatches(reservation, query),
),
[query],
);
const toggleSelected = (reservationId: number) => {
setSelectedIds((prev) =>
prev.includes(reservationId)
? prev.filter((id) => id !== reservationId)
: [...prev, reservationId],
);
};
const handleDownload = () => {
const selectedReservations =
selectedIds.length > 0
? mockReservations.filter((r) => selectedIds.includes(r.id))
: mockReservations;
downloadReservationsCsv(selectedReservations);
};
return (
<div className="min-h-screen bg-white pb-24">
<div className="h-16 w-full bg-gray-200" />
<button
type="button"
onClick={() => navigate(-1)}
className="mt-4 flex w-full items-center justify-start gap-2 px-4"
>
<ChevronIcon className="h-5 w-5 -scale-x-100 text-gray-900" />
<span className="text-md font-semibold text-gray-900">
رزروهای {event?.matchLabel ?? ""}
</span>
</button>
<div className="relative mt-4 px-4">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="جستجو"
className="w-full rounded-2xl shadow-[0px_4px_12px_0px_#0000001F] bg-white py-3 pr-6 pl-4 text-right placeholder:font-light placeholder:text-[#999999]"
/>
<SearchIcon className="pointer-events-none absolute left-8 top-1/2 h-5 w-5 -translate-y-1/2 text-[#666666]" />
</div>
<div className="mt-4 flex flex-col gap-3 px-4">
{filteredReservations.map((reservation) => (
<ReservationCard
key={reservation.id}
reservation={reservation}
selected={selectedIds.includes(reservation.id)}
onToggle={() => toggleSelected(reservation.id)}
/>
))}
</div>
<div className="fixed inset-x-0 bottom-0 bg-white px-4 py-4">
<button
type="button"
onClick={handleDownload}
className="w-full rounded-2xl bg-[#223D5D] py-4 text-lg font-semibold text-white"
>
دانلود لیست
</button>
</div>
</div>
);
}