102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import {
|
|
addDays,
|
|
addMonths,
|
|
format,
|
|
isSameDay,
|
|
isSameMonth,
|
|
startOfMonth,
|
|
startOfWeek,
|
|
} from "date-fns"
|
|
import { fr } from "date-fns/locale"
|
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
|
import { WEEK_OPTS } from "@/lib/agenda/agenda-date"
|
|
import { Button } from "@/components/ui/button"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const WEEKDAYS = ["L", "M", "M", "J", "V", "S", "D"]
|
|
|
|
export function AgendaMiniMonth({
|
|
selected,
|
|
onSelect,
|
|
}: {
|
|
selected: Date
|
|
onSelect: (date: Date) => void
|
|
}) {
|
|
const [cursor, setCursor] = useState(() => startOfMonth(selected))
|
|
|
|
useEffect(() => {
|
|
setCursor(startOfMonth(selected))
|
|
}, [selected])
|
|
|
|
const gridStart = startOfWeek(cursor, WEEK_OPTS)
|
|
const today = new Date()
|
|
const cells: Date[] = []
|
|
for (let i = 0; i < 42; i++) cells.push(addDays(gridStart, i))
|
|
|
|
return (
|
|
<div className="select-none px-1">
|
|
<div className="flex items-center justify-between pb-1 pl-2">
|
|
<span className="text-sm font-medium text-foreground/90">
|
|
{format(cursor, "MMMM yyyy", { locale: fr }).replace(/^./, (c) =>
|
|
c.toUpperCase(),
|
|
)}
|
|
</span>
|
|
<div className="flex">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-7 rounded-full text-muted-foreground"
|
|
aria-label="Mois précédent"
|
|
onClick={() => setCursor((c) => addMonths(c, -1))}
|
|
>
|
|
<ChevronLeft className="size-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-7 rounded-full text-muted-foreground"
|
|
aria-label="Mois suivant"
|
|
onClick={() => setCursor((c) => addMonths(c, 1))}
|
|
>
|
|
<ChevronRight className="size-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-7 text-center">
|
|
{WEEKDAYS.map((d, i) => (
|
|
<span
|
|
key={`${d}-${i}`}
|
|
className="py-1 text-[0.65rem] font-medium text-muted-foreground"
|
|
>
|
|
{d}
|
|
</span>
|
|
))}
|
|
{cells.map((day) => {
|
|
const isToday = isSameDay(day, today)
|
|
const isSelected = isSameDay(day, selected)
|
|
const inMonth = isSameMonth(day, cursor)
|
|
return (
|
|
<button
|
|
key={day.getTime()}
|
|
type="button"
|
|
onClick={() => onSelect(day)}
|
|
className={cn(
|
|
"mx-auto flex size-6 items-center justify-center rounded-full text-[0.7rem] transition-colors",
|
|
inMonth ? "text-foreground/85" : "text-muted-foreground/50",
|
|
isToday && "bg-primary font-semibold text-primary-foreground",
|
|
isSelected && !isToday && "bg-primary/15 font-semibold text-primary",
|
|
!isToday && !isSelected && "hover:bg-mail-nav-hover",
|
|
)}
|
|
>
|
|
{format(day, "d")}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|