wz / src /components /CaseWorkflowTimeline.tsx
sgfw's picture
Upload 17 files
f4cac62 verified
Raw History Blame Contribute Delete
6.29 kB
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { AccountabilityCase } from '../types';
import { Check, AlertCircle, Clock, CheckCircle2 } from 'lucide-react';
interface TimelineProps {
currentCase: AccountabilityCase;
}
export const STEPS_DEFINITION = [
{ index: 1, name: '启动立案', desc: '成立问责调查组,录入案件基础信息' },
{ index: 2, name: '调查核实', desc: '开展谈话、送达事实见面材料、核查申辩、形成调查报告' },
{ index: 3, name: '合规审理', desc: '审理职责部门受理并出具合规审理意见书' },
{ index: 4, name: '会议审议', desc: '问责委员会委员召开会议并线上表决与签名' },
{ index: 5, name: '申诉窗口期', desc: '10个工作日内申诉确认或直接进入发文阶段' },
{ index: 6, name: '提起申诉', desc: '被问责人提交申诉材料(有异议时触发)' },
{ index: 7, name: '指派复查', desc: '指派审理组或协助调查组,出具复查意见' },
{ index: 8, name: '按程序复议', desc: '重新提请委员会,做出复议决议' },
{ index: 9, name: '发文归档', desc: '下发红头问责文件,抄送人力资源部归档' }
];
export default function CaseWorkflowTimeline({ currentCase }: TimelineProps) {
const currentStep = currentCase.currentStep;
// 判断哪些步骤被跳过或不需要
// 如果案件没有申诉(在步骤5时选择无异议,或者已直接跳到步骤9),那么 6, 7, 8 被视为 skipped
const isSkippedStep = (stepIdx: number): boolean => {
if (!currentCase.hasAppeal && [6, 7, 8].includes(stepIdx)) {
// 已经完成申诉期,或者在申诉期之前,且未触发申诉
if (currentStep === 9 || currentStep === 5) {
return true;
}
}
return false;
};
const getStepStatus = (stepIdx: number): 'completed' | 'active' | 'pending' | 'skipped' => {
if (isSkippedStep(stepIdx)) return 'skipped';
if (currentStep > stepIdx) return 'completed';
if (currentStep === stepIdx) return 'active';
return 'pending';
};
// 期限计算说明
const limitText = currentCase.category === 'asset'
? '问题资产:启动后90天内需完成调查'
: '非问题资产:发起后30个工作日内需完成调查';
return (
<div className="bg-white border border-slate-200 rounded-lg p-4 shadow-2xs" id="workflow-timeline-card">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 mb-4 border-b border-slate-100 pb-3">
<div>
<h3 className="text-sm font-bold text-slate-900 flex items-center gap-1.5">
<Clock className="w-4 h-4 text-slate-500" />
银行问责生命周期追踪
</h3>
<p className="text-[11px] text-slate-500 mt-0.5">流程闭环可追溯,确保每个程序节点合规守信</p>
</div>
<div className="flex items-center gap-1.5 bg-slate-50 border border-slate-200 px-2.5 py-1 rounded text-[11px] font-medium text-slate-600">
<AlertCircle className="w-3.5 h-3.5 text-amber-500" />
<span>时限要求:{limitText}</span>
</div>
</div>
{/* 9步流程横向/纵向进度条 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-9 gap-2 relative">
{STEPS_DEFINITION.map((step) => {
const status = getStepStatus(step.index);
return (
<div
key={step.index}
className={`relative flex flex-col p-2 rounded border transition-all ${
status === 'completed'
? 'bg-slate-50/80 border-slate-200 text-slate-700'
: status === 'active'
? 'bg-blue-50/50 border-blue-200 ring-2 ring-blue-500/5 text-blue-900'
: status === 'skipped'
? 'bg-slate-100/50 border-slate-200/50 text-slate-400 opacity-60'
: 'bg-white border-slate-100 text-slate-400'
}`}
>
{/* 编号 & 状态图标 */}
<div className="flex justify-between items-center mb-1">
<span className={`text-[9px] font-mono font-bold uppercase tracking-wider px-1 py-0.5 rounded ${
status === 'completed'
? 'bg-slate-200 text-slate-700'
: status === 'active'
? 'bg-blue-600 text-white'
: status === 'skipped'
? 'bg-slate-200/60 text-slate-400'
: 'bg-slate-100 text-slate-400'
}`}>
STEP 0{step.index}
</span>
{status === 'completed' && <Check className="w-3.5 h-3.5 text-emerald-600" />}
{status === 'active' && (
<span className="flex h-1.5 w-1.5 relative">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-blue-600"></span>
</span>
)}
{status === 'skipped' && <span className="text-[9px] text-slate-400">跳过</span>}
</div>
{/* 步骤名称 */}
<h4 className={`text-[11px] font-bold ${
status === 'active' ? 'text-blue-600' : 'text-slate-800'
}`}>
{step.name}
</h4>
{/* 简单说明 */}
<p className="text-[10px] leading-relaxed text-slate-500 mt-1 line-clamp-2 md:line-clamp-none">
{step.desc}
</p>
{/* 进度提示线 - 仅在非最后一步且大屏幕时显示 */}
{step.index < 9 && (
<div className="hidden lg:block absolute -right-1.5 top-1/2 -translate-y-1/2 z-10">
<span className={`text-sm font-bold ${
status === 'completed' ? 'text-slate-300' : 'text-slate-100'
}`}>→</span>
</div>
)}
</div>
);
})}
</div>
</div>
);
}