[CLAUDE] PE: readOnly mode cho menu 'Duyet' (pendingMe=1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
User request: 'Menu duyet cua NCC -> chi de duyet thoi nhe khong co cac action them sua j vao'. Them readOnly prop vao PeDetailTabs — propagate xuong 3 sub-component (InfoTab / SuppliersTab / ItemsTab) + SupplierAttachmentsCell. URL pendingMe=1 (menu 'Duyet') → set readOnly=true. Hide khi readOnly: - Header: [Sua header] [Xoa] button - SuppliersTab: [+ Them NCC] button + action column (Check winner/Pencil edit/Trash delete per row) - ItemsTab: [+ Them hang muc] button + action column (Pencil/Trash per row) + click cell bao gia popup - SupplierAttachmentsCell: [+ Them file] button + Trash delete icon (giu download tren file name) - InfoTab: [Tao HD tu phieu] button Giu: - Moi thong tin doc-only - Download file dinh kem (click ten file) - Panel 3: Quy trinh + transition button (de action duyet phase) - [Dong] button - Chip 'che do duyet' gan phase badge de user biet mode Mirror fe-admin + fe-user.
This commit is contained in:
@ -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]}
|
||||
</span>
|
||||
{readOnly && (
|
||||
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[11px] font-medium text-slate-600">
|
||||
chế độ duyệt
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-0.5 flex flex-wrap items-center gap-2 text-[12px] text-slate-500">
|
||||
<span className="font-mono">{evaluation.maPhieu ?? '—'}</span>
|
||||
@ -72,7 +80,7 @@ export function PeDetailTabs({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{isDraft && (
|
||||
{isDraft && !readOnly && (
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => navigate(`/purchase-evaluations/new?id=${evaluation.id}`)} className="gap-1.5 text-xs">
|
||||
<Pencil className="h-3.5 w-3.5" /> Sửa header
|
||||
@ -88,13 +96,13 @@ export function PeDetailTabs({
|
||||
|
||||
<div className="divide-y divide-slate-200">
|
||||
<Section title="Thông tin">
|
||||
<InfoTab ev={evaluation} />
|
||||
<InfoTab ev={evaluation} readOnly={readOnly} />
|
||||
</Section>
|
||||
<Section title={`NCC tham gia (${evaluation.suppliers.length})`}>
|
||||
<SuppliersTab ev={evaluation} />
|
||||
<SuppliersTab ev={evaluation} readOnly={readOnly} />
|
||||
</Section>
|
||||
<Section title={`Hạng mục + Báo giá (${evaluation.details.length})`}>
|
||||
<ItemsTab ev={evaluation} />
|
||||
<ItemsTab ev={evaluation} readOnly={readOnly} />
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
@ -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 (
|
||||
<div className="space-y-4">
|
||||
@ -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<PeSupplier | null>(null)
|
||||
@ -255,13 +263,17 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!readOnly && (
|
||||
<div className="mb-3 flex justify-end">
|
||||
<Button onClick={() => setOpen(true)} className="gap-1.5 text-xs">
|
||||
<Plus className="h-3.5 w-3.5" /> Thêm NCC
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{ev.suppliers.length === 0 ? (
|
||||
<p className="text-sm text-slate-500">Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.</p>
|
||||
<p className="text-sm text-slate-500">
|
||||
{readOnly ? 'Chưa có NCC.' : 'Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.'}
|
||||
</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full text-sm">
|
||||
@ -271,7 +283,7 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
<th className="px-3 py-2 text-left">Liên hệ</th>
|
||||
<th className="px-3 py-2 text-left">Điều khoản TT</th>
|
||||
<th className="px-3 py-2 text-left">File đính kèm</th>
|
||||
<th className="px-3 py-2"></th>
|
||||
{!readOnly && <th className="px-3 py-2"></th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
@ -281,6 +293,9 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
<div className="font-medium text-slate-900">{s.supplierName}</div>
|
||||
{s.displayName && <div className="text-[11px] text-slate-500">{s.displayName}</div>}
|
||||
{s.note && <div className="mt-0.5 text-[11px] text-amber-600">{s.note}</div>}
|
||||
{readOnly && ev.selectedSupplierId === s.supplierId && (
|
||||
<div className="mt-0.5 text-[11px] font-medium text-emerald-700">✓ NCC được chọn</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-[12px] text-slate-600">
|
||||
{s.contactName && <div>{s.contactName}</div>}
|
||||
@ -293,8 +308,10 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
evaluationId={ev.id}
|
||||
supplierRowId={s.id}
|
||||
attachments={ev.attachments.filter(a => a.purchaseEvaluationSupplierId === s.id)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</td>
|
||||
{!readOnly && (
|
||||
<td className="px-3 py-2">
|
||||
<div className="flex justify-end gap-1">
|
||||
<button
|
||||
@ -325,6 +342,7 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -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<PeDetailRow | null>(null)
|
||||
@ -451,12 +469,16 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<p className="text-xs text-slate-500">
|
||||
{ev.suppliers.length === 0
|
||||
? 'Thêm NCC ở tab "NCC" trước khi 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á.`}
|
||||
</p>
|
||||
{!readOnly && (
|
||||
<Button onClick={() => setAddOpen(true)} className="gap-1.5 text-xs">
|
||||
<Plus className="h-3.5 w-3.5" /> Thêm hạng mục
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{ev.details.length === 0 ? (
|
||||
@ -475,7 +497,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
{s.displayName ?? s.supplierName}
|
||||
</th>
|
||||
))}
|
||||
<th className="px-2 py-2"></th>
|
||||
{!readOnly && <th className="px-2 py-2"></th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
@ -493,9 +515,10 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
return (
|
||||
<td
|
||||
key={s.id}
|
||||
onClick={() => 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,6 +526,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
{!readOnly && (
|
||||
<td className="px-2 py-2">
|
||||
<div className="flex gap-1">
|
||||
<button onClick={() => setEditDetail(d)} className="rounded px-1 py-0.5 text-slate-500 hover:bg-slate-100">
|
||||
@ -513,6 +537,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -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<HTMLInputElement>(null)
|
||||
@ -809,6 +836,7 @@ function SupplierAttachmentsCell({
|
||||
<span className="shrink-0 rounded bg-slate-200 px-1 text-[9px] text-slate-600">
|
||||
{PeAttachmentPurposeLabel[a.purpose] ?? ''}
|
||||
</span>
|
||||
{!readOnly && (
|
||||
<button
|
||||
onClick={() => { if (confirm(`Xóa "${a.fileName}"?`)) del.mutate(a.id) }}
|
||||
className="shrink-0 rounded px-1 text-red-500 hover:bg-red-50"
|
||||
@ -816,8 +844,10 @@ function SupplierAttachmentsCell({
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{!readOnly && (
|
||||
<div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
@ -835,6 +865,7 @@ function SupplierAttachmentsCell({
|
||||
{upload.isPending ? 'Đang tải…' : '+ Thêm file'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -204,6 +204,7 @@ export function PurchaseEvaluationsListPage() {
|
||||
evaluation={detail.data}
|
||||
onBack={() => setParam('id', null)}
|
||||
onDelete={() => del.mutate(detail.data!.id)}
|
||||
readOnly={pendingMe}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
@ -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]}
|
||||
</span>
|
||||
{readOnly && (
|
||||
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[11px] font-medium text-slate-600">
|
||||
chế độ duyệt
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-0.5 flex flex-wrap items-center gap-2 text-[12px] text-slate-500">
|
||||
<span className="font-mono">{evaluation.maPhieu ?? '—'}</span>
|
||||
@ -72,7 +80,7 @@ export function PeDetailTabs({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{isDraft && (
|
||||
{isDraft && !readOnly && (
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => navigate(`/purchase-evaluations/new?id=${evaluation.id}`)} className="gap-1.5 text-xs">
|
||||
<Pencil className="h-3.5 w-3.5" /> Sửa header
|
||||
@ -88,13 +96,13 @@ export function PeDetailTabs({
|
||||
|
||||
<div className="divide-y divide-slate-200">
|
||||
<Section title="Thông tin">
|
||||
<InfoTab ev={evaluation} />
|
||||
<InfoTab ev={evaluation} readOnly={readOnly} />
|
||||
</Section>
|
||||
<Section title={`NCC tham gia (${evaluation.suppliers.length})`}>
|
||||
<SuppliersTab ev={evaluation} />
|
||||
<SuppliersTab ev={evaluation} readOnly={readOnly} />
|
||||
</Section>
|
||||
<Section title={`Hạng mục + Báo giá (${evaluation.details.length})`}>
|
||||
<ItemsTab ev={evaluation} />
|
||||
<ItemsTab ev={evaluation} readOnly={readOnly} />
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
@ -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 (
|
||||
<div className="space-y-4">
|
||||
@ -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<PeSupplier | null>(null)
|
||||
@ -255,13 +263,17 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!readOnly && (
|
||||
<div className="mb-3 flex justify-end">
|
||||
<Button onClick={() => setOpen(true)} className="gap-1.5 text-xs">
|
||||
<Plus className="h-3.5 w-3.5" /> Thêm NCC
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{ev.suppliers.length === 0 ? (
|
||||
<p className="text-sm text-slate-500">Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.</p>
|
||||
<p className="text-sm text-slate-500">
|
||||
{readOnly ? 'Chưa có NCC.' : 'Chưa có NCC. Thêm NCC để bắt đầu so sánh giá.'}
|
||||
</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full text-sm">
|
||||
@ -271,7 +283,7 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
<th className="px-3 py-2 text-left">Liên hệ</th>
|
||||
<th className="px-3 py-2 text-left">Điều khoản TT</th>
|
||||
<th className="px-3 py-2 text-left">File đính kèm</th>
|
||||
<th className="px-3 py-2"></th>
|
||||
{!readOnly && <th className="px-3 py-2"></th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
@ -281,6 +293,9 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
<div className="font-medium text-slate-900">{s.supplierName}</div>
|
||||
{s.displayName && <div className="text-[11px] text-slate-500">{s.displayName}</div>}
|
||||
{s.note && <div className="mt-0.5 text-[11px] text-amber-600">{s.note}</div>}
|
||||
{readOnly && ev.selectedSupplierId === s.supplierId && (
|
||||
<div className="mt-0.5 text-[11px] font-medium text-emerald-700">✓ NCC được chọn</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-[12px] text-slate-600">
|
||||
{s.contactName && <div>{s.contactName}</div>}
|
||||
@ -293,8 +308,10 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
evaluationId={ev.id}
|
||||
supplierRowId={s.id}
|
||||
attachments={ev.attachments.filter(a => a.purchaseEvaluationSupplierId === s.id)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</td>
|
||||
{!readOnly && (
|
||||
<td className="px-3 py-2">
|
||||
<div className="flex justify-end gap-1">
|
||||
<button
|
||||
@ -325,6 +342,7 @@ function SuppliersTab({ ev }: { ev: PeDetailBundle }) {
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -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<PeDetailRow | null>(null)
|
||||
@ -451,12 +469,16 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<p className="text-xs text-slate-500">
|
||||
{ev.suppliers.length === 0
|
||||
? 'Thêm NCC ở tab "NCC" trước khi 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á.`}
|
||||
</p>
|
||||
{!readOnly && (
|
||||
<Button onClick={() => setAddOpen(true)} className="gap-1.5 text-xs">
|
||||
<Plus className="h-3.5 w-3.5" /> Thêm hạng mục
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{ev.details.length === 0 ? (
|
||||
@ -475,7 +497,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
{s.displayName ?? s.supplierName}
|
||||
</th>
|
||||
))}
|
||||
<th className="px-2 py-2"></th>
|
||||
{!readOnly && <th className="px-2 py-2"></th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
@ -493,9 +515,10 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
return (
|
||||
<td
|
||||
key={s.id}
|
||||
onClick={() => 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,6 +526,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
{!readOnly && (
|
||||
<td className="px-2 py-2">
|
||||
<div className="flex gap-1">
|
||||
<button onClick={() => setEditDetail(d)} className="rounded px-1 py-0.5 text-slate-500 hover:bg-slate-100">
|
||||
@ -513,6 +537,7 @@ function ItemsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -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<HTMLInputElement>(null)
|
||||
@ -809,6 +836,7 @@ function SupplierAttachmentsCell({
|
||||
<span className="shrink-0 rounded bg-slate-200 px-1 text-[9px] text-slate-600">
|
||||
{PeAttachmentPurposeLabel[a.purpose] ?? ''}
|
||||
</span>
|
||||
{!readOnly && (
|
||||
<button
|
||||
onClick={() => { if (confirm(`Xóa "${a.fileName}"?`)) del.mutate(a.id) }}
|
||||
className="shrink-0 rounded px-1 text-red-500 hover:bg-red-50"
|
||||
@ -816,8 +844,10 @@ function SupplierAttachmentsCell({
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{!readOnly && (
|
||||
<div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
@ -835,6 +865,7 @@ function SupplierAttachmentsCell({
|
||||
{upload.isPending ? 'Đang tải…' : '+ Thêm file'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -204,6 +204,7 @@ export function PurchaseEvaluationsListPage() {
|
||||
evaluation={detail.data}
|
||||
onBack={() => setParam('id', null)}
|
||||
onDelete={() => del.mutate(detail.data!.id)}
|
||||
readOnly={pendingMe}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user