letsbe-hub-dashboard/src/components/calendar/mini-calendar.tsx

147 lines
4.0 KiB
TypeScript
Raw Normal View History

'use client'
import { useState, useMemo } from 'react'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
interface MiniCalendarProps {
selectedDate: string
onSelectDate: (date: string) => void
}
const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
function getDaysInMonth(year: number, month: number): number {
return new Date(year, month + 1, 0).getDate()
}
function getFirstDayOfMonth(year: number, month: number): number {
return new Date(year, month, 1).getDay()
}
function formatDate(year: number, month: number, day: number): string {
return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}
export function MiniCalendar({ selectedDate, onSelectDate }: MiniCalendarProps) {
const selected = selectedDate ? new Date(selectedDate) : new Date()
const [viewYear, setViewYear] = useState(selected.getFullYear())
const [viewMonth, setViewMonth] = useState(selected.getMonth())
const today = new Date()
const todayStr = formatDate(
today.getFullYear(),
today.getMonth(),
today.getDate()
)
const selectedStr = selectedDate?.split('T')[0]?.split(' ')[0] || todayStr
const days = useMemo(() => {
const daysInMonth = getDaysInMonth(viewYear, viewMonth)
const firstDay = getFirstDayOfMonth(viewYear, viewMonth)
const result: (number | null)[] = []
// Leading empty cells
for (let i = 0; i < firstDay; i++) {
result.push(null)
}
// Days of month
for (let d = 1; d <= daysInMonth; d++) {
result.push(d)
}
return result
}, [viewYear, viewMonth])
function prevMonth() {
if (viewMonth === 0) {
setViewYear(viewYear - 1)
setViewMonth(11)
} else {
setViewMonth(viewMonth - 1)
}
}
function nextMonth() {
if (viewMonth === 11) {
setViewYear(viewYear + 1)
setViewMonth(0)
} else {
setViewMonth(viewMonth + 1)
}
}
function goToToday() {
setViewYear(today.getFullYear())
setViewMonth(today.getMonth())
onSelectDate(todayStr)
}
const monthLabel = new Date(viewYear, viewMonth).toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
})
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={prevMonth}>
<ChevronLeft className="h-4 w-4" />
</Button>
<button
onClick={goToToday}
className="text-sm font-medium hover:underline"
>
{monthLabel}
</button>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={nextMonth}>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-7 gap-0">
{WEEKDAYS.map((wd) => (
<div
key={wd}
className="py-1 text-center text-[10px] font-medium text-muted-foreground"
>
{wd}
</div>
))}
{days.map((day, idx) => {
if (day === null) {
return <div key={`empty-${idx}`} className="h-7" />
}
const dateStr = formatDate(viewYear, viewMonth, day)
const isToday = dateStr === todayStr
const isSelected = dateStr === selectedStr
return (
<button
key={dateStr}
onClick={() => onSelectDate(dateStr)}
className={cn(
'flex h-7 w-full items-center justify-center rounded-md text-xs transition-colors hover:bg-accent',
isToday && !isSelected && 'font-bold text-primary',
isSelected && 'bg-primary text-primary-foreground hover:bg-primary/90'
)}
>
{day}
</button>
)
})}
</div>
<Button
variant="outline"
size="sm"
className="w-full text-xs"
onClick={goToToday}
>
Today
</Button>
</div>
)
}