feat: translate bookings page and components to English
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,9 +29,9 @@ export default function BookingsPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-primary-800">Reservas</h1>
|
<h1 className="text-2xl font-bold text-primary-800">Bookings</h1>
|
||||||
<p className="mt-2 text-primary-600">
|
<p className="mt-2 text-primary-600">
|
||||||
Gestiona las reservas de canchas. Selecciona un horario para crear o ver una reserva.
|
Manage court bookings. Select a time slot to create or view a booking.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
const url = siteId ? `/api/courts?siteId=${siteId}` : "/api/courts";
|
const url = siteId ? `/api/courts?siteId=${siteId}` : "/api/courts";
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Error al cargar las canchas");
|
throw new Error("Error loading courts");
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setCourts(data);
|
setCourts(data);
|
||||||
return data as Court[];
|
return data as Court[];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Error desconocido");
|
setError(err instanceof Error ? err.message : "Unknown error");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, [siteId]);
|
}, [siteId]);
|
||||||
@@ -113,7 +113,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
`/api/courts/${courtId}/availability?date=${dateStr}`
|
`/api/courts/${courtId}/availability?date=${dateStr}`
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Error al cargar disponibilidad`);
|
throw new Error(`Error loading availability`);
|
||||||
}
|
}
|
||||||
return (await response.json()) as CourtAvailability;
|
return (await response.json()) as CourtAvailability;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -224,7 +224,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
fetchCourts();
|
fetchCourts();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reintentar
|
Retry
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -238,7 +238,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-lg">Calendario</CardTitle>
|
<CardTitle className="text-lg">Calendar</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="outline" size="sm" onClick={goToPrevDay}>
|
<Button variant="outline" size="sm" onClick={goToPrevDay}>
|
||||||
<svg
|
<svg
|
||||||
@@ -260,7 +260,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={goToToday}
|
onClick={goToToday}
|
||||||
>
|
>
|
||||||
Hoy
|
Today
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" size="sm" onClick={goToNextDay}>
|
<Button variant="outline" size="sm" onClick={goToNextDay}>
|
||||||
<svg
|
<svg
|
||||||
@@ -286,12 +286,12 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
<div className="flex items-center justify-center p-12">
|
<div className="flex items-center justify-center p-12">
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary-200 border-t-primary-600" />
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary-200 border-t-primary-600" />
|
||||||
<p className="text-sm text-primary-500">Cargando disponibilidad...</p>
|
<p className="text-sm text-primary-500">Loading availability...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : courts.length === 0 ? (
|
) : courts.length === 0 ? (
|
||||||
<div className="p-6 text-center text-primary-500">
|
<div className="p-6 text-center text-primary-500">
|
||||||
<p>No hay canchas disponibles.</p>
|
<p>No courts available.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
@@ -316,7 +316,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
{court.name}
|
{court.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-primary-500 mt-1">
|
<p className="text-xs text-primary-500 mt-1">
|
||||||
{court.type === "INDOOR" ? "Interior" : "Exterior"}
|
{court.type === "INDOOR" ? "Indoor" : "Outdoor"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -347,7 +347,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
className="border-r border-primary-200 last:border-r-0 p-2"
|
className="border-r border-primary-200 last:border-r-0 p-2"
|
||||||
>
|
>
|
||||||
<div className="rounded-md border border-primary-200 bg-primary-50 p-3 text-center text-xs text-primary-400">
|
<div className="rounded-md border border-primary-200 bg-primary-50 p-3 text-center text-xs text-primary-400">
|
||||||
No disponible
|
Not available
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -373,7 +373,7 @@ export function BookingCalendar({ siteId, onSlotClick }: BookingCalendarProps) {
|
|||||||
|
|
||||||
{timeSlots.length === 0 && (
|
{timeSlots.length === 0 && (
|
||||||
<div className="p-6 text-center text-primary-500">
|
<div className="p-6 text-center text-primary-500">
|
||||||
<p>No hay horarios disponibles para este día.</p>
|
<p>No time slots available for this day.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ export function BookingDialog({
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/bookings/${slot.bookingId}`);
|
const response = await fetch(`/api/bookings/${slot.bookingId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Error al cargar la reserva");
|
throw new Error("Error loading booking");
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setBooking(data);
|
setBooking(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Error desconocido");
|
setError(err instanceof Error ? err.message : "Unknown error");
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingBookingInfo(false);
|
setLoadingBookingInfo(false);
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ export function BookingDialog({
|
|||||||
`/api/clients?search=${encodeURIComponent(search)}&limit=10`
|
`/api/clients?search=${encodeURIComponent(search)}&limit=10`
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Error al buscar clientes");
|
throw new Error("Error searching players");
|
||||||
}
|
}
|
||||||
const data: ClientsResponse = await response.json();
|
const data: ClientsResponse = await response.json();
|
||||||
setClients(data.data);
|
setClients(data.data);
|
||||||
@@ -184,13 +184,13 @@ export function BookingDialog({
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
throw new Error(data.error || "Error al crear la reserva");
|
throw new Error(data.error || "Error creating booking");
|
||||||
}
|
}
|
||||||
|
|
||||||
onBookingCreated?.();
|
onBookingCreated?.();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Error al crear la reserva");
|
setError(err instanceof Error ? err.message : "Error creating booking");
|
||||||
} finally {
|
} finally {
|
||||||
setCreatingBooking(false);
|
setCreatingBooking(false);
|
||||||
}
|
}
|
||||||
@@ -210,20 +210,20 @@ export function BookingDialog({
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
cancelReason: "Cancelada por el administrador",
|
cancelReason: "Cancelled by administrator",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
throw new Error(data.error || "Error al cancelar la reserva");
|
throw new Error(data.error || "Error cancelling booking");
|
||||||
}
|
}
|
||||||
|
|
||||||
onBookingCancelled?.();
|
onBookingCancelled?.();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
err instanceof Error ? err.message : "Error al cancelar la reserva"
|
err instanceof Error ? err.message : "Error cancelling booking"
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setCancellingBooking(false);
|
setCancellingBooking(false);
|
||||||
@@ -246,7 +246,7 @@ export function BookingDialog({
|
|||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-lg">
|
<CardTitle className="text-lg">
|
||||||
{slot.available ? "Nueva Reserva" : "Detalle de Reserva"}
|
{slot.available ? "New Booking" : "Booking Details"}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -269,16 +269,16 @@ export function BookingDialog({
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-primary-600 space-y-1 mt-2">
|
<div className="text-sm text-primary-600 space-y-1 mt-2">
|
||||||
<p>
|
<p>
|
||||||
<span className="font-medium">Cancha:</span> {slot.courtName}
|
<span className="font-medium">Court:</span> {slot.courtName}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="font-medium">Fecha:</span> {formatDate(date)}
|
<span className="font-medium">Date:</span> {formatDate(date)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="font-medium">Hora:</span> {formatTime(slotDate)}
|
<span className="font-medium">Time:</span> {formatTime(slotDate)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="font-medium">Precio:</span>{" "}
|
<span className="font-medium">Price:</span>{" "}
|
||||||
{formatCurrency(slot.price)}
|
{formatCurrency(slot.price)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -296,11 +296,11 @@ export function BookingDialog({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-primary-700 mb-2">
|
<label className="block text-sm font-medium text-primary-700 mb-2">
|
||||||
Buscar Cliente
|
Search Player
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Nombre, email o telefono..."
|
placeholder="Name, email or phone..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -317,7 +317,7 @@ export function BookingDialog({
|
|||||||
|
|
||||||
{!loadingClients && searchQuery.length >= 2 && clients.length === 0 && (
|
{!loadingClients && searchQuery.length >= 2 && clients.length === 0 && (
|
||||||
<p className="text-sm text-primary-500 text-center py-4">
|
<p className="text-sm text-primary-500 text-center py-4">
|
||||||
No se encontraron clientes.
|
No players found.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -341,7 +341,7 @@ export function BookingDialog({
|
|||||||
{client.firstName} {client.lastName}
|
{client.firstName} {client.lastName}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-primary-500">
|
<p className="text-xs text-primary-500">
|
||||||
{client.email || client.phone || "Sin contacto"}
|
{client.email || client.phone || "No contact"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{client.memberships.length > 0 && (
|
{client.memberships.length > 0 && (
|
||||||
@@ -358,18 +358,18 @@ export function BookingDialog({
|
|||||||
{selectedClient && (
|
{selectedClient && (
|
||||||
<div className="mt-4 rounded-md border border-accent-200 bg-accent-50 p-3">
|
<div className="mt-4 rounded-md border border-accent-200 bg-accent-50 p-3">
|
||||||
<p className="text-sm font-medium text-accent-800">
|
<p className="text-sm font-medium text-accent-800">
|
||||||
Cliente seleccionado:
|
Selected player:
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-accent-700">
|
<p className="text-sm text-accent-700">
|
||||||
{selectedClient.firstName} {selectedClient.lastName}
|
{selectedClient.firstName} {selectedClient.lastName}
|
||||||
</p>
|
</p>
|
||||||
{selectedClient.memberships.length > 0 && (
|
{selectedClient.memberships.length > 0 && (
|
||||||
<p className="text-xs text-accent-600 mt-1">
|
<p className="text-xs text-accent-600 mt-1">
|
||||||
Membresia: {selectedClient.memberships[0].plan.name}
|
Membership: {selectedClient.memberships[0].plan.name}
|
||||||
{selectedClient.memberships[0].remainingHours !== null &&
|
{selectedClient.memberships[0].remainingHours !== null &&
|
||||||
selectedClient.memberships[0].remainingHours > 0 && (
|
selectedClient.memberships[0].remainingHours > 0 && (
|
||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
({selectedClient.memberships[0].remainingHours}h restantes)
|
({selectedClient.memberships[0].remainingHours}h remaining)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
@@ -392,7 +392,7 @@ export function BookingDialog({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-md border border-primary-200 bg-primary-50 p-4 space-y-3">
|
<div className="rounded-md border border-primary-200 bg-primary-50 p-4 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-primary-500">Cliente</p>
|
<p className="text-xs text-primary-500">Player</p>
|
||||||
<p className="font-medium text-primary-800">
|
<p className="font-medium text-primary-800">
|
||||||
{booking.client.firstName} {booking.client.lastName}
|
{booking.client.firstName} {booking.client.lastName}
|
||||||
</p>
|
</p>
|
||||||
@@ -410,7 +410,7 @@ export function BookingDialog({
|
|||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-primary-500">Estado</p>
|
<p className="text-xs text-primary-500">Status</p>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm font-medium",
|
"text-sm font-medium",
|
||||||
@@ -419,21 +419,21 @@ export function BookingDialog({
|
|||||||
booking.status === "CANCELLED" && "text-red-600"
|
booking.status === "CANCELLED" && "text-red-600"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{booking.status === "CONFIRMED" && "Confirmada"}
|
{booking.status === "CONFIRMED" && "Confirmed"}
|
||||||
{booking.status === "PENDING" && "Pendiente"}
|
{booking.status === "PENDING" && "Pending"}
|
||||||
{booking.status === "CANCELLED" && "Cancelada"}
|
{booking.status === "CANCELLED" && "Cancelled"}
|
||||||
{booking.status === "COMPLETED" && "Completada"}
|
{booking.status === "COMPLETED" && "Completed"}
|
||||||
{booking.status === "NO_SHOW" && "No asistio"}
|
{booking.status === "NO_SHOW" && "No Show"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-primary-500">Tipo de Pago</p>
|
<p className="text-xs text-primary-500">Payment Type</p>
|
||||||
<p className="text-sm text-primary-800">
|
<p className="text-sm text-primary-800">
|
||||||
{booking.paymentType === "CASH" && "Efectivo"}
|
{booking.paymentType === "CASH" && "Cash"}
|
||||||
{booking.paymentType === "CARD" && "Tarjeta"}
|
{booking.paymentType === "CARD" && "Card"}
|
||||||
{booking.paymentType === "TRANSFER" && "Transferencia"}
|
{booking.paymentType === "TRANSFER" && "Transfer"}
|
||||||
{booking.paymentType === "MEMBERSHIP" && "Membresia"}
|
{booking.paymentType === "MEMBERSHIP" && "Membership"}
|
||||||
{booking.paymentType === "FREE" && "Gratuito"}
|
{booking.paymentType === "FREE" && "Free"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -450,7 +450,7 @@ export function BookingDialog({
|
|||||||
|
|
||||||
{!loadingBookingInfo && !booking && (
|
{!loadingBookingInfo && !booking && (
|
||||||
<div className="text-center py-4 text-primary-500">
|
<div className="text-center py-4 text-primary-500">
|
||||||
<p>No se pudo cargar la informacion de la reserva.</p>
|
<p>Could not load booking information.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -460,7 +460,7 @@ export function BookingDialog({
|
|||||||
<CardFooter className="border-t border-primary-200 bg-primary-50 pt-4">
|
<CardFooter className="border-t border-primary-200 bg-primary-50 pt-4">
|
||||||
<div className="flex w-full gap-3">
|
<div className="flex w-full gap-3">
|
||||||
<Button variant="outline" onClick={onClose} className="flex-1">
|
<Button variant="outline" onClick={onClose} className="flex-1">
|
||||||
Cerrar
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{slot.available && (
|
{slot.available && (
|
||||||
@@ -473,10 +473,10 @@ export function BookingDialog({
|
|||||||
{creatingBooking ? (
|
{creatingBooking ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||||
Creando...
|
Creating...
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
"Crear Reserva"
|
"Create Booking"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -491,10 +491,10 @@ export function BookingDialog({
|
|||||||
{cancellingBooking ? (
|
{cancellingBooking ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||||
Cancelando...
|
Cancelling...
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
"Cancelar Reserva"
|
"Cancel Booking"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user