ultisuite-client/components/agenda/agenda-quick-create.tsx
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

243 lines
7.6 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
import { toast } from "sonner"
import { Users, Video, X } from "lucide-react"
import { AgendaEventScheduleFields } from "@/components/agenda/agenda-event-schedule-fields"
import { AgendaGuestPicker } from "@/components/agenda/agenda-guest-picker"
import { AgendaVideoToggle } from "@/components/agenda/agenda-video-toggle"
import { AgendaFloatingCard, type AnchorRect } from "@/components/agenda/agenda-floating-card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { useCreateAgendaEvent, useCreateAgendaMeetLink } from "@/lib/api/hooks/use-calendar-mutations"
import { createAgendaEventWithVideo } from "@/lib/agenda/agenda-save-with-video"
import { calendarColor } from "@/lib/agenda/agenda-events"
import { useEffectiveAgendaSettings } from "@/lib/agenda/use-effective-agenda-settings"
import type {
AgendaCalendar,
AgendaEventAttendee,
AgendaEventDraft,
} from "@/lib/agenda/agenda-types"
export interface AgendaQuickCreateState {
start: Date
end: Date
allDay: boolean
anchor: AnchorRect
}
export function AgendaQuickCreate({
state,
calendars,
defaultCalendarId,
userEmail,
onClose,
onMoreOptions,
}: {
state: AgendaQuickCreateState | null
calendars: AgendaCalendar[]
defaultCalendarId: string
userEmail?: string
onClose: () => void
onMoreOptions: (draft: AgendaEventDraft) => void
}) {
const [title, setTitle] = useState("")
const [calendarId, setCalendarId] = useState(defaultCalendarId)
const [start, setStart] = useState(() => new Date())
const [end, setEnd] = useState(() => new Date())
const [attendees, setAttendees] = useState<AgendaEventAttendee[]>([])
const [includeVideo, setIncludeVideo] = useState(false)
const titleRef = useRef<HTMLInputElement>(null)
const createMutation = useCreateAgendaEvent()
const meetLinkMutation = useCreateAgendaMeetLink()
const { buttonSnapMinutes, defaultVideoProvider } = useEffectiveAgendaSettings()
useEffect(() => {
if (!state) return
setTitle("")
setCalendarId(defaultCalendarId)
setStart(state.start)
setEnd(state.end)
setAttendees([])
setIncludeVideo(false)
const timer = window.setTimeout(() => titleRef.current?.focus(), 0)
return () => window.clearTimeout(timer)
}, [state, defaultCalendarId])
if (!state) return null
const calendar = calendars.find((c) => c.id === calendarId) ?? calendars[0]
const draft: AgendaEventDraft = {
title,
start,
end,
allDay: state.allDay,
calendarId,
attendees,
includeVideo,
}
const save = async () => {
if (!calendarId || !calendar) return
try {
await createAgendaEventWithVideo({
draft,
calendar,
userEmail,
includeVideo: includeVideo && !state.allDay,
videoProvider: defaultVideoProvider,
createMutation,
meetLinkMutation,
})
toast.success(includeVideo && !state.allDay ? "Événement et visio créés" : "Événement créé")
onClose()
} catch {
toast.error("Impossible de créer l'événement")
}
}
const showVideo = !state.allDay && defaultVideoProvider !== "none"
const guestTab = state.allDay ? 3 : 5
const videoTab = state.allDay ? 4 : 6
const calendarTab = state.allDay ? (showVideo ? 5 : 4) : showVideo ? 7 : 6
return (
<AgendaFloatingCard anchor={state.anchor} onClose={onClose} width={420}>
<div className="flex flex-col gap-4 px-5 pb-5 pt-3">
<div className="flex items-start gap-1">
<Input
ref={titleRef}
value={title}
tabIndex={1}
autoFocus
placeholder="Ajouter un titre"
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") void save()
}}
className="h-11 min-w-0 flex-1 rounded-none border-0 border-b-2 border-border/60 !bg-transparent px-1 !text-lg shadow-none focus-visible:border-primary focus-visible:ring-0"
/>
<Button
variant="ghost"
size="icon"
tabIndex={9}
className="size-8 shrink-0 rounded-full text-muted-foreground"
aria-label="Fermer"
onClick={onClose}
>
<X className="size-4" />
</Button>
</div>
<div className="flex flex-col gap-2">
{state.allDay ? (
<AgendaEventScheduleFields
compact
start={start}
end={end}
allDay
stepMinutes={buttonSnapMinutes}
tabIndexBase={2}
onChange={(nextStart, nextEnd) => {
setStart(nextStart)
setEnd(nextEnd)
}}
/>
) : (
<AgendaEventScheduleFields
compact
start={start}
end={end}
allDay={false}
stepMinutes={buttonSnapMinutes}
tabIndexBase={2}
onChange={(nextStart, nextEnd) => {
setStart(nextStart)
setEnd(nextEnd)
}}
/>
)}
</div>
<div className="flex items-start gap-2">
<Users className="mt-2 size-4 shrink-0 text-muted-foreground" aria-hidden />
<div className="min-w-0 flex-1">
<AgendaGuestPicker
attendees={attendees}
onChange={setAttendees}
organizerEmail={userEmail}
tabIndex={guestTab}
/>
</div>
</div>
{showVideo ? (
<div className="flex items-start gap-2">
<Video className="mt-2 size-4 shrink-0 text-muted-foreground" aria-hidden />
<AgendaVideoToggle
compact
provider={defaultVideoProvider}
enabled={includeVideo}
onEnabledChange={setIncludeVideo}
pending={createMutation.isPending || meetLinkMutation.isPending}
tabIndex={videoTab}
/>
</div>
) : null}
<Select value={calendarId} onValueChange={setCalendarId}>
<SelectTrigger
tabIndex={calendarTab}
className="h-9 w-fit min-w-44 border-0 bg-muted/60 shadow-none"
>
<SelectValue placeholder="Agenda" />
</SelectTrigger>
<SelectContent>
{calendars.map((cal) => (
<SelectItem key={cal.id} value={cal.id}>
<span className="flex items-center gap-2">
<span
className="size-3 rounded-full"
style={{ backgroundColor: calendarColor(cal) }}
/>
{cal.display_name}
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<div className="flex items-center justify-end gap-2">
<Button
variant="ghost"
tabIndex={7}
className="rounded-full"
onClick={() => onMoreOptions({ ...draft, includeVideo })}
>
Autres options
</Button>
<Button
tabIndex={8}
className="rounded-full px-5"
disabled={
createMutation.isPending ||
meetLinkMutation.isPending ||
!calendarId
}
onClick={() => void save()}
>
Enregistrer
</Button>
</div>
</div>
</AgendaFloatingCard>
)
}