[CLAUDE] FE: ContractDetailsPreview cho create mode — table headers + disabled add
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m36s

User feedback: thay vì placeholder dashed nhỏ "Chi tiết sẽ hiện sau khi
tạo Header", show structure thật của Chi tiết section ngay từ đầu nhưng
disabled. User thấy trước layout columns + button add → trải nghiệm
liên tục, không bất ngờ khi switch sang edit mode.

## Component mới: ContractDetailsPreview

- Section title "Chi tiết ({TypeLabel})" + amber pill "🔒 Cần tạo Header trước"
- Table opacity-60 với:
  - thead column headers per type (sync với HEADERS_BY_TYPE config)
  - tbody empty state: Lock icon + "Tạo Header xong sẽ thêm được hạng mục"
- Disabled "+ Thêm dòng" button (cursor-not-allowed, slate-400 text)

## HEADERS_BY_TYPE config

7 type × column headers — duplicate nhỏ với ContractDetailsTab.tsx renderers
(acceptable: chỉ là labels visual, không logic).

## Reactive theo type

User đổi dropdown "Loại HĐ" → preview headers update tương ứng (state-driven).

## Build

- fe-user: tsc + vite pass (586ms)
- fe-admin: tsc + vite pass (709ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-23 12:33:00 +07:00
parent 16e24ed962
commit 39031ca33c
2 changed files with 132 additions and 8 deletions

View File

@ -13,7 +13,7 @@
import { useState, useMemo, type FormEvent, useEffect } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useSearchParams } from 'react-router-dom'
import { FileText, Plus, Search, Save, Pencil, Trash2 } from 'lucide-react'
import { FileText, Plus, Search, Save, Pencil, Trash2, Lock } from 'lucide-react'
import { toast } from 'sonner'
import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab'
import { PhaseBadge } from '@/components/PhaseBadge'
@ -379,13 +379,75 @@ function ContractHeaderForm({
</Button>
</div>
</div>
<div className="rounded-lg border border-dashed border-slate-300 bg-slate-50 p-6 text-center text-sm text-slate-400">
Chi tiết (line items) sẽ hiện đây sau khi tạo Header xong.
</div>
<ContractDetailsPreview type={type} />
</form>
)
}
// Preview section cho create mode — render structure thật của Chi tiết
// (table headers + add button) nhưng disabled, để user thấy trước phần
// nhập liệu sẽ unlock sau khi tạo Header xong.
function ContractDetailsPreview({ type }: { type: number }) {
const headers = HEADERS_BY_TYPE[type] ?? []
const typeLabel = ContractTypeLabel[type] ?? '—'
return (
<div className="rounded-lg border border-slate-200 bg-white p-5">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<FileText className="h-4 w-4" />
Chi tiết ({typeLabel})
<span className="ml-2 rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-medium text-amber-700">
🔒 Cần tạo Header trước
</span>
</h2>
<div className="overflow-x-auto rounded-lg border border-slate-200 opacity-60">
<table className="min-w-full text-sm">
<thead className="bg-slate-50 text-[11px] uppercase tracking-wider text-slate-500">
<tr>
<th className="w-10 px-2 py-2 text-left">#</th>
{headers.map(h => <th key={h} className="px-2 py-2 text-left">{h}</th>)}
<th className="w-10 px-2 py-2"></th>
</tr>
</thead>
<tbody>
<tr>
<td colSpan={headers.length + 2} className="px-3 py-12 text-center">
<div className="flex flex-col items-center gap-2 text-slate-400">
<Lock className="h-5 w-5" />
<span className="text-sm">Tạo Header xong sẽ thêm đưc hạng mục chi tiết</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<button
type="button"
disabled
className="mt-3 flex w-full cursor-not-allowed items-center justify-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-slate-400"
title="Tạo Header trước"
>
<Plus className="h-4 w-4" />
Thêm dòng
</button>
</div>
)
}
// Headers per ContractType — sync với ContractDetailsTab.tsx để preview
// trùng với Chi tiết thật khi unlock.
const HEADERS_BY_TYPE: Record<number, string[]> = {
1: ['Hạng mục', 'ĐVT', 'Khối lượng', 'Đơn giá', 'Thành tiền', 'Hoàn thành', 'Ghi chú'],
2: ['Mã CV', 'Tên công việc', 'ĐVT', 'KL', 'Đơn giá', 'Thành tiền', 'Hoàn thành'],
3: ['Mã SP', 'Tên SP', 'ĐVT', 'SL', 'Đơn giá', 'Thành tiền', 'Giao hàng'],
4: ['Mã DV', 'Tên DV', 'ĐVT', 'Thời gian', 'Đơn giá', 'Thành tiền'],
5: ['Mã SP', 'Tên SP', 'ĐVT', 'SL', 'Đơn giá', 'VAT (%)', 'Thành tiền'],
6: ['Nhóm SP', 'Tên SP', 'ĐVT', 'Giá min', 'Giá max', 'Điều kiện thanh toán'],
7: ['Loại DV', 'Tên DV', 'ĐVT', 'Giá min', 'Giá max', 'SLA'],
}
function ContractEditForm({
contract,
onSaved,