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
2 changes: 1 addition & 1 deletion app/components/talks/MyTalksList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export default function MyTalksList({
</Button>
</>
</div>
{talk.status === 'accepted' && (
{talk.status === 'scheduled' && (
<a href={getGoogleCalendarUrl(talk)} rel="noopener noreferrer" target="_blank">
<Button size="sm" variant="outline">
<CalendarPlus className="mr-1 h-4 w-4" /> Ajouter à Google Calendar
Expand Down
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;
4 changes: 2 additions & 2 deletions 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 Expand Up @@ -262,7 +262,7 @@ export default function TalksList({
</>
)}
</div>
{talk.status === 'accepted' && (
{talk.status === 'scheduled' && (
<a href={getGoogleCalendarUrl(talk)} rel="noopener noreferrer" target="_blank">
<Button size="sm" variant="outline">
<CalendarPlus className="mr-1 h-4 w-4" /> Ajouter à Google Calendar
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>
);
}
18 changes: 16 additions & 2 deletions app/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,24 @@ export default function TalksPage() {
}

if (loading) {
return <div>Loading talks...</div>;
return (
<div className="container mx-auto space-y-8 p-4">
<Header />
<div className="flex h-96 items-center justify-center">
<div>Loading talks...</div>
</div>
</div>
);
}
if (error) {
return <div>Error loading talks: {error}</div>;
return (
<div className="container mx-auto space-y-8 p-4">
<Header />
<div className="flex h-96 items-center justify-center">
<div>Error loading talks: {error}</div>
</div>
</div>
);
}

return (
Expand Down
Loading