mirror of
http://88.130.71.182:3000/BlitTech/badoHair_fe.git
synced 2026-06-13 09:20:21 +00:00
175 lines
7.3 KiB
TypeScript
175 lines
7.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Check, X, Trash2, Mail, Phone } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
|
} from "@/components/ui/table";
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import {
|
|
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
|
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
|
} from "@/components/ui/alert-dialog";
|
|
import { useAdmin, Reservation } from "@/contexts/AdminContext";
|
|
import { useLanguage } from "@/contexts/LanguageContext";
|
|
import { ApiError } from "@/lib/api";
|
|
import { toast } from "sonner";
|
|
|
|
export default function AdminReservations() {
|
|
const { reservations, reservationsLoading, updateReservationStatus, deleteReservation } = useAdmin();
|
|
const { t, locale } = useLanguage();
|
|
const [filter, setFilter] = useState<"all" | Reservation["status"]>("all");
|
|
const [deleteId, setDeleteId] = useState<string | null>(null);
|
|
|
|
const statusConfig: Record<Reservation["status"], { label: string; variant: "default" | "secondary" | "destructive" }> = {
|
|
pending: { label: t("admin.status.pending"), variant: "secondary" },
|
|
confirmed: { label: t("admin.status.confirmed"), variant: "default" },
|
|
cancelled: { label: t("admin.status.cancelled"), variant: "destructive" },
|
|
};
|
|
|
|
const filtered = reservations
|
|
.filter((r) => filter === "all" || r.status === filter)
|
|
.sort((a, b) => `${b.date}${b.time}`.localeCompare(`${a.date}${a.time}`));
|
|
|
|
const handleConfirm = async (id: string) => {
|
|
try {
|
|
await updateReservationStatus(id, "confirmed");
|
|
toast.success(t("admin.bookings.confirmed_toast"));
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : t("admin.error"));
|
|
}
|
|
};
|
|
|
|
const handleCancel = async (id: string) => {
|
|
try {
|
|
await updateReservationStatus(id, "cancelled");
|
|
toast.success(t("admin.bookings.cancelled_toast"));
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : t("admin.error"));
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
if (!deleteId) return;
|
|
try {
|
|
await deleteReservation(deleteId);
|
|
toast.success(t("admin.bookings.deleted_toast"));
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : t("admin.error"));
|
|
} finally {
|
|
setDeleteId(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="font-serif text-2xl font-semibold">{t("admin.bookings.title")}</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{reservations.length} {t("admin.bookings.subtitle")}
|
|
</p>
|
|
</div>
|
|
|
|
<Tabs value={filter} onValueChange={(v) => setFilter(v as typeof filter)}>
|
|
<TabsList>
|
|
<TabsTrigger value="all">{t("admin.bookings.tab_all")}</TabsTrigger>
|
|
<TabsTrigger value="pending">{t("admin.bookings.tab_pending")}</TabsTrigger>
|
|
<TabsTrigger value="confirmed">{t("admin.bookings.tab_confirmed")}</TabsTrigger>
|
|
<TabsTrigger value="cancelled">{t("admin.bookings.tab_cancelled")}</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
|
|
<Card>
|
|
{reservationsLoading ? (
|
|
<div className="p-8 space-y-3">
|
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
<div key={i} className="h-12 bg-muted animate-pulse rounded" />
|
|
))}
|
|
</div>
|
|
) : (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{t("admin.bookings.col_client")}</TableHead>
|
|
<TableHead>{t("admin.bookings.col_contact")}</TableHead>
|
|
<TableHead>{t("admin.bookings.col_service")}</TableHead>
|
|
<TableHead>{t("admin.bookings.col_datetime")}</TableHead>
|
|
<TableHead>{t("admin.status")}</TableHead>
|
|
<TableHead className="text-right">{t("admin.actions")}</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{filtered.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={6} className="text-center text-muted-foreground py-8">
|
|
{t("admin.bookings.none")}
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
filtered.map((r) => (
|
|
<TableRow key={r.id}>
|
|
<TableCell className="font-medium">{r.clientName}</TableCell>
|
|
<TableCell>
|
|
<div className="text-xs space-y-1">
|
|
<div className="flex items-center gap-1 text-muted-foreground">
|
|
<Mail className="h-3 w-3" /> {r.email}
|
|
</div>
|
|
<div className="flex items-center gap-1 text-muted-foreground">
|
|
<Phone className="h-3 w-3" /> {r.phone}
|
|
</div>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>{r.service}</TableCell>
|
|
<TableCell>
|
|
<div className="text-sm">
|
|
{new Date(r.date).toLocaleDateString(locale, { day: "2-digit", month: "short", year: "numeric" })}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">{r.time}</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant={statusConfig[r.status].variant}>
|
|
{statusConfig[r.status].label}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-right space-x-1">
|
|
{r.status !== "confirmed" && (
|
|
<Button variant="ghost" size="icon" onClick={() => handleConfirm(r.id)} title={t("admin.status.confirmed")}>
|
|
<Check className="h-4 w-4 text-primary" />
|
|
</Button>
|
|
)}
|
|
{r.status !== "cancelled" && (
|
|
<Button variant="ghost" size="icon" onClick={() => handleCancel(r.id)} title={t("admin.status.cancelled")}>
|
|
<X className="h-4 w-4 text-muted-foreground" />
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" size="icon" onClick={() => setDeleteId(r.id)} title={t("admin.delete")}>
|
|
<Trash2 className="h-4 w-4 text-destructive" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
)}
|
|
</Card>
|
|
|
|
<AlertDialog open={!!deleteId} onOpenChange={(o) => !o && setDeleteId(null)}>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{t("admin.bookings.delete_title")}</AlertDialogTitle>
|
|
<AlertDialogDescription>{t("admin.irreversible")}</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>{t("admin.cancel")}</AlertDialogCancel>
|
|
<AlertDialogAction onClick={handleDelete}>{t("admin.delete")}</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
);
|
|
}
|