[CLAUDE] PurchaseEvaluation: tách khối nổi bật "4. Thông tin chọn thầu" (anh Kiệt FDC)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m7s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m7s
Tách c/d/e (Giá chào thầu + Đề xuất PRO/CCM + Bảng so sánh + Link hồ sơ) khỏi Section 3 thành khối NỔI BẬT riêng "4. Thông tin chọn thầu" — header xanh #1F7DC1 giống bảng ngân sách "giá trị thực hiện". Đánh số lại 1->5: - 1. Tên NCC/TP + Giá trị (chào thầu): winner "mang từ trên xuống", hiển-thị nổi bật (Section 3 giữ ô CHỌN NccSelectorRow; multi-winner -> tên cách dấu phẩy) - 2-3. Giá đề xuất PRO/CCM (SuggestedPriceRows) · 4. Bảng so sánh giá · 5. Link hồ sơ - Ý kiến cấp duyệt: Section 4 -> 5. FE-only (PeDetailTabs ChonNccSection + new ThongTinChonThauSection), 2 app SHA-mirror, build sạch. Mockup anh Kiệt duyệt trước khi build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -402,7 +402,12 @@ export function PeDetailTabs({
|
|||||||
<Section title="3. Đơn vị NCC/TP được chọn">
|
<Section title="3. Đơn vị NCC/TP được chọn">
|
||||||
<ChonNccSection ev={evaluation} readOnly={readOnly} />
|
<ChonNccSection ev={evaluation} readOnly={readOnly} />
|
||||||
</Section>
|
</Section>
|
||||||
<Section title="4. Ý kiến cấp duyệt (sign-off theo workflow)">
|
{/* [anh Kiệt FDC S87+] Khối NỔI BẬT "4. Thông tin chọn thầu" — header xanh
|
||||||
|
giống bảng "giá trị thực hiện". Gom winner + c/d/e tách khỏi Section 3. */}
|
||||||
|
<section className="px-3 py-3 sm:px-5 sm:py-4">
|
||||||
|
<ThongTinChonThauSection ev={evaluation} readOnly={readOnly} />
|
||||||
|
</section>
|
||||||
|
<Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)">
|
||||||
{mode === 'workspace' && (
|
{mode === 'workspace' && (
|
||||||
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">
|
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">
|
||||||
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu “Duyệt” để ký.
|
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu “Duyệt” để ký.
|
||||||
@ -1742,7 +1747,7 @@ function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: bo
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-2 rounded-lg border border-slate-200 bg-slate-50/60 px-3 py-2.5">
|
<div className="space-y-2 rounded-lg border border-slate-200 bg-slate-50/60 px-3 py-2.5">
|
||||||
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
||||||
Giá đề xuất (ngoài giá chào thầu)
|
2 – 3. Giá đề xuất từ PRO / CCM (ngoài giá chào thầu)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* PRO — Giá Min / Giá Max */}
|
{/* PRO — Giá Min / Giá Max */}
|
||||||
@ -1895,17 +1900,9 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly
|
|||||||
const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.suppliers.some(s => s.isWinner)
|
const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.suppliers.some(s => s.isWinner)
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
const [createOpen, setCreateOpen] = useState(false)
|
||||||
|
|
||||||
// c. Giá chào thầu = sum quotes của NCC được chọn (winner). Dùng helper
|
// [anh Kiệt FDC S87+] c/d/e (Giá chào thầu + Đề xuất PRO/CCM + Bảng so sánh + Link hồ
|
||||||
// module-level (computeGiaChaoThau) — cùng predicate với pre-check gửi duyệt.
|
// sơ) + hiển-thị winner ĐÃ TÁCH sang khối nổi bật "4. Thông tin chọn thầu"
|
||||||
const hasWinner = ev.suppliers.some(s => s.isWinner) // [Mig 58] multi-winner
|
// (ThongTinChonThauSection). Section 3 này GIỮ: a. ô CHỌN winner + b. bảng ngân sách + tạo HĐ.
|
||||||
const giaChaoThau = computeGiaChaoThau(ev)
|
|
||||||
|
|
||||||
// d. Bản so sánh — attachments supplier-row null, NHƯNG loại file "đính kèm khi
|
|
||||||
// duyệt" (S78 — purpose=ApprovalAttachment cũng supplierId=null) khỏi section này.
|
|
||||||
const banSoSanhAttachments = ev.attachments.filter(
|
|
||||||
a => a.purchaseEvaluationSupplierId === null
|
|
||||||
&& a.purpose !== PeAttachmentPurpose.ApprovalAttachment,
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@ -1913,39 +1910,8 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly
|
|||||||
{/* b. TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (S61 — Excel anh Kiệt). Thay BudgetFieldRow
|
{/* b. TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (S61 — Excel anh Kiệt). Thay BudgetFieldRow
|
||||||
+ BudgetAdjustSection cũ (module Budget bỏ hẳn). */}
|
+ BudgetAdjustSection cũ (module Budget bỏ hẳn). */}
|
||||||
<PeBudgetSummaryTable ev={ev} readOnly={readOnly} />
|
<PeBudgetSummaryTable ev={ev} readOnly={readOnly} />
|
||||||
<FormRow
|
{/* [anh Kiệt FDC S87+] c/d/e + hiển-thị winner ĐÃ chuyển sang khối nổi bật
|
||||||
label="c. Giá chào thầu"
|
"4. Thông tin chọn thầu" (ThongTinChonThauSection — render ở parent, sau Section 3). */}
|
||||||
value={
|
|
||||||
!hasWinner ? (
|
|
||||||
<span className="text-slate-400">— (chọn NCC/TP ở (a) trước)</span>
|
|
||||||
) : giaChaoThau === 0 ? (
|
|
||||||
<span className="text-slate-400">— (chưa nhập báo giá ở Section 4)</span>
|
|
||||||
) : (
|
|
||||||
<span className="font-semibold text-slate-900">{giaChaoThau!.toLocaleString('vi-VN')} đ</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{/* [Mig 54 2026-06-18 — anh Kiệt FDC] Giá đề xuất PRO (Min/Max) + CCM —
|
|
||||||
NGOÀI giá NCC. Role-gate qua canEditPro/CcmSuggestedPrice (mirror budget,
|
|
||||||
KHÔNG ràng phase). Khi DaDuyet + approvedPriceAmount → show giá chốt + nguồn. */}
|
|
||||||
<SuggestedPriceRows ev={ev} readOnly={readOnly} />
|
|
||||||
<div>
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<span className="w-44 shrink-0 text-[12px] text-slate-500">d. Bảng so sánh giá</span>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<GeneralAttachmentsSection
|
|
||||||
evaluationId={ev.id}
|
|
||||||
attachments={banSoSanhAttachments}
|
|
||||||
readOnly={readOnly}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* e. Link hồ sơ (anh Kiệt FDC) — 1 hyperlink tới thư mục hồ sơ trên NAS công ty.
|
|
||||||
Read-only: render thẻ <a> bấm-mở (target=_blank). Editable: Input dán URL +
|
|
||||||
nút Lưu (PUT /purchase-evaluations/:id echo field bắt buộc + hoSoLink). */}
|
|
||||||
<HoSoLinkRow ev={ev} readOnly={readOnly} />
|
|
||||||
|
|
||||||
{ev.paymentTerms && (
|
{ev.paymentTerms && (
|
||||||
<FormRow label="Điều khoản thanh toán" value={<span className="whitespace-pre-wrap">{ev.paymentTerms}</span>} />
|
<FormRow label="Điều khoản thanh toán" value={<span className="whitespace-pre-wrap">{ev.paymentTerms}</span>} />
|
||||||
@ -1974,6 +1940,62 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Section 4 — Thông tin chọn thầu (anh Kiệt FDC S87+) =====
|
||||||
|
// Tách c/d/e khỏi Section 3 thành khối NỔI BẬT riêng (header xanh #1F7DC1 giống bảng
|
||||||
|
// ngân sách "giá trị thực hiện"). Đánh số 1→5: 1.Tên NCC/TP+giá trị · 2-3.Đề xuất
|
||||||
|
// PRO/CCM · 4.Bảng so sánh giá · 5.Link hồ sơ. Tên đơn vị "mang từ trên xuống" — Section 3
|
||||||
|
// giữ ô CHỌN (NccSelectorRow), khối này HIỂN THỊ kết quả (multi-winner → tên cách dấu phẩy).
|
||||||
|
function ThongTinChonThauSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
|
||||||
|
const winners = ev.suppliers.filter(s => s.isWinner)
|
||||||
|
const hasWinner = winners.length > 0
|
||||||
|
const giaChaoThau = computeGiaChaoThau(ev)
|
||||||
|
const banSoSanhAttachments = ev.attachments.filter(
|
||||||
|
a => a.purchaseEvaluationSupplierId === null
|
||||||
|
&& a.purpose !== PeAttachmentPurpose.ApprovalAttachment,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div className="overflow-hidden rounded-lg border border-slate-300">
|
||||||
|
<div className="bg-[#1F7DC1] px-3 py-2 text-[12px] font-bold uppercase tracking-wide text-white">
|
||||||
|
Thông tin chọn thầu
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3 p-3">
|
||||||
|
{/* 1. Tên NCC/TP + Giá trị (chào thầu) — kéo từ trên xuống, nổi bật */}
|
||||||
|
<div className="rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2.5">
|
||||||
|
<div className="mb-1 flex items-center gap-2">
|
||||||
|
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-emerald-600 text-[11px] font-bold text-white">1</span>
|
||||||
|
<span className="text-[11px] font-semibold uppercase tracking-wide text-emerald-700">Tên NCC / TP được chọn</span>
|
||||||
|
</div>
|
||||||
|
<div className="pl-7">
|
||||||
|
{hasWinner
|
||||||
|
? <div className="text-[15px] font-semibold text-emerald-900">{winners.map(s => s.supplierName).join(', ')}</div>
|
||||||
|
: <div className="text-[13px] text-slate-400">— (chọn đơn vị ở mục 3 trước)</div>}
|
||||||
|
<div className="mt-1 flex items-baseline gap-2 text-[12px]">
|
||||||
|
<span className="text-slate-500">Giá trị (chào thầu):</span>
|
||||||
|
{!hasWinner
|
||||||
|
? <span className="text-slate-400">—</span>
|
||||||
|
: giaChaoThau === 0
|
||||||
|
? <span className="text-slate-400">— (chưa nhập báo giá)</span>
|
||||||
|
: <span className="text-[14px] font-semibold text-emerald-900">{giaChaoThau!.toLocaleString('vi-VN')} đ</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2 – 3. Giá đề xuất PRO / CCM (ngoài giá chào thầu) */}
|
||||||
|
<SuggestedPriceRows ev={ev} readOnly={readOnly} />
|
||||||
|
|
||||||
|
{/* 4. Bảng so sánh giá */}
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-[12px] font-medium text-slate-600">4. Bảng so sánh giá</div>
|
||||||
|
<GeneralAttachmentsSection evaluationId={ev.id} attachments={banSoSanhAttachments} readOnly={readOnly} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 5. Link hồ sơ */}
|
||||||
|
<HoSoLinkRow ev={ev} readOnly={readOnly} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// e. Link hồ sơ — 1 cột HoSoLink (string? nullable) trỏ thư mục hồ sơ NAS.
|
// e. Link hồ sơ — 1 cột HoSoLink (string? nullable) trỏ thư mục hồ sơ NAS.
|
||||||
// Read-only: thẻ <a> bấm-mở. Editable (phiếu DangSoanThao/TraLai + !readOnly):
|
// Read-only: thẻ <a> bấm-mở. Editable (phiếu DangSoanThao/TraLai + !readOnly):
|
||||||
// Input dán URL + nút Lưu. Save = PUT /purchase-evaluations/:id echo field bắt
|
// Input dán URL + nút Lưu. Save = PUT /purchase-evaluations/:id echo field bắt
|
||||||
@ -2008,7 +2030,7 @@ function HoSoLinkRow({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<span className="w-44 shrink-0 pt-1.5 text-[12px] text-slate-500">e. Link hồ sơ</span>
|
<span className="w-44 shrink-0 pt-1.5 text-[12px] text-slate-500">5. Link hồ sơ</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<div className="flex max-w-2xl items-center gap-2">
|
<div className="flex max-w-2xl items-center gap-2">
|
||||||
|
|||||||
@ -402,7 +402,12 @@ export function PeDetailTabs({
|
|||||||
<Section title="3. Đơn vị NCC/TP được chọn">
|
<Section title="3. Đơn vị NCC/TP được chọn">
|
||||||
<ChonNccSection ev={evaluation} readOnly={readOnly} />
|
<ChonNccSection ev={evaluation} readOnly={readOnly} />
|
||||||
</Section>
|
</Section>
|
||||||
<Section title="4. Ý kiến cấp duyệt (sign-off theo workflow)">
|
{/* [anh Kiệt FDC S87+] Khối NỔI BẬT "4. Thông tin chọn thầu" — header xanh
|
||||||
|
giống bảng "giá trị thực hiện". Gom winner + c/d/e tách khỏi Section 3. */}
|
||||||
|
<section className="px-3 py-3 sm:px-5 sm:py-4">
|
||||||
|
<ThongTinChonThauSection ev={evaluation} readOnly={readOnly} />
|
||||||
|
</section>
|
||||||
|
<Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)">
|
||||||
{mode === 'workspace' && (
|
{mode === 'workspace' && (
|
||||||
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">
|
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">
|
||||||
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu “Duyệt” để ký.
|
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu “Duyệt” để ký.
|
||||||
@ -1742,7 +1747,7 @@ function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: bo
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-2 rounded-lg border border-slate-200 bg-slate-50/60 px-3 py-2.5">
|
<div className="space-y-2 rounded-lg border border-slate-200 bg-slate-50/60 px-3 py-2.5">
|
||||||
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
<div className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
||||||
Giá đề xuất (ngoài giá chào thầu)
|
2 – 3. Giá đề xuất từ PRO / CCM (ngoài giá chào thầu)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* PRO — Giá Min / Giá Max */}
|
{/* PRO — Giá Min / Giá Max */}
|
||||||
@ -1895,17 +1900,9 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly
|
|||||||
const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.suppliers.some(s => s.isWinner)
|
const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.suppliers.some(s => s.isWinner)
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
const [createOpen, setCreateOpen] = useState(false)
|
||||||
|
|
||||||
// c. Giá chào thầu = sum quotes của NCC được chọn (winner). Dùng helper
|
// [anh Kiệt FDC S87+] c/d/e (Giá chào thầu + Đề xuất PRO/CCM + Bảng so sánh + Link hồ
|
||||||
// module-level (computeGiaChaoThau) — cùng predicate với pre-check gửi duyệt.
|
// sơ) + hiển-thị winner ĐÃ TÁCH sang khối nổi bật "4. Thông tin chọn thầu"
|
||||||
const hasWinner = ev.suppliers.some(s => s.isWinner) // [Mig 58] multi-winner
|
// (ThongTinChonThauSection). Section 3 này GIỮ: a. ô CHỌN winner + b. bảng ngân sách + tạo HĐ.
|
||||||
const giaChaoThau = computeGiaChaoThau(ev)
|
|
||||||
|
|
||||||
// d. Bản so sánh — attachments supplier-row null, NHƯNG loại file "đính kèm khi
|
|
||||||
// duyệt" (S78 — purpose=ApprovalAttachment cũng supplierId=null) khỏi section này.
|
|
||||||
const banSoSanhAttachments = ev.attachments.filter(
|
|
||||||
a => a.purchaseEvaluationSupplierId === null
|
|
||||||
&& a.purpose !== PeAttachmentPurpose.ApprovalAttachment,
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@ -1913,39 +1910,8 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly
|
|||||||
{/* b. TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (S61 — Excel anh Kiệt). Thay BudgetFieldRow
|
{/* b. TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (S61 — Excel anh Kiệt). Thay BudgetFieldRow
|
||||||
+ BudgetAdjustSection cũ (module Budget bỏ hẳn). */}
|
+ BudgetAdjustSection cũ (module Budget bỏ hẳn). */}
|
||||||
<PeBudgetSummaryTable ev={ev} readOnly={readOnly} />
|
<PeBudgetSummaryTable ev={ev} readOnly={readOnly} />
|
||||||
<FormRow
|
{/* [anh Kiệt FDC S87+] c/d/e + hiển-thị winner ĐÃ chuyển sang khối nổi bật
|
||||||
label="c. Giá chào thầu"
|
"4. Thông tin chọn thầu" (ThongTinChonThauSection — render ở parent, sau Section 3). */}
|
||||||
value={
|
|
||||||
!hasWinner ? (
|
|
||||||
<span className="text-slate-400">— (chọn NCC/TP ở (a) trước)</span>
|
|
||||||
) : giaChaoThau === 0 ? (
|
|
||||||
<span className="text-slate-400">— (chưa nhập báo giá ở Section 4)</span>
|
|
||||||
) : (
|
|
||||||
<span className="font-semibold text-slate-900">{giaChaoThau!.toLocaleString('vi-VN')} đ</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{/* [Mig 54 2026-06-18 — anh Kiệt FDC] Giá đề xuất PRO (Min/Max) + CCM —
|
|
||||||
NGOÀI giá NCC. Role-gate qua canEditPro/CcmSuggestedPrice (mirror budget,
|
|
||||||
KHÔNG ràng phase). Khi DaDuyet + approvedPriceAmount → show giá chốt + nguồn. */}
|
|
||||||
<SuggestedPriceRows ev={ev} readOnly={readOnly} />
|
|
||||||
<div>
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<span className="w-44 shrink-0 text-[12px] text-slate-500">d. Bảng so sánh giá</span>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<GeneralAttachmentsSection
|
|
||||||
evaluationId={ev.id}
|
|
||||||
attachments={banSoSanhAttachments}
|
|
||||||
readOnly={readOnly}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* e. Link hồ sơ (anh Kiệt FDC) — 1 hyperlink tới thư mục hồ sơ trên NAS công ty.
|
|
||||||
Read-only: render thẻ <a> bấm-mở (target=_blank). Editable: Input dán URL +
|
|
||||||
nút Lưu (PUT /purchase-evaluations/:id echo field bắt buộc + hoSoLink). */}
|
|
||||||
<HoSoLinkRow ev={ev} readOnly={readOnly} />
|
|
||||||
|
|
||||||
{ev.paymentTerms && (
|
{ev.paymentTerms && (
|
||||||
<FormRow label="Điều khoản thanh toán" value={<span className="whitespace-pre-wrap">{ev.paymentTerms}</span>} />
|
<FormRow label="Điều khoản thanh toán" value={<span className="whitespace-pre-wrap">{ev.paymentTerms}</span>} />
|
||||||
@ -1974,6 +1940,62 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Section 4 — Thông tin chọn thầu (anh Kiệt FDC S87+) =====
|
||||||
|
// Tách c/d/e khỏi Section 3 thành khối NỔI BẬT riêng (header xanh #1F7DC1 giống bảng
|
||||||
|
// ngân sách "giá trị thực hiện"). Đánh số 1→5: 1.Tên NCC/TP+giá trị · 2-3.Đề xuất
|
||||||
|
// PRO/CCM · 4.Bảng so sánh giá · 5.Link hồ sơ. Tên đơn vị "mang từ trên xuống" — Section 3
|
||||||
|
// giữ ô CHỌN (NccSelectorRow), khối này HIỂN THỊ kết quả (multi-winner → tên cách dấu phẩy).
|
||||||
|
function ThongTinChonThauSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
|
||||||
|
const winners = ev.suppliers.filter(s => s.isWinner)
|
||||||
|
const hasWinner = winners.length > 0
|
||||||
|
const giaChaoThau = computeGiaChaoThau(ev)
|
||||||
|
const banSoSanhAttachments = ev.attachments.filter(
|
||||||
|
a => a.purchaseEvaluationSupplierId === null
|
||||||
|
&& a.purpose !== PeAttachmentPurpose.ApprovalAttachment,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div className="overflow-hidden rounded-lg border border-slate-300">
|
||||||
|
<div className="bg-[#1F7DC1] px-3 py-2 text-[12px] font-bold uppercase tracking-wide text-white">
|
||||||
|
Thông tin chọn thầu
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3 p-3">
|
||||||
|
{/* 1. Tên NCC/TP + Giá trị (chào thầu) — kéo từ trên xuống, nổi bật */}
|
||||||
|
<div className="rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2.5">
|
||||||
|
<div className="mb-1 flex items-center gap-2">
|
||||||
|
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-emerald-600 text-[11px] font-bold text-white">1</span>
|
||||||
|
<span className="text-[11px] font-semibold uppercase tracking-wide text-emerald-700">Tên NCC / TP được chọn</span>
|
||||||
|
</div>
|
||||||
|
<div className="pl-7">
|
||||||
|
{hasWinner
|
||||||
|
? <div className="text-[15px] font-semibold text-emerald-900">{winners.map(s => s.supplierName).join(', ')}</div>
|
||||||
|
: <div className="text-[13px] text-slate-400">— (chọn đơn vị ở mục 3 trước)</div>}
|
||||||
|
<div className="mt-1 flex items-baseline gap-2 text-[12px]">
|
||||||
|
<span className="text-slate-500">Giá trị (chào thầu):</span>
|
||||||
|
{!hasWinner
|
||||||
|
? <span className="text-slate-400">—</span>
|
||||||
|
: giaChaoThau === 0
|
||||||
|
? <span className="text-slate-400">— (chưa nhập báo giá)</span>
|
||||||
|
: <span className="text-[14px] font-semibold text-emerald-900">{giaChaoThau!.toLocaleString('vi-VN')} đ</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2 – 3. Giá đề xuất PRO / CCM (ngoài giá chào thầu) */}
|
||||||
|
<SuggestedPriceRows ev={ev} readOnly={readOnly} />
|
||||||
|
|
||||||
|
{/* 4. Bảng so sánh giá */}
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-[12px] font-medium text-slate-600">4. Bảng so sánh giá</div>
|
||||||
|
<GeneralAttachmentsSection evaluationId={ev.id} attachments={banSoSanhAttachments} readOnly={readOnly} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 5. Link hồ sơ */}
|
||||||
|
<HoSoLinkRow ev={ev} readOnly={readOnly} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// e. Link hồ sơ — 1 cột HoSoLink (string? nullable) trỏ thư mục hồ sơ NAS.
|
// e. Link hồ sơ — 1 cột HoSoLink (string? nullable) trỏ thư mục hồ sơ NAS.
|
||||||
// Read-only: thẻ <a> bấm-mở. Editable (phiếu DangSoanThao/TraLai + !readOnly):
|
// Read-only: thẻ <a> bấm-mở. Editable (phiếu DangSoanThao/TraLai + !readOnly):
|
||||||
// Input dán URL + nút Lưu. Save = PUT /purchase-evaluations/:id echo field bắt
|
// Input dán URL + nút Lưu. Save = PUT /purchase-evaluations/:id echo field bắt
|
||||||
@ -2008,7 +2030,7 @@ function HoSoLinkRow({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<span className="w-44 shrink-0 pt-1.5 text-[12px] text-slate-500">e. Link hồ sơ</span>
|
<span className="w-44 shrink-0 pt-1.5 text-[12px] text-slate-500">5. Link hồ sơ</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<div className="flex max-w-2xl items-center gap-2">
|
<div className="flex max-w-2xl items-center gap-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user