Files
solution-erp/fe-admin/src/components/contracts/WorkflowHistoryPanel.tsx
pqhuy1987 b75448e711
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
[CLAUDE] FE-User+FE-Admin: 3-panel layout cho danh sách HĐ
Redesign trang Danh sách HĐ (Ct_*_List menu fe-user + /contracts admin)
thành 3-panel: List | Detail content | Workflow + lịch sử duyệt. Selected
HĐ giữ qua URL ?id= (bookmarkable + back/forward navigation work).

## Components mới (reuse cho cả 3-panel embedded + fullpage detail)

### fe-user/src/components/contracts/
- ContractDetailContent.tsx — Panel 2 body: header sticky (title + phase
  + actions Yêu cầu sửa/Duyệt) + Info section + Comments thread + form
  thêm góp ý + Attachments. Transition Dialog inline. Prop optional
  onBack — render arrow back button (fullpage) hoặc skip (embedded).
- WorkflowHistoryPanel.tsx — Panel 3: WorkflowSummaryCard (timeline
  policy current+next) + Lịch sử duyệt (approvals: phase from→to + actor
  + timestamp + comment).

### fe-admin/src/components/contracts/
- ContractDetailContent.tsx — variant admin có thêm Phòng ban + Bypass
  CCM trong Info section. Invalidate ['contracts'] khi transition.
- WorkflowHistoryPanel.tsx — identical fe-user.

## Trang refactored

### fe-user
- MyContractsPage.tsx — bỏ DataTable, dùng 3-panel grid
  lg:grid-cols-[320px_1fr_360px] h-[calc(100vh-4rem)]:
    Panel 1: search box + list compact (mã/tên/NCC/phase/SLA/giá), click
             update ?id= active highlight ring-brand
    Panel 2: detail content embedded
    Panel 3: workflow + history
  Mobile (<lg): chỉ Panel 1 visible, click row navigate fullpage
  /contracts/:id (UX khả dụng, không nhồi 3 panel màn hình hẹp).
  URL state: ?type=X (filter loại) + ?id= (selected) + ?q= (search).
- ContractDetailPage.tsx — slim version dùng ContractDetailContent +
  WorkflowHistoryPanel, giữ deep link /contracts/:id work.

### fe-admin
- ContractsListPage.tsx — 3-panel + filter phase + pagination compact
  trong Panel 1 footer. URL state: ?type, ?pendingMe, ?id, ?q, ?phase,
  ?page (full bookmarkable). Title hiển thị loại HĐ + count badge.
- ContractDetailPage.tsx — slim version giống fe-user.

## Build verified

- fe-user: tsc -b + vite build pass (1888 modules, 1.08MB JS)
- fe-admin: tsc -b + vite build pass (1903 modules, 1.15MB JS)

Note: npm install resolved @microsoft/signalr 8.0.7 → 8.0.17 (within
^8.0.7 caret), reverted package.json + lock changes do bump không phải
scope task này. Dev tiếp theo run npm install sẽ tự re-resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:04:46 +07:00

46 lines
2.0 KiB
TypeScript

// Panel 3 cho 3-panel layout — gom WorkflowSummaryCard (timeline policy) +
// approval history (ai/khi/quyết định gì) vào 1 stack.
import { ArrowRight, Clock } from 'lucide-react'
import { PhaseBadge } from '@/components/PhaseBadge'
import { WorkflowSummaryCard } from '@/components/WorkflowSummaryCard'
import type { ContractDetail } from '@/types/contracts'
const fmt = (s: string) => new Date(s).toLocaleString('vi-VN')
export function WorkflowHistoryPanel({ contract: c }: { contract: ContractDetail }) {
return (
<div className="space-y-4">
{c.workflow && <WorkflowSummaryCard workflow={c.workflow} currentPhase={c.phase} />}
<section className="rounded-xl border border-slate-200 bg-white p-5 shadow-sm">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<Clock className="h-4 w-4" />
Lịch sử duyệt ({c.approvals.length})
</h2>
<ol className="space-y-3">
{c.approvals.length === 0 && <li className="text-sm text-slate-400">Chưa lịch sử.</li>}
{c.approvals.map(a => (
<li key={a.id} className="flex gap-3">
<div className="mt-1 h-2 w-2 rounded-full bg-brand-500" />
<div className="flex-1 space-y-0.5 text-sm">
<div className="flex items-center gap-1">
<PhaseBadge phase={a.fromPhase} className="text-[10px]" />
<ArrowRight className="h-3 w-3 text-slate-400" />
<PhaseBadge phase={a.toPhase} className="text-[10px]" />
</div>
<div className="text-slate-700">{a.approverName ?? 'Hệ thống'}</div>
<div className="text-xs text-slate-500">{fmt(a.approvedAt)}</div>
{a.comment && (
<div className="mt-1 rounded bg-slate-50 px-2 py-1 text-xs text-slate-600">
{a.comment}
</div>
)}
</div>
</li>
))}
</ol>
</section>
</div>
)
}