'use client' import { Check, UsersRound } from 'lucide-react' import { useCallback, useEffect, useState } from 'react' import { teamFunctionInputSchema, type FunctionBranch, type TeamFunction, type TeamFunctionInput, type TeamFunctionPayload } from '@/lib/team-function-contract' import DepartmentShell from './DepartmentShell' const toneStyles = { coral: { accent: '#e4473f', accentSoft: '#fff1ef', accentMuted: '#a62f2a', line: '#f2b3ae', node: '#fff9f8', }, navy: { accent: '#26364f', accentSoft: '#eef2f7', accentMuted: '#26364f', line: '#a9b5c7', node: '#fafcff', }, blue: { accent: '#2384e8', accentSoft: '#edf6ff', accentMuted: '#176fc1', line: '#a9cef6', node: '#fbfdff', }, teal: { accent: '#138d73', accentSoft: '#edf9f5', accentMuted: '#087052', line: '#a4d6c8', node: '#fbfffd', }, } as const type Tone = keyof typeof toneStyles const tones: Tone[] = ['coral', 'navy', 'blue', 'teal'] const fieldClass = 'min-h-11 w-full rounded-lg border border-[#dfe5ed] bg-white px-3 py-2 text-sm text-[#101828] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff] disabled:bg-[#f8fafc]' const buttonClass = 'min-h-11 rounded-lg px-3 text-sm font-semibold focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff] disabled:opacity-50' function leadLabel(team: TeamFunction) { return team.lead?.name ?? (team.leadStatus === 'invalid' ? '关联已失效' : '未设置') } function Branch({ branch, tone }: { branch: FunctionBranch; tone: Tone }) { const colors = toneStyles[tone] return (
{branch.label}
{branch.items.map((item, index) => (
))}
) } export default function TeamFunctionsDashboard() { const [payload, setPayload] = useState(null) const [selectedId, setSelectedId] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [notice, setNotice] = useState('') const [draft, setDraft] = useState(null) const [busy, setBusy] = useState(false) const load = useCallback(async () => { setLoading(true); setError('') try { const response = await fetch('/api/organization/team-functions', { cache: 'no-store' }) const data = await response.json() if (!response.ok) throw new Error(data.error || '团队职能加载失败。') setPayload(data) setSelectedId(current => data.teams.some((team: TeamFunction) => team.organizationUnitId === current) ? current : data.teams[0]?.organizationUnitId ?? '') } catch (failure) { setError(failure instanceof Error ? failure.message : '团队职能加载失败。') } finally { setLoading(false) } }, []) useEffect(() => { void load() }, [load]) const selectedTeam = payload?.teams.find(team => team.organizationUnitId === selectedId) const tone = tones[Math.max(0, payload?.teams.findIndex(team => team.organizationUnitId === selectedId) ?? 0) % tones.length] const colors = toneStyles[tone] const original = selectedTeam ? { organizationUnitId: selectedTeam.organizationUnitId, revision: selectedTeam.revision, leadEmployeeProfileId: selectedTeam.lead?.employeeProfileId ?? null, description: selectedTeam.description, branches: selectedTeam.branches } : null const dirty = !!draft && JSON.stringify(draft) !== JSON.stringify(original) useEffect(() => { if (!dirty) return const guard = (event: BeforeUnloadEvent) => { event.preventDefault(); event.returnValue = '' } const guardLink = (event: MouseEvent) => { const link = (event.target as Element)?.closest('a[href]') if (link && !window.confirm('有未保存的修改,放弃并离开?')) { event.preventDefault(); event.stopPropagation() } } window.addEventListener('beforeunload', guard) document.addEventListener('click', guardLink, true) return () => { window.removeEventListener('beforeunload', guard); document.removeEventListener('click', guardLink, true) } }, [dirty]) const save = async () => { const parsed = teamFunctionInputSchema.safeParse(draft && { ...draft, branches: draft.branches.map(branch => ({ ...branch, items: branch.items.map(item => item.trim()).filter(Boolean) })) }) if (!parsed.success) { setError('分组名称和条目不能为空,请检查内容长度。'); return } setBusy(true); setError(''); setNotice('') try { const response = await fetch('/api/organization/team-functions', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(parsed.data) }) const result = await response.json() if (!response.ok) throw new Error(result.error || '保存失败,请重试。') setPayload(current => current && { ...current, teams: current.teams.map(team => { if (team.organizationUnitId !== parsed.data.organizationUnitId) return team const lead = team.candidates.find(person => person.employeeProfileId === parsed.data.leadEmployeeProfileId) return { ...team, ...parsed.data, revision: result.revision, lead: lead ? { employeeProfileId: lead.employeeProfileId, name: lead.name } : null, leadStatus: lead ? 'active' : 'unset' } }) }) setDraft(null); setNotice('已保存') } catch (failure) { setError(failure instanceof Error ? failure.message : '保存失败,请重试。') } finally { setBusy(false) } } return (

团队职能

{error &&

{error}{!draft && }

} {notice &&

{notice}

} {loading ?

正在加载团队职能…

: !selectedTeam ?

{error ? '未能加载团队。' : '品牌中心暂无部门,请核对花名册同步。'}

:

{selectedTeam.name}

负责人:{leadLabel(selectedTeam)}

{payload!.canEdit && !draft && }
{draft ?
{ event.preventDefault(); void save() }}>