import React, { useEffect, useState } from "react"; import { Button } from "../ui/button"; import { Input } from "../ui/input"; import { Switch } from "../ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../ui/select"; import { toast } from "sonner"; import { ApiError } from "../../lib/apiClient"; import { getRoles } from "../../services/roleService"; import { updateTeamMembersBulk, type TeamMemberBulkUpdateItemVo } from "../../services/teamMemberService"; import type { RoleDto } from "../../types/role"; import type { TeamMemberDto } from "../../types/teamMember"; const ZERO = "00000000-0000-0000-0000-000000000000"; /** 列表/接口可能把 phone 等字段打成数字;统一成字符串再 trim,避免白屏 */ function trimStr(v: unknown): string { if (v == null) return ""; return String(v).trim(); } function isValidBulkId(id: string): boolean { const s = (id ?? "").trim(); if (!s) return false; return s.toLowerCase() !== ZERO; } function toPhoneNumber(v: string): number | null { const s = v.trim(); if (!s) return null; const num = Number(s.replace(/\D/g, "")) || 0; return num; } export type TeamMemberBulkEditPageProps = { seed: TeamMemberDto[]; onBack: () => void; onSaved: () => void; }; type RowState = { id: string; fullName: string; userName: string; password: string; email: string; phone: string; roleId: string; locationIdsCsv: string; state: boolean; }; function memberToRow(m: TeamMemberDto): RowState { const lids = Array.isArray(m.locationIds) ? m.locationIds : []; return { id: trimStr(m.id), fullName: trimStr(m.fullName), userName: trimStr(m.userName), password: "", email: trimStr(m.email), phone: trimStr(m.phone), roleId: trimStr(m.roleId), locationIdsCsv: lids.map((x) => trimStr(x)).filter(Boolean).join(","), state: m.state !== false, }; } function parseIdsCsv(s: string): string[] { return s .split(/[,;|\s]+/) .map((x) => x.trim()) .filter(Boolean); } export function TeamMemberBulkEditPage({ seed, onBack, onSaved }: TeamMemberBulkEditPageProps) { const [rows, setRows] = useState([]); const [roles, setRoles] = useState([]); const [saving, setSaving] = useState(false); useEffect(() => { let c = false; (async () => { try { const out: RoleDto[] = []; let page = 1; const size = 100; for (;;) { const res = await getRoles({ skipCount: page, maxResultCount: size }); out.push(...(res.items ?? [])); if (!res.items || res.items.length < size) break; page += 1; if (page > 50) break; } if (!c) setRoles(out); } catch { if (!c) setRoles([]); } })(); return () => { c = true; }; }, []); useEffect(() => { setRows(seed.map(memberToRow)); }, [seed]); const updateRow = (idx: number, patch: Partial) => { setRows((prev) => { const next = [...prev]; next[idx] = { ...next[idx], ...patch }; return next; }); }; const handleSave = async () => { const items: TeamMemberBulkUpdateItemVo[] = rows .filter((r) => isValidBulkId(r.id)) .map((r) => { const item: TeamMemberBulkUpdateItemVo = { id: r.id.trim(), fullName: r.fullName.trim(), userName: r.userName.trim(), email: r.email.trim() || null, phone: toPhoneNumber(r.phone), roleId: r.roleId.trim(), locationIds: parseIdsCsv(r.locationIdsCsv), state: r.state !== false, }; const pw = r.password.trim(); if (pw) item.password = pw; return item; }); if (items.length === 0) { toast.error("No valid rows", { description: "Select team members in the list first." }); return; } setSaving(true); try { const res = await updateTeamMembersBulk({ items }); toast.success("Bulk update finished", { description: `Success: ${res.successCount}, failed: ${res.failCount}`, }); onSaved(); onBack(); } catch (e) { const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "Save failed."; toast.error("Bulk save failed", { description: msg }); } finally { setSaving(false); } }; return (

Team member bulk edit

{rows.map((r, idx) => ( ))}
# Full name * User name * Password Email Phone Role * Location IDs Active
{idx + 1} updateRow(idx, { fullName: e.target.value })} /> updateRow(idx, { userName: e.target.value })} /> updateRow(idx, { password: e.target.value })} /> updateRow(idx, { email: e.target.value })} /> updateRow(idx, { phone: e.target.value })} /> updateRow(idx, { locationIdsCsv: e.target.value })} placeholder="guid1,guid2" /> updateRow(idx, { state: !!c })} />

Leave password empty to keep the current password.

Location IDs: comma-separated location primary keys.

); }