Skip to content
This repository was archived by the owner on Sep 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 102 additions & 42 deletions app/components/talks/PlanningOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,55 @@
'use client';
import React, { useState, useEffect, useMemo } from 'react';

import { useEffect, useState } from 'react';
import type { Talk } from '@/lib/types';

interface Room {
roomId: number;
// Define types for schedule and nested room
export interface Room {
id: number;
name: string;
capacity: number;
description: string;
created_at: string;
}

interface ScheduledSlot {
export interface ScheduledSlot {
id: number;
roomId: number;
startTime: string;
endTime: string;
talk: Talk;
}

interface PlanningOverviewProps {
rooms: Room[];
date: Date;
talk_id: number;
room_id: number;
start_time: string;
end_time: string;
created_at: string;
updated_at: string;
rooms: Room;
}

export default function PlanningOverview({ rooms, date }: PlanningOverviewProps) {
const PlanningTable: React.FC = () => {
const [date, setDate] = useState<Date>(new Date());
const [rooms, setRooms] = useState<Room[]>([]);
const [scheduledSlots, setScheduledSlots] = useState<ScheduledSlot[]>([]);
const hours = Array.from({ length: 9 }, (_, i) => 9 + i); // 09–10 … 17–18

// Fetch rooms on mount
useEffect(() => {
const dateParam = date.toISOString().slice(0, 10); // "YYYY-MM-DD"
fetch('/api/rooms')
.then((res) => {
if (!res.ok) throw new Error('Impossible de charger les salles');
return res.json();
})
.then((data: { rooms: Room[] }) => {
setRooms(data.rooms);
})
.catch((err) => {
console.error(err);
alert(err.message);
});
}, []);

// Fetch schedules whenever the date changes
useEffect(() => {
const dateParam = date.toISOString().slice(0, 10);
fetch(`/api/schedules?date=${dateParam}`)
.then((res) => {
if (!res.ok) throw new Error('Impossible de charger le planning');
return res.json();
})
.then((data: { schedules: ScheduledSlot[] }) => {
// console.log('schedules from API:', data.schedules);
setScheduledSlots(data.schedules);
})
.catch((err) => {
Expand All @@ -42,35 +58,77 @@ export default function PlanningOverview({ rooms, date }: PlanningOverviewProps)
});
}, [date]);

// Build all possible timeslots from 09:00 to 19:00
const timeSlots = useMemo(() => {
const slots: { start: Date; end: Date; label: string }[] = [];
const day = date.toISOString().slice(0, 10);
for (let hour = 9; hour < 19; hour++) {
const start = new Date(`${day}T${String(hour).padStart(2, '0')}:00:00`);
const end = new Date(`${day}T${String(hour + 1).padStart(2, '0')}:00:00`);
const startLabel = `${String(start.getHours()).padStart(2, '0')}:${String(start.getMinutes()).padStart(2, '0')}`;
const endLabel = `${String(end.getHours()).padStart(2, '0')}:${String(end.getMinutes()).padStart(2, '0')}`;
const label = `${startLabel} - ${endLabel}`;
slots.push({ start, end, label });
}
return slots;
}, [date]);

const handleDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDate(new Date(e.target.value));
};

return (
<div className="mt-6 overflow-auto">
<table className="w-full table-fixed border-collapse">
<thead>
<div className="overflow-auto p-4">
<div className="mb-4">
<label className="mr-2 font-medium" htmlFor="schedule-date">
Date:
</label>
<input
className="rounded border px-2 py-1"
id="schedule-date"
type="date"
value={date.toISOString().slice(0, 10)}
onChange={handleDateChange}
/>
</div>

<table className="min-w-full table-fixed divide-y divide-gray-200 border">
<thead className="bg-gray-50">
<tr>
<th className="border bg-gray-100 p-2">Salle / Heure</th>
{hours.map((h) => (
<th key={h} className="border bg-gray-100 p-2 text-center">
{`${h}:00–${h + 1}:00`}
<th className="px-6 py-3 text-left text-xs font-medium tracking-wider uppercase">
Salle
</th>
{timeSlots.map((slot) => (
<th
key={slot.label}
className="px-4 py-2 text-xs font-medium tracking-wider uppercase"
>
{slot.label}
</th>
))}
</tr>
</thead>
<tbody>
<tbody className="divide-y divide-gray-200 bg-white">
{rooms.map((room) => (
<tr key={room.roomId}>
<td className="border p-2 font-medium">{room.name}</td>
{hours.map((h) => {
const slot = scheduledSlots.find(
(s) => s.roomId === room.roomId && new Date(s.startTime).getHours() === h,
);
<tr key={room.id}>
<td className="px-6 py-4 text-sm font-medium whitespace-nowrap">{room.name}</td>
{timeSlots.map((slot) => {
const match = scheduledSlots.find((s) => {
const slotStart = new Date(s.start_time);
return (
s.room_id === room.id &&
slotStart.getFullYear() === slot.start.getFullYear() &&
slotStart.getMonth() === slot.start.getMonth() &&
slotStart.getDate() === slot.start.getDate() &&
slotStart.getHours() === slot.start.getHours()
);
});
return (
<td key={h} className="h-16 border p-1 align-top">
{slot ? (
<div className="rounded bg-blue-50 p-1 text-sm">
<strong className="block truncate">{slot.talk.title}</strong>
<span className="text-xs">Speaker #{slot.talk.speakerId}</span>
</div>
) : null}
<td
key={`${room.id}-${slot.label}`}
className="px-4 py-2 text-sm whitespace-nowrap"
>
{match ? match.talk_id : '-'}
</td>
);
})}
Expand All @@ -80,4 +138,6 @@ export default function PlanningOverview({ rooms, date }: PlanningOverviewProps)
</table>
</div>
);
}
};

export default PlanningTable;
2 changes: 1 addition & 1 deletion app/components/talks/TalksList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export default function TalksList({
if (session.data?.user.id === talk.speakerId) {
return true;
}
return talk.status === 'accepted';
return talk.status === 'scheduled';
})
// .map(({ talk }) => (
.map((talk) => (
Expand Down
7 changes: 2 additions & 5 deletions app/components/talks/TalksSchedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Calendar as CalendarIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { isOrganizer } from '@/utils/auth.utils';
import type { Talk } from '@/lib/types';
import PlanningOverview from './PlanningOverview';
import PlanningTable from './PlanningOverview';

//
// Define the shape coming back from /api/rooms/availability
Expand Down Expand Up @@ -288,10 +288,7 @@ export default function TalksSchedule({ talks, onScheduleTalk }: TalksSchedulePr
</CardContent>
</Card>
</div>
<PlanningOverview
rooms={rooms.map(({ roomId, name }) => ({ roomId, name }))}
date={selectedDate}
/>
<PlanningTable />
</div>
);
}
Loading