149 lines
9.0 KiB
TypeScript
149 lines
9.0 KiB
TypeScript
import type {
|
||
OngoingDingWorkflowActiveTask,
|
||
OngoingDingWorkflowListItem,
|
||
} from '@/lib/ding-workflow-sync'
|
||
|
||
type WorkflowDetailTableProps<T extends OngoingDingWorkflowListItem> = {
|
||
items: T[]
|
||
tableLabel: string
|
||
mobileListLabel?: string
|
||
tasksForItem?: (item: T) => OngoingDingWorkflowActiveTask[]
|
||
}
|
||
|
||
function formatDate(value: string | null) {
|
||
if (!value) return '—'
|
||
return new Intl.DateTimeFormat('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
hour12: false,
|
||
timeZone: 'Asia/Shanghai',
|
||
}).format(new Date(value))
|
||
}
|
||
|
||
export function workflowNodeNames(
|
||
project: OngoingDingWorkflowListItem,
|
||
tasks = project.activeTasks,
|
||
) {
|
||
const names = Array.from(new Set(tasks.map((task) => task.nodeName).filter(Boolean)))
|
||
if (names.length > 0) return names
|
||
return project.status.trim().toUpperCase() === 'COMPLETED' ? ['流程已完结'] : ['待进入处理节点']
|
||
}
|
||
|
||
export function workflowTaskOwnerNames(tasks: OngoingDingWorkflowActiveTask[]) {
|
||
const names = Array.from(new Set(tasks.map((task) => task.assigneeName).filter(Boolean)))
|
||
return names.length > 0 ? names : ['—']
|
||
}
|
||
|
||
export function displayWorkflowValue(value: string | null | undefined) {
|
||
return value?.trim() || '—'
|
||
}
|
||
|
||
function MilestoneTime({ label, value }: { label: string; value: string | null }) {
|
||
return (
|
||
<div className="min-w-[11rem]">
|
||
<p className="font-semibold tabular-nums text-slate-800">{formatDate(value)}</p>
|
||
<p className="mt-1 text-xs leading-5 text-slate-500">{label}</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function WorkflowDetailTable<T extends OngoingDingWorkflowListItem>({
|
||
items,
|
||
tableLabel,
|
||
mobileListLabel = tableLabel,
|
||
tasksForItem = (item) => item.activeTasks,
|
||
}: WorkflowDetailTableProps<T>) {
|
||
return (
|
||
<>
|
||
<div
|
||
className="hidden max-w-full overflow-x-auto rounded-xl border border-slate-200 bg-white outline-none focus-visible:ring-2 focus-visible:ring-[#5a9ce6] xl:block"
|
||
role="region"
|
||
aria-label={tableLabel}
|
||
tabIndex={0}
|
||
>
|
||
<table className="w-full min-w-[2480px] table-fixed text-left text-sm">
|
||
<caption className="sr-only">{tableLabel},包含审批表单字段、当前节点与流程里程碑时间</caption>
|
||
<thead className="sticky top-0 z-20 bg-slate-50 text-xs font-bold text-slate-500">
|
||
<tr>
|
||
<th scope="col" className="sticky left-0 z-30 w-[320px] border-r border-slate-200 bg-slate-50 px-5 py-3.5">审批单标题</th>
|
||
<th scope="col" className="w-[190px] px-4 py-3.5">审批编号</th>
|
||
<th scope="col" className="w-[120px] px-4 py-3.5">需求人</th>
|
||
<th scope="col" className="w-[260px] px-4 py-3.5">需求部门</th>
|
||
<th scope="col" className="w-[140px] px-4 py-3.5">平台</th>
|
||
<th scope="col" className="w-[160px] px-4 py-3.5">品牌</th>
|
||
<th scope="col" className="w-[160px] px-4 py-3.5">类目</th>
|
||
<th scope="col" className="w-[160px] px-4 py-3.5">SPU</th>
|
||
<th scope="col" className="w-[120px] px-4 py-3.5">需求类型</th>
|
||
<th scope="col" className="w-[190px] px-4 py-3.5">标准出图最长时效限制</th>
|
||
<th scope="col" className="w-[220px] px-4 py-3.5">分配任务开始时间</th>
|
||
<th scope="col" className="w-[180px] px-4 py-3.5">当前节点名称</th>
|
||
<th scope="col" className="w-[160px] px-4 py-3.5">当前节点负责人</th>
|
||
<th scope="col" className="w-[220px] px-5 py-3.5">审核结束时间</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-200">
|
||
{items.map((project) => {
|
||
const tasks = tasksForItem(project)
|
||
return (
|
||
<tr key={project.id} className="group align-top hover:bg-slate-50">
|
||
<td className="sticky left-0 z-10 border-r border-slate-200 bg-white px-5 py-4 group-hover:bg-slate-50">
|
||
<p className="font-bold text-slate-950">{project.title}</p>
|
||
<p className="mt-1 text-xs text-slate-500">{project.projectType}</p>
|
||
</td>
|
||
<td className="break-all px-4 py-4 text-slate-600">{project.businessId || project.id}</td>
|
||
<td className="px-4 py-4 font-bold text-slate-800">{displayWorkflowValue(project.initiatorName)}</td>
|
||
<td className="px-4 py-4 leading-6 text-slate-700">{displayWorkflowValue(project.initiatorDepartment)}</td>
|
||
<td className="px-4 py-4 text-slate-700">{displayWorkflowValue(project.formFields.platform)}</td>
|
||
<td className="px-4 py-4 font-bold text-slate-800">{displayWorkflowValue(project.formFields.brand || project.brand)}</td>
|
||
<td className="px-4 py-4 text-slate-700">{displayWorkflowValue(project.formFields.category)}</td>
|
||
<td className="break-words px-4 py-4 text-slate-700">{displayWorkflowValue(project.formFields.spu)}</td>
|
||
<td className="px-4 py-4 font-semibold text-slate-800">{displayWorkflowValue(project.formFields.demandType)}</td>
|
||
<td className="px-4 py-4 text-slate-700">{displayWorkflowValue(project.formFields.standardOutputDeadline)}</td>
|
||
<td className="px-4 py-4"><MilestoneTime label={project.assignmentNodeLabel} value={project.assignmentStartedAt} /></td>
|
||
<td className="px-4 py-4 leading-6 text-slate-700">{workflowNodeNames(project, tasks).join(' / ')}</td>
|
||
<td className="px-4 py-4 font-semibold leading-6 text-slate-800">{workflowTaskOwnerNames(tasks).join(' / ')}</td>
|
||
<td className="px-5 py-4"><MilestoneTime label={project.reviewNodeLabel} value={project.reviewFinishedAt} /></td>
|
||
</tr>
|
||
)
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<ul className="grid gap-3 xl:hidden" aria-label={mobileListLabel}>
|
||
{items.map((project) => {
|
||
const tasks = tasksForItem(project)
|
||
return (
|
||
<li key={project.id} className="rounded-xl border border-slate-200 bg-white p-5">
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="min-w-0">
|
||
<h4 className="font-bold text-slate-950">{project.title}</h4>
|
||
<p className="mt-1 break-all text-xs text-slate-500">审批编号 {project.businessId || project.id}</p>
|
||
</div>
|
||
<span className="shrink-0 rounded-lg bg-blue-50 px-2.5 py-1 text-xs font-bold text-blue-700">{project.projectType}</span>
|
||
</div>
|
||
<dl className="mt-4 grid gap-x-6 gap-y-4 border-t border-slate-100 pt-4 text-sm sm:grid-cols-2 lg:grid-cols-3">
|
||
<div><dt className="text-xs text-slate-500">需求人</dt><dd className="mt-1 font-bold text-slate-800">{displayWorkflowValue(project.initiatorName)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">需求部门</dt><dd className="mt-1 font-bold leading-6 text-slate-800">{displayWorkflowValue(project.initiatorDepartment)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">平台</dt><dd className="mt-1 text-slate-700">{displayWorkflowValue(project.formFields.platform)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">品牌</dt><dd className="mt-1 font-bold text-slate-800">{displayWorkflowValue(project.formFields.brand || project.brand)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">类目</dt><dd className="mt-1 text-slate-700">{displayWorkflowValue(project.formFields.category)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">SPU</dt><dd className="mt-1 break-words text-slate-700">{displayWorkflowValue(project.formFields.spu)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">需求类型</dt><dd className="mt-1 font-bold text-slate-800">{displayWorkflowValue(project.formFields.demandType)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">标准出图最长时效限制</dt><dd className="mt-1 text-slate-700">{displayWorkflowValue(project.formFields.standardOutputDeadline)}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">分配任务开始时间</dt><dd className="mt-1"><MilestoneTime label={project.assignmentNodeLabel} value={project.assignmentStartedAt} /></dd></div>
|
||
<div><dt className="text-xs text-slate-500">当前节点名称</dt><dd className="mt-1 font-bold leading-6 text-slate-800">{workflowNodeNames(project, tasks).join(' / ')}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">当前节点负责人</dt><dd className="mt-1 font-bold leading-6 text-slate-800">{workflowTaskOwnerNames(tasks).join(' / ')}</dd></div>
|
||
<div><dt className="text-xs text-slate-500">审核结束时间</dt><dd className="mt-1"><MilestoneTime label={project.reviewNodeLabel} value={project.reviewFinishedAt} /></dd></div>
|
||
</dl>
|
||
</li>
|
||
)
|
||
})}
|
||
</ul>
|
||
</>
|
||
)
|
||
}
|