"use client" import { Clock3Icon, Loader2Icon, RefreshCwIcon } from "lucide-react" import { useEffect, useRef, useState } from "react" import { Controller, useForm } from "react-hook-form" import { toast } from "sonner" import { useAuth } from "@/components/auth-provider" import { useConfirm } from "@/components/confirm-provider" import { OptionCombobox } from "@/components/option-combobox" import { ProjectDialog } from "@/components/project-dialog" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Field, FieldError, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { useAppLocale, useI18n } from "@/i18n/provider" import { fetchAvailabilityAgents, fetchMyAvailability, updateAgentAvailability, updateMyAvailability, type OperatorAvailability } from "@/lib/api/support-availability" import { isSameSession } from "@/lib/session-request-guard" import { readSession } from "@/lib/auth" import { ServiceStatus } from "@/lib/generated/enums" import { availabilityReasonLabel, availabilityStatusLabel, availabilityStatusOptions } from "@/lib/support-availability-i18n" import { formatAvailabilityTime, localDateTimeCandidates, selectedLocalDateTimeISO, toLocalDateTime } from "@/lib/support-availability" export const AVAILABILITY_UPDATED_EVENT = "support-availability-updated" export function notifyAvailabilityUpdated() { window.dispatchEvent(new Event(AVAILABILITY_UPDATED_EVENT)) } export function OperatorStatusControl(props: { userId?: number; compact?: boolean }) { const { session } = useAuth() return } function OperatorStatusControlContent({ userId, compact = false }: { userId?: number; compact?: boolean }) { const t = useI18n() const { session } = useAuth() const [value, setValue] = useState(null) const [error, setError] = useState(false) const [loading, setLoading] = useState(true) const [open, setOpen] = useState(false) const [refreshKey, setRefreshKey] = useState(0) const accountId = session?.user.id useEffect(() => { let active = true let inFlight = false let controller: AbortController | null = null async function load() { if (inFlight || !accountId) return inFlight = true controller = new AbortController() try { const data = userId ? (await fetchAvailabilityAgents(undefined, controller.signal)).find(item => item.userId === userId) ?? null : await fetchMyAvailability(controller.signal) if (active) { setValue(data); setError(false) } } catch { if (active) { setError(true); setValue(null) } } finally { inFlight = false; if (active) setLoading(false) } } void load() const refresh = () => { if (document.visibilityState === "visible") void load() } window.addEventListener(AVAILABILITY_UPDATED_EVENT, refresh) window.addEventListener("focus", refresh) const timer = window.setInterval(refresh, 30000) return () => { active = false; controller?.abort(); window.clearInterval(timer); window.removeEventListener("focus", refresh); window.removeEventListener(AVAILABILITY_UPDATED_EVENT, refresh) } }, [accountId, userId, refreshKey]) // Responses are scoped to the mounted authenticated header/dialog; never show a previous account. if (!accountId || (value && !userId && value.userId !== accountId)) return null if (!value && !error) return loading ? : null return <> {open && value ? setOpen(false)} onSaved={next => { setValue(next); notifyAvailabilityUpdated() }} /> : null} } type StatusForm = { serviceStatus: string; start: string; end: string; startOccurrence: string; endOccurrence: string } export function OperatorStatusDialog({ value, admin = false, onClose, onSaved }: { value: OperatorAvailability; admin?: boolean; onClose: () => void; onSaved: (value: OperatorAvailability) => void }) { const t = useI18n() const { locale } = useAppLocale() const confirm = useConfirm() const { session } = useAuth() const capturedSession = useRef(session) const mounted = useRef(true) useEffect(() => { mounted.current = true; return () => { mounted.current = false } }, []) const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone const [saving, setSaving] = useState(false) const [failure, setFailure] = useState("") const savingRef = useRef(false) const form = useForm({ defaultValues: { serviceStatus: String(value.serviceStatus), start: toLocalDateTime(value.absenceStartAt), end: toLocalDateTime(value.absenceEndAt), startOccurrence: value.absenceStartAt ? new Date(value.absenceStartAt).toISOString() : "", endOccurrence: value.absenceEndAt ? new Date(value.absenceEndAt).toISOString() : "" } }) const { register, control, handleSubmit, watch, setError, formState: { errors, isDirty } } = form const vacation = Number(watch("serviceStatus")) === ServiceStatus.Vacation const startISO = selectedLocalDateTimeISO(watch("start"), watch("startOccurrence")), endISO = selectedLocalDateTimeISO(watch("end"), watch("endOccurrence")) async function close() { if (savingRef.current) return if (isDirty && !(await confirm({ title: t("supportAvailability.discardTitle"), description: t("supportAvailability.discardDescription"), confirmText: t("supportAvailability.discard") }))) return onClose() } async function save(fields: StatusForm) { if (savingRef.current || !isSameSession(capturedSession.current, readSession())) return const serviceStatus = Number(fields.serviceStatus) as ServiceStatus if (serviceStatus === ServiceStatus.Vacation) { const start = selectedLocalDateTimeISO(fields.start, fields.startOccurrence), end = selectedLocalDateTimeISO(fields.end, fields.endOccurrence) if (!start || !end || Date.parse(start) >= Date.parse(end)) { setError("end", { message: t("supportAvailability.invalidVacation") }); return } } savingRef.current = true; setSaving(true); setFailure("") try { const payload = { serviceStatus, ...(serviceStatus === ServiceStatus.Vacation ? { absenceStartAt: selectedLocalDateTimeISO(fields.start, fields.startOccurrence)!, absenceEndAt: selectedLocalDateTimeISO(fields.end, fields.endOccurrence)! } : {}) } const result = admin ? await updateAgentAvailability(value.userId, payload) : await updateMyAvailability(payload) if (!mounted.current || !isSameSession(capturedSession.current, readSession())) return onSaved(result); toast.success(t("supportAvailability.statusSaved")); onClose() } catch (error) { if (mounted.current) setFailure(error instanceof Error ? error.message : t("supportAvailability.saveFailed")) } finally { savingRef.current = false; if (mounted.current) setSaving(false) } } return { if (!next) void close() }} title={admin ? t("supportAvailability.operatorStatus", { name: value.displayName || String(value.userId) }) : t("supportAvailability.myStatus")} description={t("supportAvailability.presenceHelp")} size="md" footer={<>}>
{t(value.online ? "supportAvailability.online" : "supportAvailability.offline")}{availabilityStatusLabel(value.effectiveServiceStatus, t)}

{availabilityReasonLabel(value.reason, t)}

{value.nextOpeningAt ?

{t("supportAvailability.nextOpening", { time: formatAvailabilityTime(value.nextOpeningAt, value.calendar.effectiveTimezone, locale) })}

: null}
{t("supportAvailability.workingStatus")} } />

{t("supportAvailability.statusHelp")}

{vacation ? <>

{t("supportAvailability.vacationTimezone", { timezone: browserTimezone })}

{t("supportAvailability.vacationStart")} {t("supportAvailability.vacationEnd")}
{(["start", "end"] as const).map(name => localDateTimeCandidates(watch(name)).length > 1 ? {t("supportAvailability.dstOccurrence", { field: t(name === "start" ? "supportAvailability.vacationStart" : "supportAvailability.vacationEnd") })} ({ value, label: value }))} onChange={field.onChange} disabled={saving} ariaLabel={t("supportAvailability.chooseOccurrence")} placeholder={t("supportAvailability.chooseOccurrence")} />} /> : null)} {startISO && endISO ?

{t("supportAvailability.vacationExact", { start: startISO, end: endISO })}

: null}

{t("supportAvailability.vacationHelp", { status: availabilityStatusLabel(value.serviceStatus === ServiceStatus.Vacation ? value.returnServiceStatus ?? value.effectiveServiceStatus : value.effectiveServiceStatus, t) })}

: value.serviceStatus === ServiceStatus.Vacation ?

{t("supportAvailability.cancelVacationHelp")}

: null} {failure ?

{failure}

: null}
}