// Modified in this independent fork for support availability; see docs/hardening/README.md. "use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckIcon, ChevronsUpDownIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Controller, type Resolver, useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod/v4"; import { ImageInput } from "@/components/image-input"; import { OptionCombobox } from "@/components/option-combobox"; import { ProjectDialog } from "@/components/project-dialog"; import { Button } from "@/components/ui/button"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { EditDialog as TeamEditDialog } from "./team-edit"; import { createAgentTeam, fetchAgentProfile, fetchAgentTeamsAll, fetchUsersAll, type AdminAgentProfile, type AdminAgentTeam, type AdminUser, type CreateAdminAgentProfilePayload, type CreateAdminAgentTeamPayload, } from "@/lib/api/admin"; import { useI18n } from "@/i18n/provider"; import { availabilityStatusOptions } from "@/lib/support-availability-i18n"; import { ServiceStatus } from "@/lib/generated/enums"; type TFunction = (key: string, values?: Record) => string; type AgentEditDialogProps = { open: boolean; saving: boolean; itemId: number | null; defaultTeamId: number | null; onOpenChange: (open: boolean) => void; onSubmit: (payload: CreateAdminAgentProfilePayload) => Promise; }; const emptyForm: EditForm = { userId: "", teamId: "", agentCode: "", displayName: "", avatar: "", serviceStatus: String(ServiceStatus.Idle) as "0" | "1" | "2" | "3" | "4", maxConcurrentCount: "0", priorityLevel: "0", autoAssignEnabled: true, receiveOfflineMessage: false, remark: "", }; type EditForm = { userId: string; teamId: string; agentCode: string; displayName: string; avatar: string; serviceStatus: "0" | "1" | "2" | "3" | "4"; maxConcurrentCount: string; priorityLevel: string; autoAssignEnabled: boolean; receiveOfflineMessage: boolean; remark: string; }; function createEditFormSchema(t: TFunction) { return z.object({ userId: z.string().trim().min(1, t("agentProfile.userRequired")), teamId: z.string().trim().min(1, t("agentProfile.teamRequired")), agentCode: z.string().trim().min(1, t("agentProfile.agentCodeRequired")), displayName: z.string().trim().min(1, t("agentProfile.displayNameRequired")), avatar: z.string().trim(), serviceStatus: z.enum(["0", "1", "2", "3", "4"], { message: t("agentProfile.statusRequired"), }), maxConcurrentCount: z .string() .trim() .regex(/^\d+$/, t("agentProfile.maxConcurrentInvalid")), priorityLevel: z .string() .trim() .regex(/^-?\d+$/, t("agentProfile.priorityInvalid")), autoAssignEnabled: z.boolean(), receiveOfflineMessage: z.boolean(), remark: z.string().trim(), }); } function getServiceStatusOptions(t: TFunction) { return availabilityStatusOptions(t, false); } function buildForm(item: AdminAgentProfile | null): EditForm { if (!item) { return emptyForm; } return { userId: String(item.userId), teamId: String(item.teamId), agentCode: item.agentCode, displayName: item.displayName, avatar: item.avatar || "", serviceStatus: String(item.serviceStatus) as EditForm["serviceStatus"], maxConcurrentCount: String(item.maxConcurrentCount), priorityLevel: String(item.priorityLevel), autoAssignEnabled: item.autoAssignEnabled, receiveOfflineMessage: item.receiveOfflineMessage, remark: item.remark || "", }; } function buildFormWithDefaultTeam( item: AdminAgentProfile | null, defaultTeamId: number | null, ): EditForm { const form = buildForm(item); if (!item && defaultTeamId) { return { ...form, teamId: String(defaultTeamId), }; } return form; } function buildPayload(form: EditForm): CreateAdminAgentProfilePayload { return { userId: Number(form.userId), teamId: Number(form.teamId), agentCode: form.agentCode.trim(), displayName: form.displayName.trim(), avatar: form.avatar.trim(), serviceStatus: Number(form.serviceStatus), maxConcurrentCount: Number(form.maxConcurrentCount), priorityLevel: Number(form.priorityLevel), autoAssignEnabled: form.autoAssignEnabled, receiveOfflineMessage: form.receiveOfflineMessage, remark: form.remark.trim(), }; } export function EditDialog({ open, saving, itemId, defaultTeamId, onOpenChange, onSubmit, }: AgentEditDialogProps) { if (!open) { return null; } return ( ); } type AgentEditDialogBodyProps = { open: boolean; saving: boolean; itemId: number | null; defaultTeamId: number | null; onOpenChange: (open: boolean) => void; onSubmit: (payload: CreateAdminAgentProfilePayload) => Promise; }; function AgentEditDialogBody({ open, saving, itemId, defaultTeamId, onOpenChange, onSubmit, }: AgentEditDialogBodyProps) { const t = useI18n(); const [users, setUsers] = useState([]); const [teams, setTeams] = useState([]); const [userSelectOpen, setUserSelectOpen] = useState(false); const [teamSelectOpen, setTeamSelectOpen] = useState(false); const [createTeamOpen, setCreateTeamOpen] = useState(false); const [createTeamSaving, setCreateTeamSaving] = useState(false); const [loading, setLoading] = useState(false); const handleCreateTeamSubmit = async (payload: CreateAdminAgentTeamPayload) => { setCreateTeamSaving(true); try { const newTeam = await createAgentTeam(payload); toast.success(t("agentProfile.teamCreated", { name: payload.name })); setTeams((prev) => [...prev, newTeam]); form.setValue("teamId", String(newTeam.id), { shouldValidate: true }); setCreateTeamOpen(false); } catch (error) { toast.error( error instanceof Error ? error.message : t("agentProfile.teamSaveFailed"), ); } finally { setCreateTeamSaving(false); } }; const userOptions = users.map((user) => ({ value: String(user.id), label: `${user.nickname || user.username} (${user.username})`, })); const teamOptions = teams.map((team) => ({ value: String(team.id), label: team.name, })); const serviceStatusOptions = useMemo(() => getServiceStatusOptions(t), [t]); const editFormSchema = useMemo(() => createEditFormSchema(t), [t]); const editFormResolver = useMemo( () => zodResolver(editFormSchema) as Resolver, [editFormSchema], ); const form = useForm({ resolver: editFormResolver, defaultValues: buildFormWithDefaultTeam(null, defaultTeamId), }); const { control, handleSubmit, reset, register, formState: { errors }, } = form; const loadOptions = useCallback(async () => { try { const [usersData, teamsData] = await Promise.all([ fetchUsersAll(), fetchAgentTeamsAll(), ]); setUsers(usersData); setTeams(teamsData); if (!itemId && teamsData.length > 0) { const current = form.getValues("teamId"); if (!current) { form.setValue("teamId", String(defaultTeamId || teamsData[0].id), { shouldValidate: true }); } } } catch (error) { toast.error(error instanceof Error ? error.message : t("agentProfile.loadOptionsFailed")); } }, [defaultTeamId, form, itemId, t]); useEffect(() => { async function loadDetail() { if (!itemId) { reset(buildFormWithDefaultTeam(null, defaultTeamId)); return; } setLoading(true); try { const data = await fetchAgentProfile(itemId); reset(buildForm(data)); } catch (error) { toast.error(error instanceof Error ? error.message : t("agentProfile.loadDetailFailed")); } finally { setLoading(false); } } void loadDetail(); }, [itemId, defaultTeamId, reset, t]); useEffect(() => { if (open) { void loadOptions(); } }, [loadOptions, open]); async function onFormSubmit(values: EditForm) { await onSubmit(buildPayload(values)); } const formId = "agent-edit-form"; return ( <> } > {loading ? (
{t("agentProfile.loading")}
) : (
{t("agentProfile.linkedUser")} ( } > {userOptions.find( (option) => option.value === field.value, )?.label ?? t("agentProfile.selectUser")} {t("agentProfile.emptyUser")} {userOptions.map((option) => ( { field.onChange(option.value); setUserSelectOpen(false); }} > {option.label} ))} )} />
{t("agentProfile.linkedTeam")}
( } > {teamOptions.find( (option) => option.value === field.value, )?.label ?? t("agentProfile.selectTeam")} {t("agentProfile.emptyTeam")} {teamOptions.map((option) => ( { field.onChange(option.value); setTeamSelectOpen(false); }} > {option.label} ))} )} /> {teams.length === 0 ? (
{t("agentProfile.noTeamsAvailable")}
) : null}
{t("agentProfile.displayName")} {t("agentProfile.agentCodeLabel")}
{!itemId ? {t("agentProfile.serviceStatus")} ( )} /> :

{t("supportAvailability.statusSeparate")}

} {t("agentProfile.avatar")} ( )} />
{t("agentProfile.maxConcurrent")} {t("agentProfile.priority")}
{t("agentProfile.autoAssignEnabled")} ( )} /> {t("agentProfile.receiveOfflineMessage")} ( )} />

{t("supportAvailability.offlineDeprecated")}

{t("agentProfile.remark")}