133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import React, { useState, useMemo } from 'react';
|
|
|
|
interface CalendarEvent {
|
|
id: string;
|
|
date: Date;
|
|
text: string;
|
|
color: string;
|
|
}
|
|
|
|
interface CalendarViewProps {
|
|
events: CalendarEvent[];
|
|
onDateSelect: (date: Date) => void;
|
|
selectedDate: Date | null;
|
|
}
|
|
|
|
const EventPill: React.FC<{ text: string; color: string }> = ({ text, color }) => (
|
|
<div className={`text-white text-[10px] px-1.5 py-0.5 rounded-full truncate ${color}`}>
|
|
{text}
|
|
</div>
|
|
);
|
|
|
|
const CalendarView: React.FC<CalendarViewProps> = ({ events, onDateSelect, selectedDate }) => {
|
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
|
|
|
const changeMonth = (amount: number) => {
|
|
setCurrentMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() + amount, 1));
|
|
};
|
|
|
|
const calendarGrid = useMemo(() => {
|
|
const year = currentMonth.getFullYear();
|
|
const month = currentMonth.getMonth();
|
|
|
|
const firstDayOfMonth = new Date(year, month, 1).getDay();
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
|
|
const grid = [];
|
|
let dayCounter = 1;
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
// 6 weeks for full display
|
|
const week = [];
|
|
for (let j = 0; j < 7; j++) {
|
|
if ((i === 0 && j < firstDayOfMonth) || dayCounter > daysInMonth) {
|
|
week.push(null);
|
|
} else {
|
|
const date = new Date(year, month, dayCounter);
|
|
week.push(date);
|
|
dayCounter++;
|
|
}
|
|
}
|
|
grid.push(week);
|
|
if (dayCounter > daysInMonth) break;
|
|
}
|
|
return grid;
|
|
}, [currentMonth]);
|
|
|
|
const isSameDay = (d1: Date, d2: Date) =>
|
|
d1.getFullYear() === d2.getFullYear() &&
|
|
d1.getMonth() === d2.getMonth() &&
|
|
d1.getDate() === d2.getDate();
|
|
|
|
return (
|
|
<div className="w-full">
|
|
<div className="flex justify-between items-center mb-4 px-2">
|
|
<button
|
|
onClick={() => changeMonth(-1)}
|
|
className="text-gray-600 hover:text-red-600 p-2 rounded-full w-10 h-10 flex items-center justify-center hover:bg-gray-100"
|
|
>
|
|
<i className="fa-solid fa-chevron-left"></i>
|
|
</button>
|
|
<h3 className="text-lg font-bold text-gray-800">
|
|
{currentMonth.toLocaleString('id-ID', { month: 'long', year: 'numeric' })}
|
|
</h3>
|
|
<button
|
|
onClick={() => changeMonth(1)}
|
|
className="text-gray-600 hover:text-red-600 p-2 rounded-full w-10 h-10 flex items-center justify-center hover:bg-gray-100"
|
|
>
|
|
<i className="fa-solid fa-chevron-right"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-7 gap-1 text-center text-xs font-semibold text-gray-500 mb-2">
|
|
{['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'].map((day) => (
|
|
<div key={day}>{day}</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-7 gap-1">
|
|
{calendarGrid.flat().map((date, index) => {
|
|
if (!date) {
|
|
return <div key={`empty-${index}`} className="h-24 bg-gray-50 rounded-md"></div>;
|
|
}
|
|
const isToday = isSameDay(date, new Date());
|
|
const isSelected = selectedDate ? isSameDay(date, selectedDate) : false;
|
|
const dayEvents = events.filter((e) => isSameDay(e.date, date));
|
|
|
|
return (
|
|
<div
|
|
key={date.toString()}
|
|
onClick={() => onDateSelect(date)}
|
|
className={`h-24 p-1.5 border rounded-md flex flex-col cursor-pointer transition-colors ${
|
|
isSelected
|
|
? 'bg-red-100 border-red-300'
|
|
: 'bg-white hover:bg-gray-100 border-gray-200'
|
|
}`}
|
|
>
|
|
<span
|
|
className={`font-semibold text-xs flex items-center justify-center w-6 h-6 rounded-full ${
|
|
isToday ? 'bg-red-600 text-white' : 'text-gray-700'
|
|
}`}
|
|
>
|
|
{date.getDate()}
|
|
</span>
|
|
<div className="mt-1 space-y-1 overflow-hidden">
|
|
{dayEvents.slice(0, 2).map((event) => (
|
|
<EventPill key={event.id} text={event.text} color={event.color} />
|
|
))}
|
|
{dayEvents.length > 2 && (
|
|
<div className="text-[10px] text-gray-500 font-semibold text-center">
|
|
+{dayEvents.length - 2} lagi
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CalendarView;
|