Files
lti-web-client/src/figma-make/components/base/date-picker.tsx
T
2026-05-19 12:11:07 +07:00

193 lines
5.8 KiB
TypeScript

import * as React from 'react';
import { useState } from 'react';
import {
ChevronLeft,
ChevronRight,
Calendar as CalendarIcon,
} from 'lucide-react';
import { Button } from '@/figma-make/components/base/button';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/figma-make/components/base/popover';
import { Input } from '@/figma-make/components/base/input';
import { Label } from '@/figma-make/components/base/label';
interface DatePickerProps {
date: string;
onDateChange: (date: string) => void;
disabled?: boolean;
placeholder?: string;
formatDisplay?: (date: string) => string;
hasError?: boolean;
}
export function DatePicker({
date,
onDateChange,
disabled = false,
placeholder = 'Select date',
formatDisplay,
hasError = false,
}: DatePickerProps) {
const [open, setOpen] = useState(false);
const [currentMonth, setCurrentMonth] = useState(() => {
const d = date ? new Date(date) : new Date();
return { year: d.getFullYear(), month: d.getMonth() };
});
const defaultFormatDisplay = (dateStr: string) => {
if (!dateStr) return placeholder;
const d = new Date(dateStr + 'T00:00:00');
return d.toLocaleDateString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
// const formatDateInput = (dateStr: string) => {
// if (!dateStr) return '';
// const d = new Date(dateStr + 'T00:00:00');
// return d.toLocaleDateString('en-GB', {
// day: '2-digit',
// month: '2-digit',
// year: 'numeric',
// });
// };
const displayFormatter = formatDisplay || defaultFormatDisplay;
const navigateMonth = (direction: 'prev' | 'next') => {
const newDate = new Date(
currentMonth.year,
currentMonth.month + (direction === 'next' ? 1 : -1)
);
setCurrentMonth({ year: newDate.getFullYear(), month: newDate.getMonth() });
};
const handleDateSelect = (dateStr: string) => {
onDateChange(dateStr);
setOpen(false);
};
const handleManualInput = (value: string) => {
onDateChange(value);
setOpen(false);
};
const renderCalendar = () => {
const { year, month } = currentMonth;
const firstDay = new Date(year, month, 1).getDay();
const adjustedFirstDay = firstDay === 0 ? 6 : firstDay - 1; // Monday = 0
const daysInMonth = new Date(year, month + 1, 0).getDate();
const monthName = new Date(year, month).toLocaleDateString('en-US', {
month: 'long',
});
const days = [];
// Empty cells before first day
for (let i = 0; i < adjustedFirstDay; i++) {
days.push(<div key={`empty-${i}`} className='h-9 w-9' />);
}
// Days of the month
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isSelected = dateStr === date;
const isToday = dateStr === new Date().toISOString().split('T')[0];
days.push(
<button
key={day}
onClick={() => handleDateSelect(dateStr)}
className={`
h-9 w-9 rounded-md text-sm font-medium transition-colors
${isSelected ? 'bg-[#0069e0] text-white hover:bg-[#0052b3]' : ''}
${!isSelected && isToday ? 'border border-[#0069e0] text-[#0069e0]' : ''}
${!isSelected && !isToday ? 'hover:bg-gray-100 text-gray-700' : ''}
`}
>
{day}
</button>
);
}
return (
<div className='p-3'>
<div className='flex items-center justify-between mb-3'>
<button
onClick={() => navigateMonth('prev')}
className='p-1 hover:bg-gray-100 rounded-md transition-colors'
>
<ChevronLeft className='w-4 h-4 text-gray-600' />
</button>
<div className='font-semibold text-sm text-gray-900'>
{monthName} {year}
</div>
<button
onClick={() => navigateMonth('next')}
className='p-1 hover:bg-gray-100 rounded-md transition-colors'
>
<ChevronRight className='w-4 h-4 text-gray-600' />
</button>
</div>
<div className='grid grid-cols-7 gap-1 mb-2'>
{['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'].map((day) => (
<div
key={day}
className='h-9 w-9 flex items-center justify-center text-xs font-medium text-gray-500'
>
{day}
</div>
))}
</div>
<div className='grid grid-cols-7 gap-1'>{days}</div>
</div>
);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant='outline'
disabled={disabled}
className={`w-full justify-start text-left font-normal hover:bg-gray-50 ${hasError ? 'border-red-500 focus:ring-red-500' : 'border-gray-200'}`}
>
<CalendarIcon className='mr-2 h-4 w-4 text-gray-500' />
{date ? (
<span className='text-gray-900'>{displayFormatter(date)}</span>
) : (
<span className='text-gray-500'>{placeholder}</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent
className='w-auto p-0 bg-white shadow-lg rounded-xl'
align='start'
>
{renderCalendar()}
<div className='p-3 border-t border-gray-200'>
<Label
htmlFor='manual-date'
className='text-xs text-gray-600 mb-1.5 block'
>
Atau ketik manual (YYYY-MM-DD):
</Label>
<Input
id='manual-date'
type='date'
value={date}
onChange={(e) => handleManualInput(e.target.value)}
className='text-sm border-gray-200'
/>
</div>
</PopoverContent>
</Popover>
);
}