feat: translate bookings page and components to English

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ivan
2026-03-01 21:22:55 +00:00
parent 0fb27b1825
commit 3e65974727
3 changed files with 52 additions and 52 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
)} )}