diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 9277017..aac435d 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -95,22 +95,19 @@ export function PeDetailTabs({
-
- + {/* Section 1 — đúng spec form FO-PHIẾU TRÌNH KÝ CHỌN TP/NCC */} +
+
-
+
+ +
+
-
+
-
- a.purchaseEvaluationSupplierId === null)} - readOnly={readOnly} - /> -
) @@ -145,35 +142,90 @@ export function PeHistorySection({ ev }: { ev: PeDetailBundle }) { ) } -// ===== Tab: Thông tin ===== -function InfoTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { +// ===== Section 1 — Thông tin gói thầu (spec: a. Tên gói thầu / b. Dự án) ===== +function InfoTab({ ev }: { ev: PeDetailBundle }) { + return ( +
+ + + {(ev.diaDiem || ev.moTa) && ( +
+ {ev.diaDiem &&
Địa điểm: {ev.diaDiem}
} + {ev.moTa &&
Mô tả: {ev.moTa}
} +
+ )} +
+ ) +} + +// ===== Section 2 — Chọn NCC/TP (spec: a/b/c/d) ===== +function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId const [createOpen, setCreateOpen] = useState(false) + + // c. Giá chào thầu = sum quotes của NCC được chọn (winner) + const winnerSupplierRowId = ev.selectedSupplierId + ? ev.suppliers.find(s => s.supplierId === ev.selectedSupplierId)?.id ?? null + : null + const giaChaoThau = winnerSupplierRowId + ? ev.details + .flatMap(d => d.quotes) + .filter(q => q.purchaseEvaluationSupplierId === winnerSupplierRowId) + .reduce((sum, q) => sum + q.thanhTien, 0) + : null + + // d. Bản so sánh — attachments với purpose=ComparisonTable hoặc supplier-row null + const banSoSanhAttachments = ev.attachments.filter( + a => a.purchaseEvaluationSupplierId === null, + ) + return ( -
-
- - - - - - - - {ev.budget.maNganSach ?? '—'} - {' · '} - {ev.budget.tenNganSach} - {' · '} - {ev.budget.tongNganSach.toLocaleString('vi-VN')} đ - - ) : — (chưa link)} +
+ ✓ {ev.selectedSupplierName} + ) : — (chưa chọn)} + /> + + {ev.budget.maNganSach ?? '—'} + {' · '}{ev.budget.tenNganSach} + {' · '}{ev.budget.tongNganSach.toLocaleString('vi-VN')} đ + + ) : — (chưa link)} + /> + {giaChaoThau.toLocaleString('vi-VN')} đ + ) : — (chưa chọn NCC / chưa nhập báo giá)} + /> +
+
+ d. Bản so sánh +
+ +
+
+
+ + {ev.paymentTerms && ( + {ev.paymentTerms}} /> + )} + {ev.contractId && ( + ✓ Xem HĐ} /> - {ev.contractId && ( - ✓ Xem HĐ} /> - )} -
+ )} + {canCreateContract && (
@@ -191,6 +243,16 @@ function InfoTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: bool ) } +// Form row: label cố định 176px (w-44) bên trái + value bên phải (giống spec). +function FormRow({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+
{label}
+
{value}
+
+ ) +} + function CreateContractDialog({ evaluation, onClose }: { evaluation: PeDetailBundle; onClose: () => void }) { const navigate = useNavigate() const [form, setForm] = useState({ @@ -253,15 +315,6 @@ function CreateContractDialog({ evaluation, onClose }: { evaluation: PeDetailBun ) } -function Field({ label, value }: { label: string; value: React.ReactNode }) { - return ( -
-
{label}
-
{value}
-
- ) -} - // ===== Tab: NCC ===== function SuppliersTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { const qc = useQueryClient() diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index 9277017..aac435d 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -95,22 +95,19 @@ export function PeDetailTabs({
-
- + {/* Section 1 — đúng spec form FO-PHIẾU TRÌNH KÝ CHỌN TP/NCC */} +
+
-
+
+ +
+
-
+
-
- a.purchaseEvaluationSupplierId === null)} - readOnly={readOnly} - /> -
) @@ -145,35 +142,90 @@ export function PeHistorySection({ ev }: { ev: PeDetailBundle }) { ) } -// ===== Tab: Thông tin ===== -function InfoTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { +// ===== Section 1 — Thông tin gói thầu (spec: a. Tên gói thầu / b. Dự án) ===== +function InfoTab({ ev }: { ev: PeDetailBundle }) { + return ( +
+ + + {(ev.diaDiem || ev.moTa) && ( +
+ {ev.diaDiem &&
Địa điểm: {ev.diaDiem}
} + {ev.moTa &&
Mô tả: {ev.moTa}
} +
+ )} +
+ ) +} + +// ===== Section 2 — Chọn NCC/TP (spec: a/b/c/d) ===== +function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId const [createOpen, setCreateOpen] = useState(false) + + // c. Giá chào thầu = sum quotes của NCC được chọn (winner) + const winnerSupplierRowId = ev.selectedSupplierId + ? ev.suppliers.find(s => s.supplierId === ev.selectedSupplierId)?.id ?? null + : null + const giaChaoThau = winnerSupplierRowId + ? ev.details + .flatMap(d => d.quotes) + .filter(q => q.purchaseEvaluationSupplierId === winnerSupplierRowId) + .reduce((sum, q) => sum + q.thanhTien, 0) + : null + + // d. Bản so sánh — attachments với purpose=ComparisonTable hoặc supplier-row null + const banSoSanhAttachments = ev.attachments.filter( + a => a.purchaseEvaluationSupplierId === null, + ) + return ( -
-
- - - - - - - - {ev.budget.maNganSach ?? '—'} - {' · '} - {ev.budget.tenNganSach} - {' · '} - {ev.budget.tongNganSach.toLocaleString('vi-VN')} đ - - ) : — (chưa link)} +
+ ✓ {ev.selectedSupplierName} + ) : — (chưa chọn)} + /> + + {ev.budget.maNganSach ?? '—'} + {' · '}{ev.budget.tenNganSach} + {' · '}{ev.budget.tongNganSach.toLocaleString('vi-VN')} đ + + ) : — (chưa link)} + /> + {giaChaoThau.toLocaleString('vi-VN')} đ + ) : — (chưa chọn NCC / chưa nhập báo giá)} + /> +
+
+ d. Bản so sánh +
+ +
+
+
+ + {ev.paymentTerms && ( + {ev.paymentTerms}} /> + )} + {ev.contractId && ( + ✓ Xem HĐ} /> - {ev.contractId && ( - ✓ Xem HĐ} /> - )} -
+ )} + {canCreateContract && (
@@ -191,6 +243,16 @@ function InfoTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: bool ) } +// Form row: label cố định 176px (w-44) bên trái + value bên phải (giống spec). +function FormRow({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+
{label}
+
{value}
+
+ ) +} + function CreateContractDialog({ evaluation, onClose }: { evaluation: PeDetailBundle; onClose: () => void }) { const navigate = useNavigate() const [form, setForm] = useState({ @@ -253,15 +315,6 @@ function CreateContractDialog({ evaluation, onClose }: { evaluation: PeDetailBun ) } -function Field({ label, value }: { label: string; value: React.ReactNode }) { - return ( -
-
{label}
-
{value}
-
- ) -} - // ===== Tab: NCC ===== function SuppliersTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { const qc = useQueryClient()