commit
32a36cceff
444 files changed
+67186
No files matched your search
@@ -0,0 +1,132 @@
|
||||
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;
|
||||
Reference in new issue
Block a user