Compare commits
4 Commits
a882c8698d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a713369e03 | ||
|
|
7d0d6d32f1 | ||
|
|
da8a730867 | ||
|
|
296491d0b9 |
@@ -135,7 +135,7 @@ export default function LiveCourtsPage() {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error("Failed to load court data");
|
||||
const data = await response.json();
|
||||
setCourts(data.courts ?? data.data ?? []);
|
||||
setCourts(Array.isArray(data) ? data : data.courts ?? data.data ?? []);
|
||||
setLastUpdated(new Date());
|
||||
} catch (err) {
|
||||
console.error("Live courts fetch error:", err);
|
||||
|
||||
@@ -103,7 +103,12 @@ export default function SettingsPage() {
|
||||
const res = await fetch("/api/courts");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setCourts(data.data || []);
|
||||
// Courts API returns array directly, map pricePerHour to hourlyRate for frontend
|
||||
const courtsArray = Array.isArray(data) ? data : data.data || [];
|
||||
setCourts(courtsArray.map((c: Record<string, unknown>) => ({
|
||||
...c,
|
||||
hourlyRate: c.pricePerHour ?? c.hourlyRate,
|
||||
})));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching courts:", error);
|
||||
@@ -494,14 +499,14 @@ export default function SettingsPage() {
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
||||
court.status === "active"
|
||||
["active", "AVAILABLE"].includes(court.status)
|
||||
? "bg-accent/10 text-accent-700"
|
||||
: court.status === "maintenance"
|
||||
: ["maintenance", "MAINTENANCE"].includes(court.status)
|
||||
? "bg-amber-100 text-amber-700"
|
||||
: "bg-gray-100 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{court.status === "active" ? "Active" : court.status === "maintenance" ? "Maintenance" : "Inactive"}
|
||||
{["active", "AVAILABLE"].includes(court.status) ? "Active" : ["maintenance", "MAINTENANCE"].includes(court.status) ? "Maintenance" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
@@ -726,10 +731,11 @@ function CourtFormModal({
|
||||
siteId,
|
||||
type,
|
||||
hourlyRate: parseFloat(hourlyRate),
|
||||
pricePerHour: parseFloat(hourlyRate),
|
||||
peakHourlyRate: peakHourlyRate ? parseFloat(peakHourlyRate) : null,
|
||||
status,
|
||||
isOpenPlay,
|
||||
});
|
||||
} as Partial<Court> & { pricePerHour?: number });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -125,23 +125,34 @@ export async function PUT(
|
||||
type,
|
||||
status,
|
||||
pricePerHour,
|
||||
hourlyRate,
|
||||
description,
|
||||
features,
|
||||
displayOrder,
|
||||
isActive,
|
||||
isOpenPlay,
|
||||
} = body;
|
||||
|
||||
const price = pricePerHour ?? hourlyRate;
|
||||
|
||||
// Map lowercase form values to Prisma enum values
|
||||
const typeMap: Record<string, string> = { indoor: 'INDOOR', outdoor: 'OUTDOOR', covered: 'COVERED' };
|
||||
const statusMap: Record<string, string> = { active: 'AVAILABLE', maintenance: 'MAINTENANCE', inactive: 'CLOSED' };
|
||||
const mappedType = type ? (typeMap[type.toLowerCase()] || type) : undefined;
|
||||
const mappedStatus = status ? (statusMap[status.toLowerCase()] || status) : undefined;
|
||||
|
||||
const court = await db.court.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(name !== undefined && { name }),
|
||||
...(type !== undefined && { type }),
|
||||
...(status !== undefined && { status }),
|
||||
...(pricePerHour !== undefined && { pricePerHour }),
|
||||
...(mappedType !== undefined && { type: mappedType }),
|
||||
...(mappedStatus !== undefined && { status: mappedStatus }),
|
||||
...(price !== undefined && { pricePerHour: price }),
|
||||
...(description !== undefined && { description }),
|
||||
...(features !== undefined && { features }),
|
||||
...(displayOrder !== undefined && { displayOrder }),
|
||||
...(isActive !== undefined && { isActive }),
|
||||
...(isOpenPlay !== undefined && { isOpenPlay }),
|
||||
},
|
||||
include: {
|
||||
site: {
|
||||
|
||||
@@ -97,14 +97,24 @@ export async function POST(request: NextRequest) {
|
||||
type,
|
||||
status,
|
||||
pricePerHour,
|
||||
hourlyRate,
|
||||
description,
|
||||
features,
|
||||
displayOrder,
|
||||
isActive,
|
||||
isOpenPlay,
|
||||
} = body;
|
||||
|
||||
const price = pricePerHour ?? hourlyRate;
|
||||
|
||||
// Map lowercase form values to Prisma enum values
|
||||
const typeMap: Record<string, string> = { indoor: 'INDOOR', outdoor: 'OUTDOOR', covered: 'COVERED' };
|
||||
const statusMap: Record<string, string> = { active: 'AVAILABLE', maintenance: 'MAINTENANCE', inactive: 'CLOSED' };
|
||||
const mappedType = typeMap[type?.toLowerCase()] || type || 'INDOOR';
|
||||
const mappedStatus = statusMap[status?.toLowerCase()] || status || 'AVAILABLE';
|
||||
|
||||
// Validate required fields
|
||||
if (!siteId || !name || pricePerHour === undefined) {
|
||||
if (!siteId || !name || price === undefined) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields: siteId, name, pricePerHour' },
|
||||
{ status: 400 }
|
||||
@@ -138,12 +148,13 @@ export async function POST(request: NextRequest) {
|
||||
data: {
|
||||
siteId,
|
||||
name,
|
||||
type: type || 'INDOOR',
|
||||
status: status || 'AVAILABLE',
|
||||
pricePerHour,
|
||||
type: mappedType,
|
||||
status: mappedStatus,
|
||||
pricePerHour: price,
|
||||
description: description || null,
|
||||
features: features || [],
|
||||
displayOrder: displayOrder ?? 0,
|
||||
isOpenPlay: isOpenPlay ?? false,
|
||||
isActive: isActive ?? true,
|
||||
},
|
||||
include: {
|
||||
|
||||
@@ -63,26 +63,46 @@ export async function GET(request: NextRequest) {
|
||||
orderBy: { displayOrder: 'asc' },
|
||||
});
|
||||
|
||||
// Compute status for each court
|
||||
// Compute status for each court and transform to frontend shape
|
||||
const courtsWithStatus = courts.map((court) => {
|
||||
let status: 'available' | 'active' | 'open-play' | 'booked';
|
||||
let status: 'available' | 'active' | 'open_play' | 'booked';
|
||||
|
||||
if (court.sessions.length > 0) {
|
||||
// Court has active sessions
|
||||
if (court.isOpenPlay) {
|
||||
status = 'open-play';
|
||||
} else {
|
||||
status = 'active';
|
||||
}
|
||||
status = court.isOpenPlay ? 'open_play' : 'active';
|
||||
} else if (court.isOpenPlay) {
|
||||
status = 'open_play';
|
||||
} else if (court.bookings.length > 0) {
|
||||
status = 'booked';
|
||||
} else {
|
||||
status = 'available';
|
||||
}
|
||||
|
||||
// Transform sessions to players array for frontend
|
||||
const players = court.sessions.map((session) => ({
|
||||
id: session.client?.id || session.id,
|
||||
firstName: session.client?.firstName,
|
||||
lastName: session.client?.lastName,
|
||||
walkInName: session.walkInName,
|
||||
checkedInAt: session.startTime.toISOString(),
|
||||
sessionId: session.id,
|
||||
}));
|
||||
|
||||
// Get upcoming booking info
|
||||
const upcomingBooking = court.bookings.length > 0 ? {
|
||||
startTime: court.bookings[0].startTime.toISOString(),
|
||||
clientName: court.bookings[0].client
|
||||
? `${court.bookings[0].client.firstName} ${court.bookings[0].client.lastName}`
|
||||
: 'Walk-in',
|
||||
} : undefined;
|
||||
|
||||
return {
|
||||
...court,
|
||||
id: court.id,
|
||||
name: court.name,
|
||||
type: court.type,
|
||||
isOpenPlay: court.isOpenPlay,
|
||||
status,
|
||||
players,
|
||||
upcomingBooking,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
24
scripts/start.sh
Executable file
24
scripts/start.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# SmashPoint - Cabo Pickleball Club
|
||||
# Production server start script
|
||||
|
||||
set -e
|
||||
|
||||
# Load environment variables
|
||||
export NEXTAUTH_URL="https://smashpoint.consultoria-as.com"
|
||||
export NEXT_PUBLIC_APP_URL="https://smashpoint.consultoria-as.com"
|
||||
export NEXTAUTH_SECRET="xApk6WiZYJZwUpKk6ZlyHoseXqsCSnTmRDqzDdmtRVY="
|
||||
|
||||
APP_DIR="/root/Padel/apps/web"
|
||||
PORT=3000
|
||||
|
||||
# Kill any existing server on the port
|
||||
fuser -k $PORT/tcp 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
echo "Starting SmashPoint on port $PORT..."
|
||||
echo "URL: $NEXTAUTH_URL"
|
||||
|
||||
npx next start --port $PORT
|
||||
Reference in New Issue
Block a user