"use client" import { useEffect, useMemo, useRef, useState } from "react" import { ArrowLeftIcon, CheckIcon, Loader2Icon, XIcon } from "lucide-react" import { toast } from "sonner" import { OptionCombobox } from "@/components/option-combobox" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Textarea } from "@/components/ui/textarea" import { fetchAgentTeamsAll, generateAgentTeamScheduleBatch, previewAgentTeamScheduleBatch, type AdminAgentTeam, type AdminAgentTeamScheduleBatchPreview, type BatchAdminAgentTeamSchedulePayload, } from "@/lib/api/admin" import { useI18n } from "@/i18n/provider" import { cn } from "@/lib/utils" type BatchScheduleDialogProps = { open: boolean onOpenChange: (open: boolean) => void onSuccess: (created: number) => void | Promise } type TFunction = (key: string, values?: Record) => string const weekdayKeys = [ "weekdayMon", "weekdayTue", "weekdayWed", "weekdayThu", "weekdayFri", "weekdaySat", "weekdaySun", ] as const function getWeekdayOptions(t: TFunction) { return weekdayKeys.map((key, index) => ({ value: index + 1, label: t(`agentTeamSchedule.${key}`), })) } function todayDateValue() { const today = new Date() const year = today.getFullYear() const month = String(today.getMonth() + 1).padStart(2, "0") const day = String(today.getDate()).padStart(2, "0") return `${year}-${month}-${day}` } function defaultFormState() { const today = todayDateValue() return { selectedTeamIds: [] as number[], startDate: today, endDate: today, weekdays: [1, 2, 3, 4, 5], startTime: "09:00", endTime: "18:00", remark: "", } } type BatchFormState = ReturnType type DialogStep = "form" | "preview" function buildPayload(form: BatchFormState): BatchAdminAgentTeamSchedulePayload { return { teamIds: [...form.selectedTeamIds], startDate: form.startDate, endDate: form.endDate, weekdays: [...form.weekdays], startTime: form.startTime, endTime: form.endTime, remark: form.remark.trim(), } } function getWeekdayLabel(value: number, t: TFunction) { const weekdayOptions = getWeekdayOptions(t) return weekdayOptions.find((option) => option.value === value)?.label ?? String(value) } function validateForm(form: BatchFormState, t: TFunction) { const today = todayDateValue() if (form.selectedTeamIds.length === 0) { return t("agentTeamSchedule.selectAtLeastOneTeam") } if (!form.startDate || !form.endDate) { return t("agentTeamSchedule.selectDateRange") } if (form.startDate < today) { return t("agentTeamSchedule.startBeforeToday") } if (form.endDate < form.startDate) { return t("agentTeamSchedule.endBeforeStart") } if (form.weekdays.length === 0) { return t("agentTeamSchedule.selectAtLeastOneWeekday") } if (!form.startTime || !form.endTime) { return t("agentTeamSchedule.selectStartEndTime") } if (form.endTime <= form.startTime) { return t("agentTeamSchedule.endAfterStart") } return "" } export function BatchScheduleDialog({ open, onOpenChange, onSuccess, }: BatchScheduleDialogProps) { const t = useI18n() const [teams, setTeams] = useState([]) const [form, setForm] = useState(defaultFormState) const [step, setStep] = useState("form") const [preview, setPreview] = useState(null) const [previewPayload, setPreviewPayload] = useState(null) const [loadingTeams, setLoadingTeams] = useState(false) const [previewing, setPreviewing] = useState(false) const [submitting, setSubmitting] = useState(false) const openRef = useRef(open) const previewRequestIdRef = useRef(0) const busy = loadingTeams || previewing || submitting const weekdayOptions = useMemo(() => getWeekdayOptions(t), [t]) const teamOptions = useMemo( () => teams .filter((team) => !form.selectedTeamIds.includes(team.id)) .map((team) => ({ value: String(team.id), label: team.name })), [form.selectedTeamIds, teams] ) const selectedTeams = useMemo(() => { const teamMap = new Map(teams.map((team) => [team.id, team])) return form.selectedTeamIds.map((teamId) => teamMap.get(teamId)).filter(Boolean) as AdminAgentTeam[] }, [form.selectedTeamIds, teams]) const selectedWeekdays = useMemo( () => new Set(form.weekdays), [form.weekdays] ) const hasConflict = preview?.conflict === true useEffect(() => { openRef.current = open }, [open]) useEffect(() => { if (!open) { previewRequestIdRef.current += 1 setForm(defaultFormState()) setStep("form") setPreview(null) setPreviewPayload(null) setPreviewing(false) setSubmitting(false) return } let ignore = false async function loadTeams() { setLoadingTeams(true) try { const data = await fetchAgentTeamsAll() if (!ignore) { setTeams(data) } } catch (error) { if (!ignore) { toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadTeamsFailed")) } } finally { if (!ignore) { setLoadingTeams(false) } } } void loadTeams() return () => { ignore = true } }, [open, t]) function updateForm(values: Partial) { previewRequestIdRef.current += 1 setForm((current) => ({ ...current, ...values })) setPreview(null) setPreviewPayload(null) setPreviewing(false) setStep("form") } function handleOpenChange(nextOpen: boolean) { if (!nextOpen && busy) { return } onOpenChange(nextOpen) } function handleTeamSelect(value: string) { const teamId = Number(value) if (!Number.isFinite(teamId) || form.selectedTeamIds.includes(teamId)) { return } updateForm({ selectedTeamIds: [...form.selectedTeamIds, teamId] }) } function removeTeam(teamId: number) { updateForm({ selectedTeamIds: form.selectedTeamIds.filter((selectedTeamId) => selectedTeamId !== teamId), }) } function toggleWeekday(weekday: number) { const nextWeekdays = selectedWeekdays.has(weekday) ? form.weekdays.filter((value) => value !== weekday) : [...form.weekdays, weekday].sort((a, b) => a - b) updateForm({ weekdays: nextWeekdays }) } async function handlePreview() { const validationMessage = validateForm(form, t) if (validationMessage) { toast.error(validationMessage) return } const payload = buildPayload(form) const requestId = previewRequestIdRef.current + 1 previewRequestIdRef.current = requestId setPreviewing(true) try { const data = await previewAgentTeamScheduleBatch(payload) if (!openRef.current || previewRequestIdRef.current !== requestId) { return } setPreview(data) setPreviewPayload(payload) setStep("preview") } catch (error) { if (openRef.current && previewRequestIdRef.current === requestId) { toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.previewFailed")) } } finally { if (openRef.current && previewRequestIdRef.current === requestId) { setPreviewing(false) } } } async function handleSubmit() { if (!preview || !previewPayload) { toast.error(t("agentTeamSchedule.previewFirst")) return } if (preview.conflict) { toast.error(t("agentTeamSchedule.conflictCannotSubmit")) return } const payload = previewPayload setSubmitting(true) try { const data = await generateAgentTeamScheduleBatch(payload) toast.success(t("agentTeamSchedule.batchCreated", { count: data.created })) setPreview(null) setPreviewPayload(null) onOpenChange(false) try { await onSuccess(data.created) } catch (error) { toast.error(error instanceof Error ? t("agentTeamSchedule.batchRefreshFailed", { message: error.message }) : t("agentTeamSchedule.batchRefreshFailedFallback")) } } catch (error) { toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.generateFailed")) } finally { setSubmitting(false) } } return ( {t("agentTeamSchedule.batch")} {t("agentTeamSchedule.batchDescription")}
{step === "form" ? (
{selectedTeams.length > 0 ? (
{selectedTeams.map((team) => ( {team.name} ))}
) : (
{t("agentTeamSchedule.noSelectedTeams")}
)}
updateForm({ startDate: event.target.value })} />
updateForm({ endDate: event.target.value })} />
{weekdayOptions.map((option) => { const selected = selectedWeekdays.has(option.value) return ( ) })}
updateForm({ startTime: event.target.value })} />
updateForm({ endTime: event.target.value })} />