125 lines
4.6 KiB
TypeScript
125 lines
4.6 KiB
TypeScript
"use client"
|
|
|
|
import type { MouseEvent } from "react"
|
|
import { format, isSameDay, isSameMonth } from "date-fns"
|
|
import { fr } from "date-fns/locale"
|
|
import { AgendaEventChip } from "@/components/agenda/agenda-event-chip"
|
|
import type { AnchorRect } from "@/components/agenda/agenda-floating-card"
|
|
import { viewDays } from "@/lib/agenda/agenda-date"
|
|
import { eventsOnDay, isMultiDay } from "@/lib/agenda/agenda-events"
|
|
import type { AgendaEvent } from "@/lib/agenda/agenda-types"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const MAX_CHIPS = 4
|
|
|
|
function anchorFromEvent(e: MouseEvent<HTMLElement>): AnchorRect {
|
|
const rect = e.currentTarget.getBoundingClientRect()
|
|
return { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
}
|
|
|
|
export function AgendaViewMonth({
|
|
date,
|
|
events,
|
|
onCreateAt,
|
|
onEventClick,
|
|
onOpenDay,
|
|
}: {
|
|
date: Date
|
|
events: AgendaEvent[]
|
|
onCreateAt: (day: Date, anchor: AnchorRect) => void
|
|
onEventClick: (event: AgendaEvent, anchor: AnchorRect) => void
|
|
onOpenDay: (day: Date) => void
|
|
}) {
|
|
const days = viewDays("month", date)
|
|
const weeks: Date[][] = []
|
|
for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7))
|
|
const today = new Date()
|
|
|
|
return (
|
|
<div className="flex h-full min-h-0 flex-col overflow-hidden rounded-tl-2xl border-t border-l border-border/60 bg-card">
|
|
<div className="grid shrink-0 grid-cols-7 border-b border-border/60">
|
|
{days.slice(0, 7).map((d) => (
|
|
<div
|
|
key={d.getTime()}
|
|
className="py-1.5 text-center text-[0.7rem] font-medium tracking-wide text-muted-foreground uppercase"
|
|
>
|
|
{format(d, "EEE", { locale: fr }).replace(".", "")}
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div
|
|
className="grid min-h-0 flex-1 grid-cols-7"
|
|
style={{ gridTemplateRows: `repeat(${weeks.length}, minmax(0, 1fr))` }}
|
|
>
|
|
{weeks.map((week) =>
|
|
week.map((day) => {
|
|
const dayEvents = eventsOnDay(events, day).sort((a, b) => {
|
|
const aBanner = a.allDay || isMultiDay(a)
|
|
const bBanner = b.allDay || isMultiDay(b)
|
|
if (aBanner !== bBanner) return aBanner ? -1 : 1
|
|
return a.start.getTime() - b.start.getTime()
|
|
})
|
|
const visible = dayEvents.slice(0, MAX_CHIPS)
|
|
const hidden = dayEvents.length - visible.length
|
|
const isToday = isSameDay(day, today)
|
|
const inMonth = isSameMonth(day, date)
|
|
return (
|
|
<div
|
|
key={day.getTime()}
|
|
role="gridcell"
|
|
className={cn(
|
|
"flex min-h-0 cursor-pointer flex-col gap-0.5 overflow-hidden border-r border-b border-border/40 px-1 pb-1",
|
|
!inMonth && "bg-muted/30",
|
|
)}
|
|
onClick={(e) => onCreateAt(day, anchorFromEvent(e))}
|
|
>
|
|
<div className="flex justify-center pt-1">
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
"flex h-6 min-w-6 items-center justify-center rounded-full px-1 text-xs hover:bg-mail-nav-hover",
|
|
isToday && "bg-primary font-semibold text-primary-foreground hover:bg-primary",
|
|
!inMonth && "text-muted-foreground/60",
|
|
)}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
onOpenDay(day)
|
|
}}
|
|
>
|
|
{day.getDate() === 1 && inMonth
|
|
? format(day, "d MMM", { locale: fr })
|
|
: day.getDate()}
|
|
</button>
|
|
</div>
|
|
{visible.map((event) => (
|
|
<AgendaEventChip
|
|
key={event.key}
|
|
event={event}
|
|
filled={event.allDay || isMultiDay(event)}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
onEventClick(event, anchorFromEvent(e))
|
|
}}
|
|
/>
|
|
))}
|
|
{hidden > 0 && (
|
|
<button
|
|
type="button"
|
|
className="w-full truncate rounded-md px-1.5 py-[1px] text-left text-xs font-medium text-muted-foreground hover:bg-mail-nav-hover"
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
onOpenDay(day)
|
|
}}
|
|
>
|
|
{hidden} autre{hidden > 1 ? "s" : ""}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}),
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|