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 type { LabelMultipleOptionDto, LabelMultipleOptionCreateInput, LabelMultipleOptionUpdateInput, } from "../../types/labelMultipleOption"; import type { LocationDto } from "../../types/location"; import type { GroupListItem } from "../../types/group"; import { cn } from "../ui/utils"; import { SearchableMultiSelect } from "../ui/searchable-multi-select"; function toDisplay(v: string | null | undefined): string { const s = (v ?? "").trim(); return s ? s : "None"; } /** 列表行单元格统一字体(与 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 resolveScopedLocationIds( regionFilter: string, locationFilter: string, locationCatalog: LocationDto[], filterGroups: GroupListItem[], ): string[] | null { if (regionFilter === "all" && locationFilter === "all") return null; if (locationFilter !== "all") { const lid = locationFilter.trim(); return lid ? [lid] : []; } const g = filterGroups.find((x) => x.id === regionFilter); if (!g) return []; const gn = (g.groupName ?? "").trim(); const pn = (g.partnerName ?? "").trim(); return locationCatalog .filter((l) => (l.groupName ?? "").trim() === gn && (l.partner ?? "").trim() === pn) .map((l) => l.id) .filter(Boolean); } 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 optionMatchesRegionFilter( opt: LabelMultipleOptionDto, regionFilterId: string, catalog: LocationDto[], groups: GroupListItem[], ): boolean { if (regionFilterId === "all") return true; const g = groups.find((x) => x.id === regionFilterId); if (!g) return true; const gn = (g.groupName ?? "").trim(); const pn = (g.partnerName ?? "").trim(); const matched = locationsForOptionScope(opt, catalog); if (!matched.length) return true; return matched.some( (l) => (l.groupName ?? "").trim() === gn && (l.partner ?? "").trim() === pn, ); } 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); } /** 指定门店模式:校验区域与门店多选(与 Label Types 一致) */ function buildSpecifiedLocationPayload( locations: LocationDto[], selectedRegionNames: string[], selectedLocationIds: string[], ): { ok: true; locationIds: string[] } | { ok: false; message: string } { const regionNamesClean = selectedRegionNames.map((x) => x.trim()).filter(Boolean); if (regionNamesClean.length === 0) { return { ok: false, message: "Please select at least one region." }; } const locationIdsParsed = [ ...new Set( selectedLocationIds .map((x) => String(x).trim()) .filter((id) => locations.some((l) => l.id === id)), ), ]; if (locationIdsParsed.length === 0) { return { ok: false, message: "Please select at least one location." }; } const locOutsideRegion = locationIdsParsed.find((id) => { const loc = locations.find((l) => l.id === id); if (!loc) return true; const g = (loc.groupName ?? "").trim(); return !g || !regionNamesClean.includes(g); }); if (locOutsideRegion) { return { ok: false, message: "Each selected location must belong to one of the selected regions.", }; } return { ok: true, locationIds: locationIdsParsed }; } function regionNamesToGroupIds(names: string[], groups: GroupListItem[]): string[] { const out: string[] = []; const seen = new Set(); for (const name of names.map((x) => x.trim()).filter(Boolean)) { for (const g of groups) { const gn = (g.groupName ?? "").trim(); if (gn === name && g.id && !seen.has(g.id)) { seen.add(g.id); out.push(g.id); } } } return out; } function hydrateScopeFromLocationIds( locationIds: string[], locations: LocationDto[], ): { regionNames: string[]; locationIds: string[] } { const lids = [...new Set(locationIds.map((x) => String(x).trim()).filter(Boolean))]; const regionNames = [ ...new Set( lids .map((id) => (locations.find((l) => l.id === id)?.groupName ?? "").trim()) .filter(Boolean), ), ]; return { regionNames, locationIds: lids }; } 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)]; } /** Region / Location 多选表单项(Create / Edit 共用) */ function MultipleOptionScopeFields({ locations, selectedRegionNames, selectedLocationIds, onRegionChange, onLocationChange, }: { locations: LocationDto[]; selectedRegionNames: string[]; selectedLocationIds: string[]; onRegionChange: (names: string[]) => void; onLocationChange: (ids: string[]) => void; }) { const regionOptions = useMemo(() => { const s = new Set(); for (const loc of locations) { const gn = (loc.groupName ?? "").trim(); if (gn) s.add(gn); } return [...s] .sort((a, b) => a.localeCompare(b)) .map((name) => ({ value: name, label: name })); }, [locations]); const locationOptionsForPicker = useMemo( () => locations .filter((loc) => { if (selectedRegionNames.length === 0) return true; const g = (loc.groupName ?? "").trim(); return !!g && selectedRegionNames.includes(g); }) .map((loc) => { const code = (loc.locationCode ?? "").trim(); const name = (loc.locationName ?? "").trim(); const label = code && name ? `${code} - ${name}` : name || code || loc.id; return { value: loc.id, label }; }) .filter((o) => o.value), [locations, selectedRegionNames], ); const onRegionMultiChange = useCallback( (nextRegions: string[]) => { const names = nextRegions.map((x) => x.trim()).filter(Boolean); onRegionChange(nextRegions); onLocationChange( selectedLocationIds.filter((id) => { const loc = locations.find((l) => l.id === id); if (!loc) return false; if (names.length === 0) return true; const g = (loc.groupName ?? "").trim(); return !!g && names.includes(g); }), ); }, [locations, onRegionChange, onLocationChange, selectedLocationIds], ); return (

Required. Narrows the location list.

Required. ALL selects every location in the filtered list (within selected regions).

); } 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 [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 [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 abortRef = useRef(null); const keywordTimerRef = useRef(null); const [debouncedKeyword, setDebouncedKeyword] = useState(""); const regionSelectOptions = useMemo(() => { const m = new Map(); for (const g of filterGroups) { 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]); const locationsForToolbarFilter = useMemo(() => { if (regionFilter === "all") return locationCatalog; const g = filterGroups.find((x) => x.id === regionFilter); if (!g) return locationCatalog; const gn = (g.groupName ?? "").trim(); const pn = (g.partnerName ?? "").trim(); return locationCatalog.filter( (l) => (l.groupName ?? "").trim() === gn && (l.partner ?? "").trim() === pn, ); }, [locationCatalog, filterGroups, regionFilter]); useEffect(() => { setLocationFilter("all"); }, [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 = await getGroups({ skipCount: 1, maxResultCount: 500 }); if (cancelled) return; setLocationCatalog(out); setFilterGroups(grpRes.items ?? []); } catch { if (!cancelled) { setLocationCatalog([]); setFilterGroups([]); } } })(); 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, 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 = regionFilter !== "all" || locationFilter !== "all"; const scopeLocationIds = resolveScopedLocationIds( regionFilter, locationFilter, locationCatalog, filterGroups, ); if (!needsClientFilter) { 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 ( 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, regionFilter, locationFilter, stateFilter, pageIndex, pageSize, refreshSeq, locationCatalog, filterGroups, ]); 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" />
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, groups, onOpenChange, onCreated, }: { open: boolean; locations: LocationDto[]; groups: GroupListItem[]; onOpenChange: (open: boolean) => void; onCreated: () => void; }) { const [submitting, setSubmitting] = useState(false); const [selectedRegionNames, setSelectedRegionNames] = useState([]); const [selectedLocationIds, setSelectedLocationIds] = useState([]); const [form, setForm] = useState({ optionCode: "", optionName: "", optionValuesJson: [], state: true, orderNum: null, }); const [newValue, setNewValue] = useState(""); const resetForm = () => { setSelectedRegionNames([]); setSelectedLocationIds([]); setForm({ optionCode: "", 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 locPayload = buildSpecifiedLocationPayload( locations, selectedRegionNames, selectedLocationIds, ); if (!locPayload.ok) { toast.error("Validation failed", { description: locPayload.message }); return; } const groupIds = regionNamesToGroupIds(selectedRegionNames, groups); setSubmitting(true); try { await createLabelMultipleOption({ ...form, 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, optionCode: e.target.value }))} />
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, groups, onOpenChange, onUpdated, }: { open: boolean; option: LabelMultipleOptionDto | null; locations: LocationDto[]; groups: GroupListItem[]; onOpenChange: (open: boolean) => void; onUpdated: () => void; }) { const [submitting, setSubmitting] = useState(false); const [loadingDetail, setLoadingDetail] = useState(false); const [selectedRegionNames, setSelectedRegionNames] = useState([]); const [selectedLocationIds, setSelectedLocationIds] = useState([]); const [form, setForm] = useState({ optionCode: "", 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({ optionCode: detail.optionCode ?? option.optionCode ?? "", 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 = hydrateScopeFromLocationIds(lids, locations); 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 names = [ ...new Set( gids .map((id) => (groups.find((g) => g.id === id)?.groupName ?? "").trim()) .filter(Boolean), ), ]; setSelectedRegionNames(names); setSelectedLocationIds([]); return; } const fromLabels = await fetchLocationIdsForMultipleOption(option.id, ac.signal); if (ac.signal.aborted) return; if (fromLabels.length > 0) { const scope = hydrateScopeFromLocationIds(fromLabels, locations); setSelectedRegionNames(scope.regionNames); setSelectedLocationIds(scope.locationIds); } else { setSelectedRegionNames([]); setSelectedLocationIds([]); } } catch { if (ac.signal.aborted) return; setForm({ optionCode: option.optionCode ?? "", optionName: option.optionName ?? "", optionValuesJson: option.optionValuesJson ?? [], state: option.state ?? true, orderNum: option.orderNum ?? null, }); setSelectedRegionNames([]); setSelectedLocationIds([]); setNewValue(""); } finally { if (!ac.signal.aborted) setLoadingDetail(false); } })(); return () => ac.abort(); }, [open, option, locations, 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 locPayload = buildSpecifiedLocationPayload( locations, selectedRegionNames, selectedLocationIds, ); if (!locPayload.ok) { toast.error("Validation failed", { description: locPayload.message }); return; } const groupIds = regionNamesToGroupIds(selectedRegionNames, groups); setSubmitting(true); try { await updateLabelMultipleOption(option.id, { ...form, 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, optionCode: e.target.value }))} />
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 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}?
); }