[CLAUDE] FE: PE detail flat layout — Panel 2 gop 3 section, Panel 3 them approvals + history
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m59s

User request: 'cho tat ca cai nay the hien tren dung 1 man hinh nhe, cai
duyet va lich su thi dua sang panel 3'.

Panel 2 (PeDetailTabs): truoc 5 tab (Info/NCC/Items/Approvals/History).
Sau bo tabs, flat render 3 section stack doc voi divider + title uppercase:
  Thong tin → NCC tham gia (N) → Hang muc + Bao gia (N)

Panel 3 (PeWorkflowPanel): truoc chi workflow timeline + transition btn.
Sau them 2 section ben duoi:
  Workflow timeline → Lich su duyet (PeApprovalsSection) → Lich su thay doi
  (PeHistorySection)

Export PeApprovalsSection + PeHistorySection tu PeDetailTabs — reuse
ApprovalsTab + HistoryTab logic cu, wrap them <h3> section title.

Dong bo ca fe-admin + fe-user (copy identical file).
This commit is contained in:
pqhuy1987
2026-04-24 11:44:19 +07:00
parent fc4b3d6078
commit 68938a521a
4 changed files with 116 additions and 78 deletions

View File

@ -1,6 +1,7 @@
// Detail tabs cho 1 phiếu Duyệt NCC: Thông tin / NCC / Hạng mục + Báo giá /
// Duyệt / Lịch sử. Inline action dialog để add NCC, add Detail, upsert Quote,
// select winner.
// Detail content cho 1 phiếu Duyệt NCC. Flat render (no tabs): Thông tin +
// NCC + Hạng mục + Báo giá stack vertically trong 1 màn hình.
// Duyệt history + Lịch sử thay đổi → moved to Panel 3 (xem PeWorkflowPanel
// → PeApprovalsSection + PeHistorySection).
import { useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
@ -27,10 +28,10 @@ import {
} from '@/types/purchaseEvaluation'
import type { Supplier } from '@/types/master'
type TabKey = 'info' | 'suppliers' | 'items' | 'approvals' | 'history'
const fmtMoney = (v: number) => v.toLocaleString('vi-VN')
// Main detail content — flat render 3 section không tabs.
// Tên giữ PeDetailTabs để không break callsite (rename gây churn).
export function PeDetailTabs({
evaluation,
onBack,
@ -40,7 +41,6 @@ export function PeDetailTabs({
onBack: () => void
onDelete: () => void
}) {
const [tab, setTab] = useState<TabKey>('info')
const navigate = useNavigate()
const isDraft = evaluation.phase === PurchaseEvaluationPhase.DangSoanThao
@ -83,42 +83,50 @@ export function PeDetailTabs({
</div>
</div>
<nav className="flex gap-1 border-b border-slate-200 px-3 pt-2">
{(
[
['info', 'Thông tin'],
['suppliers', `NCC (${evaluation.suppliers.length})`],
['items', `Hạng mục (${evaluation.details.length})`],
['approvals', `Duyệt (${evaluation.approvals.length})`],
['history', 'Lịch sử'],
] as const
).map(([k, lbl]) => (
<button
key={k}
onClick={() => setTab(k)}
className={cn(
'rounded-t-md border-b-2 px-3 py-1.5 text-xs font-medium transition',
tab === k
? 'border-brand-500 text-brand-700'
: 'border-transparent text-slate-500 hover:text-slate-700',
)}
>
{lbl}
</button>
))}
</nav>
<div className="p-5">
{tab === 'info' && <InfoTab ev={evaluation} />}
{tab === 'suppliers' && <SuppliersTab ev={evaluation} />}
{tab === 'items' && <ItemsTab ev={evaluation} />}
{tab === 'approvals' && <ApprovalsTab ev={evaluation} />}
{tab === 'history' && <HistoryTab ev={evaluation} />}
<div className="divide-y divide-slate-200">
<Section title="Thông tin">
<InfoTab ev={evaluation} />
</Section>
<Section title={`NCC tham gia (${evaluation.suppliers.length})`}>
<SuppliersTab ev={evaluation} />
</Section>
<Section title={`Hạng mục + Báo giá (${evaluation.details.length})`}>
<ItemsTab ev={evaluation} />
</Section>
</div>
</div>
)
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className="px-5 py-4">
<h3 className="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-500">{title}</h3>
{children}
</section>
)
}
// ===== Exports cho Panel 3 — Approvals history + Changelog =====
export function PeApprovalsSection({ ev }: { ev: PeDetailBundle }) {
return (
<div>
<h3 className="mb-2 text-sm font-semibold text-slate-900">Lịch sử duyệt ({ev.approvals.length})</h3>
<ApprovalsTab ev={ev} />
</div>
)
}
export function PeHistorySection({ ev }: { ev: PeDetailBundle }) {
return (
<div>
<h3 className="mb-2 text-sm font-semibold text-slate-900">Lịch sử thay đi</h3>
<HistoryTab ev={ev} />
</div>
)
}
// ===== Tab: Thông tin =====
function InfoTab({ ev }: { ev: PeDetailBundle }) {
const canCreateContract = ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId

View File

@ -1,5 +1,7 @@
// Panel 3: workflow + transition buttons. Pulls nextPhases từ BE bundle
// (single source of truth) → render per-phase action button.
// Panel 3: workflow timeline + transition buttons + approval history + changelog.
// Pulls nextPhases từ BE bundle (single source of truth) → render per-phase
// action button. Approvals + History moved here from PeDetailTabs (2 section
// dưới cùng) để Panel 2 tập trung hiển thị nội dung phiếu (Info + NCC + Items).
import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
@ -16,6 +18,7 @@ import {
PurchaseEvaluationPhaseLabel,
type PeDetailBundle,
} from '@/types/purchaseEvaluation'
import { PeApprovalsSection, PeHistorySection } from './PeDetailTabs'
export function PeWorkflowPanel({ evaluation }: { evaluation: PeDetailBundle }) {
const [target, setTarget] = useState<number | null>(null)
@ -112,6 +115,14 @@ export function PeWorkflowPanel({ evaluation }: { evaluation: PeDetailBundle })
<Textarea value={comment} onChange={e => setComment(e.target.value)} rows={3} />
</Dialog>
)}
<div className="border-t border-slate-200 pt-4">
<PeApprovalsSection ev={evaluation} />
</div>
<div className="border-t border-slate-200 pt-4">
<PeHistorySection ev={evaluation} />
</div>
</div>
)
}