Files
solution-erp/.claude/workflows/runs/2026-07-30-S162-cay-4-folder-gd/harness-s162.tsx.txt
2026-07-30 09:46:39 +07:00

181 lines
12 KiB
Plaintext

// THROWAWAY — harness FD2 cho S162 (cây 4 folder GĐ). XOÁ SAU KHI CHỤP.
// Render CHÍNH các component thật (không vẽ lại markup) với cache TanStack seed
// sẵn + menu quyền seed vào localStorage ⇒ soi được UI mà không cần API/SQL.
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { MemoryRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import './index.css'
import { AuthProvider } from '@/contexts/AuthContext'
import { PipelineStageFolders } from '@/components/pipeline/PipelineStageFolders'
import { PipelineTreePanel } from '@/components/pipeline/PipelineTreePanel'
const menu = [
{ key: 'PurchaseEvaluations', label: 'Duyệt NCC', parentKey: null, order: 1, icon: null, canRead: true, canCreate: true, canUpdate: true, canDelete: true, isVisible: true, displayLabel: null, children: [] },
{ key: 'KeHoachKyKet', label: 'Kế hoạch ký kết HĐ', parentKey: null, order: 2, icon: null, canRead: true, canCreate: true, canUpdate: true, canDelete: true, isVisible: true, displayLabel: null, children: [] },
{ key: 'Contracts', label: 'Duyệt hợp đồng', parentKey: null, order: 3, icon: null, canRead: true, canCreate: true, canUpdate: true, canDelete: true, isVisible: true, displayLabel: null, children: [] },
{ key: 'HopDongCung', label: 'Hợp đồng cứng', parentKey: null, order: 4, icon: null, canRead: true, canCreate: true, canUpdate: true, canDelete: true, isVisible: true, displayLabel: null, children: [] },
]
localStorage.setItem('solution-erp-user-token', 'harness')
localStorage.setItem('solution-erp-user-user', JSON.stringify({ id: 'u1', fullName: 'Harness', email: 'h@x.vn', roles: [] }))
localStorage.setItem('solution-erp-user-menu', JSON.stringify(menu))
const pe = (i: number, over: Record<string, unknown>) => ({
id: `pe-${i}`, maPhieu: `PDG-2026-${String(i).padStart(3, '0')}`,
tenGoiThau: 'Cung cấp và lắp đặt hệ thống điện nhẹ khối A', type: 1, phase: 7,
projectId: 'prj-1', projectName: 'Khu phức hợp Sunrise Riverside', projectCode: 'SRS',
workItemId: 'wi-1', workItemName: '02 Hệ thống điện nhẹ',
selectedSupplierId: null, selectedSupplierName: null, contractId: null, slaDeadline: null,
createdAt: '2026-05-14T03:00:00Z', updatedAt: null,
drafterUserId: 'u1', drafterName: 'Nguyễn Văn Kiệt', departmentId: 'd1', departmentName: 'Phòng Mua hàng',
budgetPeriodAmount: null, expectedRemainingAmount: null, isUrgentByPro: false, isUrgentByCcm: false,
winnerSupplierNames: [], endsBeforeCeo: false, finalizeStepName: null, finalizeLevelName: null,
...over,
})
const peRows = [
pe(1, { contractId: 'ct-1' }),
pe(2, { phase: 3, tenGoiThau: 'Thi công phần thô tháp B', maPhieu: 'PDG-2026-002' }),
pe(3, { workItemId: 'wi-2', workItemName: '10 Cửa nhôm kính', tenGoiThau: 'Cửa nhôm kính mặt dựng', maPhieu: 'PDG-2026-003', createdAt: '2026-03-02T03:00:00Z' }),
pe(4, { projectId: 'prj-2', projectName: 'Nhà máy Đại An giai đoạn 2', projectCode: 'DAN', workItemId: null, workItemName: null, tenGoiThau: 'San lấp mặt bằng', maPhieu: 'PDG-2026-004', createdAt: '2025-11-20T03:00:00Z' }),
]
const khkk = [{
id: 'kh-1', maKeHoach: 'KHKK-2026-001', phase: 3, purchaseEvaluationId: 'pe-1',
peMaPhieu: 'PDG-2026-001', peTenGoiThau: 'Cung cấp và lắp đặt hệ thống điện nhẹ khối A',
projectId: 'prj-1', projectName: 'Khu phức hợp Sunrise Riverside', departmentId: null, departmentName: null,
drafterUserId: 'u1', drafterFullName: 'Nguyễn Văn Kiệt', approvalWorkflowId: null, workflowCode: null,
currentApprovalLevelOrder: null, totalProposedAmount: 4200000000, createdAt: '2026-06-01T03:00:00Z', updatedAt: null,
}]
const contracts = [{
id: 'ct-1', maHopDong: 'HĐ-TP-2026-014', tenHopDong: 'HĐ thi công hệ thống điện nhẹ khối A',
type: 1, phase: 9, supplierId: 's1', supplierName: 'Công ty TNHH Điện nhẹ Toàn Cầu',
projectId: 'prj-1', projectName: 'Khu phức hợp Sunrise Riverside', giaTri: 4150000000,
slaDeadline: null, createdAt: '2026-06-20T03:00:00Z', hasSealedCopy: true,
}]
const qc = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: 600_000 } } })
qc.setQueryData(['pipeline-pe-index'], peRows)
qc.setQueryData(['pipeline-khkk-index'], khkk)
qc.setQueryData(['pipeline-contract-index'], contracts)
// Vỏ cây trang Duyệt NCC (sao markup từ PurchaseEvaluationsListPage để soi
// folder trong đúng ngữ cảnh). Leaf phiếu = rút gọn của dòng phiếu thật.
function PeTreeShell() {
return (
<aside className="flex w-[400px] flex-col overflow-hidden border-r border-slate-200 bg-white">
<div className="flex-1 overflow-y-auto">
<div className="divide-y divide-slate-100">
<details open className="group/year">
<summary className="flex cursor-pointer items-center gap-1.5 bg-slate-50 px-3 py-2 [&::-webkit-details-marker]:hidden">
<svg className="h-3 w-3 shrink-0 text-slate-500 transition-transform group-open/year:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
<span className="text-base">📅</span>
<span className="flex-1 truncate text-[13px] font-semibold text-slate-900">Năm 2026</span>
<span className="rounded-full bg-slate-200 px-2 py-0.5 text-[10px] font-medium text-slate-700">3</span>
</summary>
<div className="ml-3 border-l border-slate-200">
<details open className="group/proj">
<summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 [&::-webkit-details-marker]:hidden">
<svg className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/proj:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
<span className="text-sm">📁</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700">SRS</span>
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">3</span>
</summary>
<div className="ml-3 border-l border-slate-200">
<details open className="group/wi">
<summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 [&::-webkit-details-marker]:hidden">
<svg className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/wi:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
<span className="text-sm">🧱</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700">02 Hệ thống điện nhẹ</span>
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">2</span>
</summary>
<PipelineStageFolders
className="ml-3 border-l border-slate-200"
currentStage={1}
stages={[
{ n: 1, leaves: [], count: 2, content: (
<ul className="divide-y divide-slate-100">
{peRows.slice(0, 2).map(p => (
<li key={p.id}>
<div className="px-3 py-2">
<div className="flex items-start justify-between gap-2">
<span className="truncate text-[13px] font-medium text-slate-900">{p.tenGoiThau}</span>
<span className="shrink-0 rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-medium text-emerald-800">Đã duyệt</span>
</div>
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
<span className="font-mono">{p.maPhieu}</span>
<span className="text-slate-300">·</span>
<span>14/05/2026 10:00</span>
</div>
</div>
</li>
))}
</ul>
) },
{ n: 2, leaves: [{ id: 'kh-1', primary: 'KHKK-2026-001', secondary: 'Cung cấp và lắp đặt hệ thống điện nhẹ khối A', badgeLabel: 'Đã duyệt', badgeClass: 'bg-emerald-100 text-emerald-800 border-emerald-300', onOpen: () => {} }] },
{ n: 3, leaves: [{ id: 'ct-1', primary: 'HĐ-TP-2026-014', secondary: 'HĐ thi công hệ thống điện nhẹ khối A', badgeLabel: 'Đã phát hành', badgeClass: 'bg-emerald-100 text-emerald-800', onOpen: () => {} }] },
{ n: 4, leaves: [{ id: 'ct-1', primary: 'HĐ-TP-2026-014', secondary: 'HĐ thi công hệ thống điện nhẹ khối A', badgeLabel: 'Đã lưu bản cứng', badgeClass: 'bg-emerald-100 text-emerald-800', onOpen: () => {} }] },
]}
/>
</details>
<details className="group/wi">
<summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 [&::-webkit-details-marker]:hidden">
<svg className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/wi:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
<span className="text-sm">🧱</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700">10 Cửa nhôm kính</span>
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">1</span>
</summary>
</details>
</div>
</details>
</div>
</details>
</div>
</div>
</aside>
)
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className="space-y-2">
<h2 className="text-[13px] font-bold uppercase tracking-wide text-slate-500">{title}</h2>
{children}
</section>
)
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<MemoryRouter>
<QueryClientProvider client={qc}>
<AuthProvider>
<div className="space-y-8 bg-slate-50 p-5">
<Section title="A · Trong cây trang Duyệt NCC (GĐ1 mở sẵn)">
<PeTreeShell />
</Section>
<Section title="B · Panel cây trái — trang Kế hoạch ký kết (GĐ2 mở sẵn)">
<div className="max-w-[21rem]"><PipelineTreePanel currentStage={2} /></div>
</Section>
<Section title="C · Panel cây trái — trang Hợp đồng cứng (GĐ4 mở sẵn)">
<div className="max-w-[21rem]"><PipelineTreePanel currentStage={4} /></div>
</Section>
<Section title="D · Trạng thái biên: khoá quyền / lỗi tải / rỗng">
<div className="w-[400px] border-l border-slate-200 bg-white">
<PipelineStageFolders
currentStage={1}
stages={[
{ n: 1, leaves: [{ id: 'x', primary: 'PDG-2026-009', secondary: 'Gói thầu mẫu', badgeLabel: 'Đã gửi duyệt', badgeClass: 'bg-amber-100 text-amber-800', onOpen: () => {} }] },
{ n: 2, leaves: [], locked: true },
{ n: 3, leaves: [], failed: true },
{ n: 4, leaves: [] },
]}
/>
</div>
</Section>
</div>
</AuthProvider>
</QueryClientProvider>
</MemoryRouter>
</StrictMode>,
)