Update planning
This commit is contained in:
parent
44d837496f
commit
976b51ca1b
1 changed files with 539 additions and 212 deletions
|
|
@ -5,102 +5,68 @@ interface Event {
|
|||
title: string;
|
||||
date: string;
|
||||
time: string;
|
||||
endTime?: string;
|
||||
type: "meeting" | "task" | "reminder";
|
||||
color: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
type ViewType = "month" | "week" | "day";
|
||||
|
||||
export const PlanningPage = () => {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const [currentView, setCurrentView] = useState<ViewType>("month");
|
||||
const [events, setEvents] = useState<Event[]>([
|
||||
{
|
||||
id: 1,
|
||||
title: "Réunion équipe",
|
||||
date: "2024-01-15",
|
||||
time: "10:00",
|
||||
endTime: "11:00",
|
||||
type: "meeting",
|
||||
color: "bg-blue-500",
|
||||
description: "Discussion sur les objectifs du trimestre",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Présentation client",
|
||||
date: "2024-01-16",
|
||||
time: "14:30",
|
||||
endTime: "16:00",
|
||||
type: "meeting",
|
||||
color: "bg-red-500",
|
||||
description: "Présentation du nouveau produit",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Révision code",
|
||||
date: "2024-01-17",
|
||||
time: "09:00",
|
||||
endTime: "10:30",
|
||||
type: "task",
|
||||
color: "bg-green-500",
|
||||
description: "Code review des fonctionnalités développées",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Appel client",
|
||||
date: "2024-01-18",
|
||||
time: "15:00",
|
||||
endTime: "15:30",
|
||||
type: "meeting",
|
||||
color: "bg-purple-500",
|
||||
},
|
||||
]);
|
||||
|
||||
const [isEventModalOpen, setIsEventModalOpen] = useState(false);
|
||||
const [newEventTitle, setNewEventTitle] = useState("");
|
||||
const [newEventTime, setNewEventTime] = useState("");
|
||||
const [newEventEndTime, setNewEventEndTime] = useState("");
|
||||
const [newEventType, setNewEventType] = useState<
|
||||
"meeting" | "task" | "reminder"
|
||||
>("meeting");
|
||||
|
||||
// Get calendar days for current month
|
||||
const getDaysInMonth = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastDay.getDate();
|
||||
const startingDayOfWeek = firstDay.getDay();
|
||||
|
||||
const days = [];
|
||||
|
||||
// Add empty cells for days before the first day of the month
|
||||
for (let i = 0; i < startingDayOfWeek; i++) {
|
||||
days.push(null);
|
||||
}
|
||||
|
||||
// Add all days of the month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
days.push(new Date(year, month, day));
|
||||
}
|
||||
|
||||
return days;
|
||||
};
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
return date.toISOString().split("T")[0];
|
||||
};
|
||||
|
||||
const getEventsForDate = (date: Date) => {
|
||||
const dateString = formatDate(date);
|
||||
return events.filter((event) => event.date === dateString);
|
||||
};
|
||||
|
||||
const addEvent = () => {
|
||||
if (newEventTitle.trim()) {
|
||||
const newEvent: Event = {
|
||||
id: Math.max(...events.map((e) => e.id), 0) + 1,
|
||||
title: newEventTitle.trim(),
|
||||
date: formatDate(selectedDate),
|
||||
time: newEventTime || "09:00",
|
||||
type: newEventType,
|
||||
color:
|
||||
newEventType === "meeting"
|
||||
? "bg-blue-500"
|
||||
: newEventType === "task"
|
||||
? "bg-green-500"
|
||||
: "bg-yellow-500",
|
||||
};
|
||||
setEvents([...events, newEvent]);
|
||||
setIsEventModalOpen(false);
|
||||
setNewEventTitle("");
|
||||
setNewEventTime("");
|
||||
setNewEventType("meeting");
|
||||
}
|
||||
};
|
||||
const [newEventDescription, setNewEventDescription] = useState("");
|
||||
|
||||
const monthNames = [
|
||||
"Janvier",
|
||||
|
|
@ -117,171 +83,493 @@ export const PlanningPage = () => {
|
|||
"Décembre",
|
||||
];
|
||||
|
||||
const dayNames = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
|
||||
const dayNames = [
|
||||
"Dimanche",
|
||||
"Lundi",
|
||||
"Mardi",
|
||||
"Mercredi",
|
||||
"Jeudi",
|
||||
"Vendredi",
|
||||
"Samedi",
|
||||
];
|
||||
const dayNamesShort = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];
|
||||
|
||||
const navigateMonth = (direction: number) => {
|
||||
setCurrentDate(
|
||||
new Date(currentDate.getFullYear(), currentDate.getMonth() + direction, 1)
|
||||
);
|
||||
const formatDate = (date: Date) => {
|
||||
// Use local timezone instead of UTC to avoid timezone issues
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Planning
|
||||
</h1>
|
||||
<button
|
||||
onClick={() => setIsEventModalOpen(true)}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
const getEventsForDate = (date: Date) => {
|
||||
const dateString = formatDate(date);
|
||||
return events.filter((event) => event.date === dateString);
|
||||
};
|
||||
|
||||
const navigateDate = (direction: number) => {
|
||||
const newDate = new Date(currentDate);
|
||||
if (currentView === "month") {
|
||||
newDate.setMonth(newDate.getMonth() + direction);
|
||||
} else if (currentView === "week") {
|
||||
newDate.setDate(newDate.getDate() + direction * 7);
|
||||
} else {
|
||||
newDate.setDate(newDate.getDate() + direction);
|
||||
}
|
||||
setCurrentDate(newDate);
|
||||
};
|
||||
|
||||
const goToToday = () => {
|
||||
const today = new Date();
|
||||
setCurrentDate(today);
|
||||
setSelectedDate(today);
|
||||
};
|
||||
|
||||
const addEvent = () => {
|
||||
if (newEventTitle.trim()) {
|
||||
const newEvent: Event = {
|
||||
id: Math.max(...events.map((e) => e.id), 0) + 1,
|
||||
title: newEventTitle.trim(),
|
||||
date: formatDate(selectedDate),
|
||||
time: newEventTime || "09:00",
|
||||
endTime: newEventEndTime || "",
|
||||
type: newEventType,
|
||||
color:
|
||||
newEventType === "meeting"
|
||||
? "bg-blue-500"
|
||||
: newEventType === "task"
|
||||
? "bg-green-500"
|
||||
: "bg-yellow-500",
|
||||
description: newEventDescription.trim(),
|
||||
};
|
||||
setEvents([...events, newEvent]);
|
||||
setIsEventModalOpen(false);
|
||||
setNewEventTitle("");
|
||||
setNewEventTime("");
|
||||
setNewEventEndTime("");
|
||||
setNewEventType("meeting");
|
||||
setNewEventDescription("");
|
||||
}
|
||||
};
|
||||
|
||||
const getViewTitle = () => {
|
||||
if (currentView === "month") {
|
||||
return `${
|
||||
monthNames[currentDate.getMonth()]
|
||||
} ${currentDate.getFullYear()}`;
|
||||
} else if (currentView === "week") {
|
||||
const startOfWeek = new Date(currentDate);
|
||||
// Get Monday as first day (0=Sunday, 1=Monday, etc.)
|
||||
const dayOfWeek = currentDate.getDay();
|
||||
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
startOfWeek.setDate(currentDate.getDate() - daysFromMonday);
|
||||
const endOfWeek = new Date(startOfWeek);
|
||||
endOfWeek.setDate(startOfWeek.getDate() + 6);
|
||||
|
||||
if (startOfWeek.getMonth() === endOfWeek.getMonth()) {
|
||||
return `${startOfWeek.getDate()} - ${endOfWeek.getDate()} ${
|
||||
monthNames[startOfWeek.getMonth()]
|
||||
} ${startOfWeek.getFullYear()}`;
|
||||
} else {
|
||||
return `${startOfWeek.getDate()} ${
|
||||
monthNames[startOfWeek.getMonth()]
|
||||
} - ${endOfWeek.getDate()} ${
|
||||
monthNames[endOfWeek.getMonth()]
|
||||
} ${startOfWeek.getFullYear()}`;
|
||||
}
|
||||
} else {
|
||||
return currentDate.toLocaleDateString("fr-FR", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDaysInMonth = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastDay.getDate();
|
||||
// Adjust for Monday as first day of week
|
||||
const startingDayOfWeek = firstDay.getDay();
|
||||
const mondayStartingDay =
|
||||
startingDayOfWeek === 0 ? 6 : startingDayOfWeek - 1;
|
||||
|
||||
const days = [];
|
||||
for (let i = 0; i < mondayStartingDay; i++) {
|
||||
days.push(null);
|
||||
}
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
days.push(new Date(year, month, day));
|
||||
}
|
||||
return days;
|
||||
};
|
||||
|
||||
const getWeekDays = () => {
|
||||
const startOfWeek = new Date(currentDate);
|
||||
// Get Monday as first day (0=Sunday, 1=Monday, etc.)
|
||||
const dayOfWeek = currentDate.getDay();
|
||||
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Sunday = 6 days from Monday
|
||||
startOfWeek.setDate(currentDate.getDate() - daysFromMonday);
|
||||
|
||||
const weekDays = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const day = new Date(startOfWeek);
|
||||
day.setDate(startOfWeek.getDate() + i);
|
||||
weekDays.push(day);
|
||||
}
|
||||
return weekDays;
|
||||
};
|
||||
|
||||
const timeSlots = Array.from(
|
||||
{ length: 24 },
|
||||
(_, i) => `${i.toString().padStart(2, "0")}:00`
|
||||
);
|
||||
|
||||
const renderMonthView = () => (
|
||||
<div className="flex-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
{/* Days header */}
|
||||
<div className="grid grid-cols-7 border-b border-gray-200 dark:border-gray-700">
|
||||
{dayNamesShort.map((day) => (
|
||||
<div
|
||||
key={day}
|
||||
className="p-4 text-center text-sm font-medium text-gray-500 dark:text-gray-400 border-r border-gray-200 dark:border-gray-700 last:border-r-0"
|
||||
>
|
||||
+ Nouvel événement
|
||||
</button>
|
||||
</div>
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Calendar */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
{/* Calendar Header */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
|
||||
</h2>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => navigateMonth(-1)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
{/* Calendar grid */}
|
||||
<div className="grid grid-cols-7">
|
||||
{getDaysInMonth(currentDate).map((day, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`min-h-[120px] border-b border-gray-200 dark:border-gray-700 ${
|
||||
(index + 1) % 7 !== 0
|
||||
? "border-r border-gray-200 dark:border-gray-700"
|
||||
: ""
|
||||
} ${
|
||||
day
|
||||
? "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
: "bg-gray-50 dark:bg-gray-900"
|
||||
} ${
|
||||
day && formatDate(day) === formatDate(new Date())
|
||||
? "bg-blue-50 dark:bg-blue-900/20"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (day) {
|
||||
setSelectedDate(day);
|
||||
setIsEventModalOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{day && (
|
||||
<div className="p-2">
|
||||
<div
|
||||
className={`text-sm font-medium mb-1 ${
|
||||
formatDate(day) === formatDate(new Date())
|
||||
? "text-blue-600 dark:text-blue-400"
|
||||
: "text-gray-900 dark:text-white"
|
||||
}`}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigateMonth(1)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{day.getDate()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{getEventsForDate(day)
|
||||
.slice(0, 3)
|
||||
.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className={`text-xs px-2 py-1 rounded text-white ${event.color} truncate cursor-pointer hover:opacity-80`}
|
||||
title={`${event.time} ${event.title}`}
|
||||
>
|
||||
{event.title}
|
||||
</div>
|
||||
))}
|
||||
{getEventsForDate(day).length > 3 && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 px-2">
|
||||
+{getEventsForDate(day).length - 3} autres
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
{/* Calendar Grid */}
|
||||
<div className="grid grid-cols-7 gap-1 mb-2">
|
||||
{dayNames.map((day) => (
|
||||
const renderWeekView = () => (
|
||||
<div className="flex-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 flex flex-col">
|
||||
{/* Week header */}
|
||||
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="w-20 p-4 border-r border-gray-200 dark:border-gray-700 flex-shrink-0"></div>
|
||||
{getWeekDays().map((day, index) => (
|
||||
<div
|
||||
key={day.toISOString()}
|
||||
className={`flex-1 p-4 text-center border-r border-gray-200 dark:border-gray-700 ${
|
||||
index === 6 ? "border-r-0" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 uppercase">
|
||||
{dayNamesShort[day.getDay() === 0 ? 6 : day.getDay() - 1]}
|
||||
</div>
|
||||
<div
|
||||
className={`text-lg font-medium mt-1 ${
|
||||
formatDate(day) === formatDate(new Date())
|
||||
? "text-blue-600 dark:text-blue-400"
|
||||
: "text-gray-900 dark:text-white"
|
||||
}`}
|
||||
>
|
||||
{day.getDate()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Time slots */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{timeSlots.map((time) => (
|
||||
<div
|
||||
key={time}
|
||||
className="flex border-b border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="w-20 p-2 text-xs text-gray-500 dark:text-gray-400 text-right border-r border-gray-200 dark:border-gray-700 flex-shrink-0">
|
||||
{time}
|
||||
</div>
|
||||
{getWeekDays().map((day, index) => (
|
||||
<div
|
||||
key={`${day.toISOString()}-${time}`}
|
||||
className={`flex-1 min-h-[60px] border-r border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer relative ${
|
||||
index === 6 ? "border-r-0" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedDate(day);
|
||||
setNewEventTime(time);
|
||||
setIsEventModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{getEventsForDate(day)
|
||||
.filter((event) => event.time.startsWith(time.split(":")[0]))
|
||||
.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className={`absolute left-1 right-1 top-1 p-1 rounded text-white ${event.color} text-xs`}
|
||||
>
|
||||
<div className="font-medium truncate">{event.title}</div>
|
||||
<div className="opacity-75">
|
||||
{event.time} - {event.endTime}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderDayView = () => (
|
||||
<div className="flex-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
{/* Day header */}
|
||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700 text-center">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 uppercase">
|
||||
{dayNames[currentDate.getDay()]}
|
||||
</div>
|
||||
<div className="text-2xl font-medium text-gray-900 dark:text-white mt-1">
|
||||
{currentDate.getDate()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Time slots */}
|
||||
<div className="max-h-[600px] overflow-y-auto">
|
||||
{timeSlots.map((time) => (
|
||||
<div
|
||||
key={time}
|
||||
className="flex border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer relative min-h-[60px]"
|
||||
onClick={() => {
|
||||
setNewEventTime(time);
|
||||
setIsEventModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="w-20 p-2 text-xs text-gray-500 dark:text-gray-400 text-right border-r border-gray-200 dark:border-gray-700">
|
||||
{time}
|
||||
</div>
|
||||
<div className="flex-1 p-2 relative">
|
||||
{getEventsForDate(currentDate)
|
||||
.filter((event) => event.time.startsWith(time.split(":")[0]))
|
||||
.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className={`p-2 rounded text-white ${event.color} mb-1`}
|
||||
>
|
||||
<div className="font-medium">{event.title}</div>
|
||||
<div className="text-sm opacity-75">
|
||||
{event.time} - {event.endTime}
|
||||
</div>
|
||||
{event.description && (
|
||||
<div className="text-sm mt-1 opacity-75">
|
||||
{event.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 dark:bg-gray-900">
|
||||
<div className="flex">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 min-h-screen">
|
||||
<div className="p-4">
|
||||
<button
|
||||
onClick={() => setIsEventModalOpen(true)}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium shadow-sm"
|
||||
>
|
||||
+ Créer un événement
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mini Calendar */}
|
||||
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white mb-3">
|
||||
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-1 text-xs">
|
||||
{dayNamesShort.map((day) => (
|
||||
<div
|
||||
key={day}
|
||||
className="p-3 text-center text-sm font-medium text-gray-500 dark:text-gray-400"
|
||||
className="text-center text-gray-500 dark:text-gray-400 p-1"
|
||||
>
|
||||
{day}
|
||||
{day.slice(0, 1)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
{getDaysInMonth(currentDate).map((day, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`min-h-[120px] p-2 border border-gray-200 dark:border-gray-700 ${
|
||||
day
|
||||
? "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
: ""
|
||||
className={`text-center p-1 cursor-pointer rounded ${
|
||||
day ? "hover:bg-gray-100 dark:hover:bg-gray-700" : ""
|
||||
} ${
|
||||
day && formatDate(day) === formatDate(selectedDate)
|
||||
? "bg-blue-100 dark:bg-blue-900"
|
||||
day && formatDate(day) === formatDate(new Date())
|
||||
? "bg-blue-600 text-white"
|
||||
: day
|
||||
? "text-gray-900 dark:text-white"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => day && setSelectedDate(day)}
|
||||
onClick={() => {
|
||||
if (day) {
|
||||
setSelectedDate(day);
|
||||
setIsEventModalOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{day && (
|
||||
<>
|
||||
<div className="text-base font-medium text-gray-900 dark:text-white mb-2">
|
||||
{day.getDate()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{getEventsForDate(day)
|
||||
.slice(0, 3)
|
||||
.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className={`text-xs px-2 py-1 rounded text-white ${event.color} truncate`}
|
||||
>
|
||||
{event.time} {event.title}
|
||||
</div>
|
||||
))}
|
||||
{getEventsForDate(day).length > 3 && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
+{getEventsForDate(day).length - 3} autres
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{day ? day.getDate() : ""}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Selected Date Events */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Événements du{" "}
|
||||
{selectedDate.toLocaleDateString("fr-FR", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{getEventsForDate(selectedDate).length > 0 ? (
|
||||
getEventsForDate(selectedDate).map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="flex items-center space-x-3 p-3 rounded-lg bg-gray-50 dark:bg-gray-700"
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
Planning
|
||||
</h1>
|
||||
<button
|
||||
onClick={goToToday}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Aujourd'hui
|
||||
</button>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => navigateDate(-1)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full ${event.color}`}
|
||||
></div>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{event.title}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{event.time} • {event.type}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-gray-500 dark:text-gray-400 text-sm col-span-full">
|
||||
Aucun événement prévu pour cette date
|
||||
</p>
|
||||
)}
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigateDate(1)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<h2 className="text-xl font-medium text-gray-900 dark:text-white">
|
||||
{getViewTitle()}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
|
||||
{(["month", "week", "day"] as ViewType[]).map((view) => (
|
||||
<button
|
||||
key={view}
|
||||
onClick={() => setCurrentView(view)}
|
||||
className={`px-3 py-1.5 text-sm rounded-md transition-colors capitalize ${
|
||||
currentView === view
|
||||
? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-sm"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{view === "month"
|
||||
? "Mois"
|
||||
: view === "week"
|
||||
? "Semaine"
|
||||
: "Jour"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Calendar Views */}
|
||||
<div className="flex-1 p-4">
|
||||
{currentView === "month" && renderMonthView()}
|
||||
{currentView === "week" && renderWeekView()}
|
||||
{currentView === "day" && renderDayView()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -289,35 +577,48 @@ export const PlanningPage = () => {
|
|||
{isEventModalOpen && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md mx-4">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Nouvel événement
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Titre
|
||||
Titre *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newEventTitle}
|
||||
onChange={(e) => setNewEventTitle(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Titre de l'événement"
|
||||
placeholder="Ajouter un titre"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Heure
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
value={newEventTime}
|
||||
onChange={(e) => setNewEventTime(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Début
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
value={newEventTime}
|
||||
onChange={(e) => setNewEventTime(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Fin
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
value={newEventEndTime}
|
||||
onChange={(e) => setNewEventEndTime(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -339,26 +640,52 @@ export const PlanningPage = () => {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Date: {selectedDate.toLocaleDateString("fr-FR")}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
value={newEventDescription}
|
||||
onChange={(e) => setNewEventDescription(e.target.value)}
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Ajouter une description (optionnel)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-700 p-3 rounded-md">
|
||||
📅{" "}
|
||||
{selectedDate.toLocaleDateString("fr-FR", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md"
|
||||
onClick={() => setIsEventModalOpen(false)}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||
onClick={() => {
|
||||
setIsEventModalOpen(false);
|
||||
setNewEventTitle("");
|
||||
setNewEventTime("");
|
||||
setNewEventEndTime("");
|
||||
setNewEventType("meeting");
|
||||
setNewEventDescription("");
|
||||
}}
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
onClick={addEvent}
|
||||
disabled={!newEventTitle.trim()}
|
||||
>
|
||||
Créer
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue