import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../ui/table"; import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../ui/dialog"; import { Label } from "../ui/label"; import { Switch } from "../ui/switch"; import { Badge } from "../ui/badge"; import { Plus, Edit, MoreHorizontal, X, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { skipCountForPage } from "../../lib/paginationQuery"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "../ui/pagination"; import { getLabelMultipleOptions, getLabelMultipleOption, createLabelMultipleOption, updateLabelMultipleOption, deleteLabelMultipleOption, } from "../../services/labelMultipleOptionService"; import { getLabels } from "../../services/labelService"; import { getLocations } from "../../services/locationService"; import { getGroups } from "../../services/groupService"; import { getPartners } from "../../services/partnerService"; import { buildSpecifiedLocationPayload, effectiveScopePartnerId, hydrateCategoryScopeFromLocationIds, regionNamesToGroupIds, scopePartnerValidationMessage, } from "../../lib/categoryScopeForm"; import { CategoryScopeFields } from "../shared/category-scope-fields"; import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth"; import type { LabelMultipleOptionDto, LabelMultipleOptionCreateInput, LabelMultipleOptionUpdateInput, } from "../../types/labelMultipleOption"; import type { LocationDto } from "../../types/location"; import type { GroupListItem } from "../../types/group"; import type { PartnerListItem } from "../../types/partner"; import { filterGroupsByCompany, filterLocationsByCompanyAndRegion, matchesCompanyByLocationIds, matchesRegionByLocationIds, resolveScopedLocationIdsForToolbar, } from "../../lib/labelingToolbarScope"; import { cn } from "../ui/utils"; function toDisplay(v: string | null | undefined): string { const s = (v ?? "").trim(); return s ? s : "None"; } /** 新增时由名称生成编码(界面不再手填 Option Code) */ function optionCodeFromName(name: string): string { const slug = name .trim() .toUpperCase() .replace(/[^A-Z0-9]+/g, "_") .replace(/^_+|_+$/g, "") .slice(0, 40); return slug ? `OPT_${slug}` : `OPT_${Date.now()}`; } /** 列表行单元格统一字体(与 Label Types / Label Categories 一致) */ const LIST_ROW_CELL = "text-sm font-normal font-sans text-gray-900"; const LIST_ROW_CELL_NOWRAP = `${LIST_ROW_CELL} whitespace-nowrap`; const LIST_STATUS_BADGE = "!text-sm !font-normal font-sans normal-case border-0"; function collectMultipleOptionIdsFromValue(value: unknown, out: Set): void { if (value == null) return; if (Array.isArray(value)) { for (const v of value) collectMultipleOptionIdsFromValue(v, out); return; } if (typeof value === "object") { for (const [k, v] of Object.entries(value as Record)) { if (k === "multipleOptionId" || k === "MultipleOptionId") { const id = String(v ?? "").trim(); if (id) out.add(id); } collectMultipleOptionIdsFromValue(v, out); } } } async function fetchMultipleOptionIdsInLocations( locationIds: string[], signal: AbortSignal, ): Promise> { const optionIds = new Set(); for (const lid of locationIds) { let page = 1; for (;;) { const res = await getLabels( { skipCount: skipCountForPage(page), maxResultCount: 500, locationId: lid, }, signal, ); for (const lbl of res.items ?? []) { collectMultipleOptionIdsFromValue(lbl.labelInfoJson, optionIds); } if ((res.items?.length ?? 0) < 500) break; page += 1; if (page > 50) break; } if (signal.aborted) break; } return optionIds; } function locationsForOptionScope(opt: LabelMultipleOptionDto, catalog: LocationDto[]): LocationDto[] { const idSet = new Set((opt.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean)); if (!idSet.size) return []; return catalog.filter((l) => idSet.has(l.id)); } function optionMatchesCompanyFilter( opt: LabelMultipleOptionDto, companyFilterId: string, catalog: LocationDto[], partners: PartnerListItem[], ): boolean { const lids = (opt.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean); return matchesCompanyByLocationIds(lids, companyFilterId, catalog, partners); } function optionMatchesRegionFilter( opt: LabelMultipleOptionDto, regionFilterId: string, catalog: LocationDto[], groups: GroupListItem[], ): boolean { const lids = (opt.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean); return matchesRegionByLocationIds(lids, regionFilterId, catalog, groups); } function optionMatchesLocationFilter(opt: LabelMultipleOptionDto, locationFilterId: string): boolean { if (locationFilterId === "all") return true; const lid = locationFilterId.trim(); if (!lid) return true; const lids = new Set((opt.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean)); if (!lids.size) return true; return lids.has(lid); } function labelInfoJsonReferencesOptionId(value: unknown, optionId: string): boolean { const ids = new Set(); collectMultipleOptionIdsFromValue(value, ids); return ids.has(optionId); } async function fetchLocationIdsForMultipleOption(optionId: string, signal?: AbortSignal): Promise { const lids: string[] = []; let page = 1; for (;;) { const res = await getLabels( { skipCount: skipCountForPage(page), maxResultCount: 500, }, signal, ); for (const lbl of res.items ?? []) { if (!labelInfoJsonReferencesOptionId(lbl.labelInfoJson, optionId)) continue; const id = (lbl.locationId ?? "").trim(); if (id) lids.push(id); } if ((res.items?.length ?? 0) < 500) break; page += 1; if (page > 50) break; } return [...new Set(lids)]; } async function fetchAllLabelMultipleOptionsMatching( keyword: string | undefined, state: boolean | undefined, signal: AbortSignal, ): Promise { const out: LabelMultipleOptionDto[] = []; let page = 1; const size = 500; for (;;) { const res = await getLabelMultipleOptions( { skipCount: skipCountForPage(page), maxResultCount: size, keyword, state, }, signal, ); const items = res.items ?? []; out.push(...items); if (items.length < size) break; page += 1; if (page > 200) break; } return out; } export function MultipleOptionsView() { const scopeAuth = useCategoryScopeAuth(); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [editingOption, setEditingOption] = useState(null); const [deletingOption, setDeletingOption] = useState(null); const [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); const [total, setTotal] = useState(0); const [refreshSeq, setRefreshSeq] = useState(0); const [actionsOpenForId, setActionsOpenForId] = useState(null); const [keyword, setKeyword] = useState(""); const [companyFilter, setCompanyFilter] = useState("all"); const [regionFilter, setRegionFilter] = useState("all"); const [locationFilter, setLocationFilter] = useState("all"); const [stateFilter, setStateFilter] = useState("all"); const [pageIndex, setPageIndex] = useState(1); const [pageSize, setPageSize] = useState(10); const [locationCatalog, setLocationCatalog] = useState([]); const [filterGroups, setFilterGroups] = useState([]); const [filterPartners, setFilterPartners] = useState([]); const abortRef = useRef(null); const keywordTimerRef = useRef(null); const [debouncedKeyword, setDebouncedKeyword] = useState(""); const companySelectOptions = useMemo( () => [...filterPartners] .filter((p) => (p.partnerName ?? "").trim()) .sort((a, b) => (a.partnerName ?? "").localeCompare(b.partnerName ?? "", undefined, { sensitivity: "base" }), ), [filterPartners], ); const regionSelectOptions = useMemo(() => { const list = filterGroupsByCompany(filterGroups, companyFilter); const m = new Map(); for (const g of list) { const id = (g.id ?? "").trim(); if (id && !m.has(id)) m.set(id, g); } return Array.from(m.values()).sort((a, b) => (a.groupName ?? "").localeCompare(b.groupName ?? "", undefined, { sensitivity: "base" }), ); }, [filterGroups, companyFilter]); const locationsForToolbarFilter = useMemo( () => filterLocationsByCompanyAndRegion( locationCatalog, filterPartners, companyFilter, regionFilter, filterGroups, ), [locationCatalog, filterPartners, companyFilter, regionFilter, filterGroups], ); useEffect(() => { setRegionFilter("all"); setLocationFilter("all"); }, [companyFilter]); useEffect(() => { setLocationFilter("all"); }, [regionFilter]); useEffect(() => { if (companyFilter === "all") return; if (!companySelectOptions.some((p) => p.id === companyFilter)) setCompanyFilter("all"); }, [companyFilter, companySelectOptions]); useEffect(() => { if (regionFilter === "all") return; if (!regionSelectOptions.some((g) => g.id === regionFilter)) setRegionFilter("all"); }, [companyFilter, regionSelectOptions, regionFilter]); useEffect(() => { if (locationFilter === "all") return; const allowed = new Set(locationsForToolbarFilter.map((l) => l.id)); if (!allowed.has(locationFilter)) setLocationFilter("all"); }, [locationsForToolbarFilter, locationFilter]); useEffect(() => { let cancelled = false; (async () => { try { const out: LocationDto[] = []; let locPage = 1; const locSize = 500; for (;;) { const res = await getLocations({ skipCount: skipCountForPage(locPage), maxResultCount: locSize, }); out.push(...(res.items ?? [])); if (!res.items || res.items.length < locSize) break; locPage += 1; if (locPage > 200) break; } const [grpRes, partnerRes] = await Promise.all([ getGroups({ skipCount: 1, maxResultCount: 500 }), getPartners({ skipCount: 1, maxResultCount: 500, state: true }), ]); if (cancelled) return; setLocationCatalog(out); setFilterGroups(grpRes.items ?? []); setFilterPartners(partnerRes.items ?? []); } catch { if (!cancelled) { setLocationCatalog([]); setFilterGroups([]); setFilterPartners([]); } } })(); return () => { cancelled = true; }; }, []); useEffect(() => { if (keywordTimerRef.current) window.clearTimeout(keywordTimerRef.current); keywordTimerRef.current = window.setTimeout(() => setDebouncedKeyword(keyword.trim()), 300); return () => { if (keywordTimerRef.current) window.clearTimeout(keywordTimerRef.current); }; }, [keyword]); const totalPages = Math.max(1, Math.ceil(total / pageSize)); useEffect(() => { setPageIndex(1); }, [debouncedKeyword, companyFilter, regionFilter, locationFilter, stateFilter, pageSize]); useEffect(() => { const run = async () => { abortRef.current?.abort(); const ac = new AbortController(); abortRef.current = ac; setLoading(true); try { const skipCount = skipCountForPage(pageIndex); const stateBool = stateFilter === "all" ? undefined : stateFilter === "true"; const kw = debouncedKeyword || undefined; const needsClientFilter = companyFilter !== "all" || regionFilter !== "all" || locationFilter !== "all"; const scopeLocationIds = resolveScopedLocationIdsForToolbar( companyFilter, regionFilter, locationFilter, locationCatalog, filterGroups, filterPartners, ); const canUseServerScope = companyFilter === "all" && (regionFilter !== "all" || locationFilter !== "all"); if (!needsClientFilter) { const res = await getLabelMultipleOptions( { skipCount, maxResultCount: pageSize, keyword: kw, state: stateBool, }, ac.signal, ); setOptions(res.items ?? []); setTotal(res.totalCount ?? 0); } else if (canUseServerScope) { const res = await getLabelMultipleOptions( { skipCount, maxResultCount: pageSize, keyword: kw, state: stateBool, groupId: regionFilter !== "all" ? regionFilter : undefined, locationId: locationFilter !== "all" ? locationFilter : undefined, }, ac.signal, ); setOptions(res.items ?? []); setTotal(res.totalCount ?? 0); } else { const scopedOptionIds = scopeLocationIds?.length ? await fetchMultipleOptionIdsInLocations(scopeLocationIds, ac.signal) : new Set(); const all = await fetchAllLabelMultipleOptionsMatching(kw, stateBool, ac.signal); const filtered = all.filter((opt) => { const hasScope = (opt.locationIds ?? []).some((x) => String(x).trim()); if (hasScope) { return ( optionMatchesCompanyFilter(opt, companyFilter, locationCatalog, filterPartners) && optionMatchesRegionFilter(opt, regionFilter, locationCatalog, filterGroups) && optionMatchesLocationFilter(opt, locationFilter) ); } return scopedOptionIds.has(opt.id); }); setTotal(filtered.length); const start = (pageIndex - 1) * pageSize; setOptions(filtered.slice(start, start + pageSize)); } } catch (e: any) { if (e?.name === "AbortError") return; toast.error("Failed to load multiple options.", { description: e?.message ? String(e.message) : "Please try again.", }); setOptions([]); setTotal(0); } finally { setLoading(false); } }; run(); return () => abortRef.current?.abort(); }, [ debouncedKeyword, companyFilter, regionFilter, locationFilter, stateFilter, pageIndex, pageSize, refreshSeq, locationCatalog, filterGroups, filterPartners, ]); const refreshList = () => setRefreshSeq((x) => x + 1); const openEdit = (opt: LabelMultipleOptionDto) => { setActionsOpenForId(null); setEditingOption(opt); setIsEditDialogOpen(true); }; const openDelete = (opt: LabelMultipleOptionDto) => { setActionsOpenForId(null); setDeletingOption(opt); setIsDeleteDialogOpen(true); }; return (
setKeyword(e.target.value)} style={{ height: 40, boxSizing: 'border-box' }} className="bg-white border border-gray-300 rounded-md w-40 shrink-0 placeholder:text-gray-500" /> {scopeAuth.requireCompanySelection ? ( ) : null}
Multiple Option Set Name Contents Status Sequence Order Last Edited Actions {loading ? ( Loading... ) : options.length === 0 ? ( No results. ) : ( options.map((item) => { const contentsText = item.optionValuesJson && item.optionValuesJson.length > 0 ? item.optionValuesJson.join("; ") : "None"; return ( {toDisplay(item.optionName)}
{contentsText}
{item.state !== false ? "active" : "inactive"} {item.orderNum != null && Number.isFinite(item.orderNum) ? item.orderNum : "—"} {toDisplay(item.lastEdited)} setActionsOpenForId(open ? item.id : null)} >
); }) )}
Showing {total === 0 ? 0 : (pageIndex - 1) * pageSize + 1}- {Math.min(pageIndex * pageSize, total)} of {total}
{ e.preventDefault(); setPageIndex((p) => Math.max(1, p - 1)); }} aria-disabled={pageIndex <= 1} className={pageIndex <= 1 ? "pointer-events-none opacity-50" : ""} /> e.preventDefault()} > Page {pageIndex} / {totalPages} { e.preventDefault(); setPageIndex((p) => Math.min(totalPages, p + 1)); }} aria-disabled={pageIndex >= totalPages} className={pageIndex >= totalPages ? "pointer-events-none opacity-50" : ""} />
{ setPageIndex(1); refreshList(); }} /> { setIsEditDialogOpen(open); if (!open) setEditingOption(null); }} onUpdated={refreshList} /> { setIsDeleteDialogOpen(open); if (!open) setDeletingOption(null); }} onDeleted={refreshList} />
); } function CreateMultipleOptionDialog({ open, locations, partners, groups, requireCompanySelection, fixedPartnerId, onOpenChange, onCreated, }: { open: boolean; locations: LocationDto[]; partners: PartnerListItem[]; groups: GroupListItem[]; requireCompanySelection: boolean; fixedPartnerId: string; onOpenChange: (open: boolean) => void; onCreated: () => void; }) { const [submitting, setSubmitting] = useState(false); const [selectedPartnerId, setSelectedPartnerId] = useState(""); const [selectedRegionNames, setSelectedRegionNames] = useState([]); const [selectedLocationIds, setSelectedLocationIds] = useState([]); const [form, setForm] = useState({ optionName: "", optionValuesJson: [], state: true, orderNum: null, }); const [newValue, setNewValue] = useState(""); const resetForm = () => { setSelectedPartnerId(""); setSelectedRegionNames([]); setSelectedLocationIds([]); setForm({ optionName: "", optionValuesJson: [], state: true, orderNum: null, }); setNewValue(""); }; useEffect(() => { if (!open) { resetForm(); } }, [open]); const addValue = () => { const trimmed = newValue.trim(); if (!trimmed) return; if (form.optionValuesJson.includes(trimmed)) { toast.error("Duplicate value", { description: "This value already exists.", }); return; } setForm((p) => ({ ...p, optionValuesJson: [...p.optionValuesJson, trimmed], })); setNewValue(""); }; const removeValue = (index: number) => { setForm((p) => ({ ...p, optionValuesJson: p.optionValuesJson.filter((_, i) => i !== index), })); }; const submit = async () => { if (!form.optionName.trim()) { toast.error("Validation failed", { description: "Option Name is required.", }); return; } if (form.optionValuesJson.length === 0) { toast.error("Validation failed", { description: "At least one option value is required.", }); return; } if (form.orderNum === null || form.orderNum === undefined || !Number.isFinite(form.orderNum)) { toast.error("Validation failed", { description: "Order is required." }); return; } const scopePartnerId = effectiveScopePartnerId( requireCompanySelection, selectedPartnerId, fixedPartnerId, ); const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId); if (partnerErr) { toast.error("Validation failed", { description: partnerErr }); return; } const locPayload = buildSpecifiedLocationPayload( locations, partners, groups, scopePartnerId, selectedRegionNames, selectedLocationIds, ); if (!locPayload.ok) { toast.error("Validation failed", { description: locPayload.message }); return; } const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId); setSubmitting(true); try { await createLabelMultipleOption({ ...form, optionCode: optionCodeFromName(form.optionName), groupIds, regionIds: groupIds, locationIds: locPayload.locationIds, }); toast.success("Multiple option created.", { description: "The multiple option has been created successfully.", }); onOpenChange(false); onCreated(); } catch (e: any) { toast.error("Failed to create multiple option.", { description: e?.message ? String(e.message) : "Please try again.", }); } finally { setSubmitting(false); } }; return ( Add New Multiple Option Enter the details for the new multiple option.
setForm((p) => ({ ...p, optionName: e.target.value }))} />
setNewValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addValue(); } }} />
{form.optionValuesJson.length > 0 && (
{form.optionValuesJson.map((val, idx) => ( {val} ))}
)}
setForm((p) => ({ ...p, orderNum: e.target.value ? Number(e.target.value) : null }))} />
Enabled
setForm((p) => ({ ...p, state: checked }))} />
); } function EditMultipleOptionDialog({ open, option, locations, partners, groups, requireCompanySelection, fixedPartnerId, onOpenChange, onUpdated, }: { open: boolean; option: LabelMultipleOptionDto | null; locations: LocationDto[]; partners: PartnerListItem[]; groups: GroupListItem[]; requireCompanySelection: boolean; fixedPartnerId: string; onOpenChange: (open: boolean) => void; onUpdated: () => void; }) { const [submitting, setSubmitting] = useState(false); const [loadingDetail, setLoadingDetail] = useState(false); const [selectedPartnerId, setSelectedPartnerId] = useState(""); const [selectedRegionNames, setSelectedRegionNames] = useState([]); const [selectedLocationIds, setSelectedLocationIds] = useState([]); const [form, setForm] = useState({ optionName: "", optionValuesJson: [], state: true, orderNum: null, }); const [newValue, setNewValue] = useState(""); useEffect(() => { if (!open || !option?.id) return; const ac = new AbortController(); setLoadingDetail(true); (async () => { try { const detail = await getLabelMultipleOption(option.id, ac.signal); if (ac.signal.aborted) return; setForm({ optionName: detail.optionName ?? option.optionName ?? "", optionValuesJson: detail.optionValuesJson ?? option.optionValuesJson ?? [], state: detail.state ?? option.state ?? true, orderNum: detail.orderNum ?? option.orderNum ?? null, }); setNewValue(""); const lids = (detail.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean); if (lids.length > 0) { const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups); setSelectedPartnerId(scope.partnerId); setSelectedRegionNames(scope.regionNames); setSelectedLocationIds(scope.locationIds); return; } const gids = [...(detail.groupIds ?? []), ...(detail.regionIds ?? [])] .map((x) => String(x).trim()) .filter(Boolean); if (gids.length > 0) { const matchedGroups = groups.filter((g) => gids.includes(g.id)); const pid = matchedGroups[0]?.partnerId ?? ""; const names = [ ...new Set(matchedGroups.map((g) => (g.groupName ?? "").trim()).filter(Boolean)), ]; setSelectedPartnerId(pid); setSelectedRegionNames(names); setSelectedLocationIds([]); return; } const fromLabels = await fetchLocationIdsForMultipleOption(option.id, ac.signal); if (ac.signal.aborted) return; if (fromLabels.length > 0) { const scope = hydrateCategoryScopeFromLocationIds(fromLabels, locations, partners, groups); setSelectedPartnerId(scope.partnerId); setSelectedRegionNames(scope.regionNames); setSelectedLocationIds(scope.locationIds); } else { setSelectedPartnerId(""); setSelectedRegionNames([]); setSelectedLocationIds([]); } } catch { if (ac.signal.aborted) return; setForm({ optionName: option.optionName ?? "", optionValuesJson: option.optionValuesJson ?? [], state: option.state ?? true, orderNum: option.orderNum ?? null, }); setSelectedPartnerId(""); setSelectedRegionNames([]); setSelectedLocationIds([]); setNewValue(""); } finally { if (!ac.signal.aborted) setLoadingDetail(false); } })(); return () => ac.abort(); }, [open, option, locations, partners, groups]); const addValue = () => { const trimmed = newValue.trim(); if (!trimmed) return; if (form.optionValuesJson.includes(trimmed)) { toast.error("Duplicate value", { description: "This value already exists.", }); return; } setForm((p) => ({ ...p, optionValuesJson: [...p.optionValuesJson, trimmed], })); setNewValue(""); }; const removeValue = (index: number) => { setForm((p) => ({ ...p, optionValuesJson: p.optionValuesJson.filter((_, i) => i !== index), })); }; const submit = async () => { if (!option?.id) return; if (!form.optionName.trim()) { toast.error("Validation failed", { description: "Option Name is required.", }); return; } if (form.optionValuesJson.length === 0) { toast.error("Validation failed", { description: "At least one option value is required.", }); return; } if (form.orderNum === null || form.orderNum === undefined || !Number.isFinite(form.orderNum)) { toast.error("Validation failed", { description: "Order is required." }); return; } const scopePartnerId = effectiveScopePartnerId( requireCompanySelection, selectedPartnerId, fixedPartnerId, ); const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId); if (partnerErr) { toast.error("Validation failed", { description: partnerErr }); return; } const locPayload = buildSpecifiedLocationPayload( locations, partners, groups, scopePartnerId, selectedRegionNames, selectedLocationIds, ); if (!locPayload.ok) { toast.error("Validation failed", { description: locPayload.message }); return; } const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId); setSubmitting(true); try { await updateLabelMultipleOption(option.id, { ...form, optionCode: option.optionCode ?? "", groupIds, regionIds: groupIds, locationIds: locPayload.locationIds, }); toast.success("Multiple option updated.", { description: "The multiple option has been updated successfully.", }); onOpenChange(false); onUpdated(); } catch (e: any) { toast.error("Failed to update multiple option.", { description: e?.message ? String(e.message) : "Please try again.", }); } finally { setSubmitting(false); } }; return ( Edit Multiple Option Update the multiple option details.
setForm((p) => ({ ...p, optionName: e.target.value }))} />
setNewValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addValue(); } }} />
{form.optionValuesJson.length > 0 && (
{form.optionValuesJson.map((val, idx) => ( {val} ))}
)}
setForm((p) => ({ ...p, orderNum: e.target.value ? Number(e.target.value) : null }))} />
Enabled
setForm((p) => ({ ...p, state: checked }))} />
{loadingDetail ? (

Loading company, region and location…

) : ( )}
); } function DeleteMultipleOptionDialog({ open, option, onOpenChange, onDeleted, }: { open: boolean; option: LabelMultipleOptionDto | null; onOpenChange: (open: boolean) => void; onDeleted: () => void; }) { const [submitting, setSubmitting] = useState(false); const name = useMemo(() => { const n = (option?.optionName ?? "").trim(); return n || option?.optionCode || "this option"; }, [option]); const submit = async () => { if (!option?.id) return; setSubmitting(true); try { await deleteLabelMultipleOption(option.id); toast.success("Multiple option deleted.", { description: "The multiple option has been removed successfully.", }); onOpenChange(false); onDeleted(); } catch (e: any) { toast.error("Failed to delete multiple option.", { description: e?.message ? String(e.message) : "Please try again.", }); } finally { setSubmitting(false); } }; return ( Delete Multiple Option This action cannot be undone.
Are you sure you want to delete {name}?
); }