diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx
index a6c9e41..056733c 100644
--- a/fe-admin/src/components/pe/PeDetailTabs.tsx
+++ b/fe-admin/src/components/pe/PeDetailTabs.tsx
@@ -39,10 +39,13 @@ export function PeDetailTabs({
evaluation,
onBack,
onDelete,
+ readOnly = false,
}: {
evaluation: PeDetailBundle
onBack: () => void
onDelete: () => void
+ /** Menu "Duyệt" (pendingMe=1) — ẩn mọi action thêm/sửa/xóa, chỉ xem + duyệt phase. */
+ readOnly?: boolean
}) {
const navigate = useNavigate()
const isDraft = evaluation.phase === PurchaseEvaluationPhase.DangSoanThao
@@ -61,6 +64,11 @@ export function PeDetailTabs({
>
{PurchaseEvaluationPhaseLabel[evaluation.phase]}
+ {readOnly && (
+
+ chế độ duyệt
+
+ )}
{evaluation.maPhieu ?? '—'}
@@ -72,7 +80,7 @@ export function PeDetailTabs({
- {isDraft && (
+ {isDraft && !readOnly && (
<>
@@ -131,8 +139,8 @@ export function PeHistorySection({ ev }: { ev: PeDetailBundle }) {
}
// ===== Tab: Thông tin =====
-function InfoTab({ ev }: { ev: PeDetailBundle }) {
- const canCreateContract = ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId
+function InfoTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
+ const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId
const [createOpen, setCreateOpen] = useState(false)
return (
@@ -236,7 +244,7 @@ function Field({ label, value }: { label: string; value: React.ReactNode }) {
}
// ===== Tab: NCC =====
-function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
+function SuppliersTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
const qc = useQueryClient()
const [open, setOpen] = useState(false)
const [editRow, setEditRow] = useState
(null)
@@ -255,13 +263,17 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
return (
-
-
-
+ {!readOnly && (
+
+
+
+ )}
{ev.suppliers.length === 0 ? (
-
Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.
+
+ {readOnly ? 'Chưa có NCC.' : 'Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.'}
+
) : (
@@ -271,7 +283,7 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
| Liên hệ |
Điều khoản TT |
File đính kèm |
- |
+ {!readOnly && | }
@@ -281,6 +293,9 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
{s.supplierName}
{s.displayName && {s.displayName}
}
{s.note && {s.note}
}
+ {readOnly && ev.selectedSupplierId === s.supplierId && (
+ ✓ NCC được chọn
+ )}
{s.contactName && {s.contactName} }
@@ -293,38 +308,41 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
evaluationId={ev.id}
supplierRowId={s.id}
attachments={ev.attachments.filter(a => a.purchaseEvaluationSupplierId === s.id)}
+ readOnly={readOnly}
/>
|
-
-
-
-
-
-
- |
+ {!readOnly && (
+
+
+
+
+
+
+ |
+ )}
))}
@@ -431,7 +449,7 @@ function EditSupplierDialog({ evaluationId, row, onClose }: { evaluationId: stri
}
// ===== Tab: Hạng mục + Báo giá (matrix) =====
-function ItemsTab({ ev }: { ev: PeDetailBundle }) {
+function ItemsTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
const qc = useQueryClient()
const [addOpen, setAddOpen] = useState(false)
const [editDetail, setEditDetail] = useState(null)
@@ -451,12 +469,16 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
{ev.suppliers.length === 0
- ? 'Thêm NCC ở tab "NCC" trước khi nhập báo giá.'
- : `${ev.details.length} hạng mục × ${ev.suppliers.length} NCC — click ô để nhập báo giá.`}
+ ? (readOnly ? 'Chưa có NCC tham gia.' : 'Thêm NCC ở tab "NCC" trước khi nhập báo giá.')
+ : readOnly
+ ? `${ev.details.length} hạng mục × ${ev.suppliers.length} NCC`
+ : `${ev.details.length} hạng mục × ${ev.suppliers.length} NCC — click ô để nhập báo giá.`}
-
+ {!readOnly && (
+
+ )}
{ev.details.length === 0 ? (
@@ -475,7 +497,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
{s.displayName ?? s.supplierName}
))}
- |
+ {!readOnly && | }
@@ -493,9 +515,10 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
return (
setQuoteEdit({ detail: d, supplier: s, existing: q })}
+ onClick={readOnly ? undefined : () => setQuoteEdit({ detail: d, supplier: s, existing: q })}
className={cn(
- 'cursor-pointer border-r border-slate-200 px-2 py-2 text-right font-mono transition hover:bg-brand-50',
+ 'border-r border-slate-200 px-2 py-2 text-right font-mono transition',
+ !readOnly && 'cursor-pointer hover:bg-brand-50',
q?.isSelected && 'bg-emerald-50 font-semibold text-emerald-700',
)}
>
@@ -503,16 +526,18 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
)
})}
-
-
-
-
-
- |
+ {!readOnly && (
+
+
+
+
+
+ |
+ )}
))}
@@ -729,10 +754,12 @@ function SupplierAttachmentsCell({
evaluationId,
supplierRowId,
attachments,
+ readOnly = false,
}: {
evaluationId: string
supplierRowId: string
attachments: PeAttachment[]
+ readOnly?: boolean
}) {
const qc = useQueryClient()
const fileInputRef = useRef(null)
@@ -809,32 +836,36 @@ function SupplierAttachmentsCell({
{PeAttachmentPurposeLabel[a.purpose] ?? ''}
-
+ {!readOnly && (
+
+ )}
))}
-
-
-
-
+ {!readOnly && (
+
+
+
+
+ )}
)
}
diff --git a/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx b/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
index a953083..fa017a7 100644
--- a/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
+++ b/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
@@ -204,6 +204,7 @@ export function PurchaseEvaluationsListPage() {
evaluation={detail.data}
onBack={() => setParam('id', null)}
onDelete={() => del.mutate(detail.data!.id)}
+ readOnly={pendingMe}
/>
)}
diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx
index a6c9e41..056733c 100644
--- a/fe-user/src/components/pe/PeDetailTabs.tsx
+++ b/fe-user/src/components/pe/PeDetailTabs.tsx
@@ -39,10 +39,13 @@ export function PeDetailTabs({
evaluation,
onBack,
onDelete,
+ readOnly = false,
}: {
evaluation: PeDetailBundle
onBack: () => void
onDelete: () => void
+ /** Menu "Duyệt" (pendingMe=1) — ẩn mọi action thêm/sửa/xóa, chỉ xem + duyệt phase. */
+ readOnly?: boolean
}) {
const navigate = useNavigate()
const isDraft = evaluation.phase === PurchaseEvaluationPhase.DangSoanThao
@@ -61,6 +64,11 @@ export function PeDetailTabs({
>
{PurchaseEvaluationPhaseLabel[evaluation.phase]}
+ {readOnly && (
+
+ chế độ duyệt
+
+ )}
{evaluation.maPhieu ?? '—'}
@@ -72,7 +80,7 @@ export function PeDetailTabs({
- {isDraft && (
+ {isDraft && !readOnly && (
<>
@@ -131,8 +139,8 @@ export function PeHistorySection({ ev }: { ev: PeDetailBundle }) {
}
// ===== Tab: Thông tin =====
-function InfoTab({ ev }: { ev: PeDetailBundle }) {
- const canCreateContract = ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId
+function InfoTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
+ const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.selectedSupplierId
const [createOpen, setCreateOpen] = useState(false)
return (
@@ -236,7 +244,7 @@ function Field({ label, value }: { label: string; value: React.ReactNode }) {
}
// ===== Tab: NCC =====
-function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
+function SuppliersTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
const qc = useQueryClient()
const [open, setOpen] = useState(false)
const [editRow, setEditRow] = useState
(null)
@@ -255,13 +263,17 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
return (
-
-
-
+ {!readOnly && (
+
+
+
+ )}
{ev.suppliers.length === 0 ? (
-
Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.
+
+ {readOnly ? 'Chưa có NCC.' : 'Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.'}
+
) : (
@@ -271,7 +283,7 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
| Liên hệ |
Điều khoản TT |
File đính kèm |
- |
+ {!readOnly && | }
@@ -281,6 +293,9 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
{s.supplierName}
{s.displayName && {s.displayName}
}
{s.note && {s.note}
}
+ {readOnly && ev.selectedSupplierId === s.supplierId && (
+ ✓ NCC được chọn
+ )}
{s.contactName && {s.contactName} }
@@ -293,38 +308,41 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
evaluationId={ev.id}
supplierRowId={s.id}
attachments={ev.attachments.filter(a => a.purchaseEvaluationSupplierId === s.id)}
+ readOnly={readOnly}
/>
|
-
-
-
-
-
-
- |
+ {!readOnly && (
+
+
+
+
+
+
+ |
+ )}
))}
@@ -431,7 +449,7 @@ function EditSupplierDialog({ evaluationId, row, onClose }: { evaluationId: stri
}
// ===== Tab: Hạng mục + Báo giá (matrix) =====
-function ItemsTab({ ev }: { ev: PeDetailBundle }) {
+function ItemsTab({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) {
const qc = useQueryClient()
const [addOpen, setAddOpen] = useState(false)
const [editDetail, setEditDetail] = useState(null)
@@ -451,12 +469,16 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
{ev.suppliers.length === 0
- ? 'Thêm NCC ở tab "NCC" trước khi nhập báo giá.'
- : `${ev.details.length} hạng mục × ${ev.suppliers.length} NCC — click ô để nhập báo giá.`}
+ ? (readOnly ? 'Chưa có NCC tham gia.' : 'Thêm NCC ở tab "NCC" trước khi nhập báo giá.')
+ : readOnly
+ ? `${ev.details.length} hạng mục × ${ev.suppliers.length} NCC`
+ : `${ev.details.length} hạng mục × ${ev.suppliers.length} NCC — click ô để nhập báo giá.`}
-
+ {!readOnly && (
+
+ )}
{ev.details.length === 0 ? (
@@ -475,7 +497,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
{s.displayName ?? s.supplierName}
))}
- |
+ {!readOnly && | }
@@ -493,9 +515,10 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
return (
setQuoteEdit({ detail: d, supplier: s, existing: q })}
+ onClick={readOnly ? undefined : () => setQuoteEdit({ detail: d, supplier: s, existing: q })}
className={cn(
- 'cursor-pointer border-r border-slate-200 px-2 py-2 text-right font-mono transition hover:bg-brand-50',
+ 'border-r border-slate-200 px-2 py-2 text-right font-mono transition',
+ !readOnly && 'cursor-pointer hover:bg-brand-50',
q?.isSelected && 'bg-emerald-50 font-semibold text-emerald-700',
)}
>
@@ -503,16 +526,18 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
)
})}
-
-
-
-
-
- |
+ {!readOnly && (
+
+
+
+
+
+ |
+ )}
))}
@@ -729,10 +754,12 @@ function SupplierAttachmentsCell({
evaluationId,
supplierRowId,
attachments,
+ readOnly = false,
}: {
evaluationId: string
supplierRowId: string
attachments: PeAttachment[]
+ readOnly?: boolean
}) {
const qc = useQueryClient()
const fileInputRef = useRef(null)
@@ -809,32 +836,36 @@ function SupplierAttachmentsCell({
{PeAttachmentPurposeLabel[a.purpose] ?? ''}
-
+ {!readOnly && (
+
+ )}
))}
-
-
-
-
+ {!readOnly && (
+
+
+
+
+ )}
)
}
diff --git a/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx b/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx
index a953083..fa017a7 100644
--- a/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx
+++ b/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx
@@ -204,6 +204,7 @@ export function PurchaseEvaluationsListPage() {
evaluation={detail.data}
onBack={() => setParam('id', null)}
onDelete={() => del.mutate(detail.data!.id)}
+ readOnly={pendingMe}
/>
)}