[CLAUDE] FE: PE co gap PILL (do PRO / xanh CCM) dong bo moi danh sach + inbox
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m46s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m46s
Surface co gap GAP tren MOI danh sach phieu de CEO/nguoi duyet thay urgency ngay tren danh sach khong can mo phieu (anh Kiet FDC). Cay danh sach: emoji -> pill labeled. Khung 'Danh sach phieu' cho duyet (Workspace + picker) + inbox 'phieu cho toi duyet': them pill (truoc day thieu). NEW PeUrgentChips.tsx single-source-of-truth (style khop badge detail S69) x2 app. FE-only, 0 BE, 0 migration — DTO da co IsUrgentByPro/Ccm tu S69 Mig 53. Build PASS x2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -25,6 +25,7 @@ import {
|
|||||||
isEditablePhase,
|
isEditablePhase,
|
||||||
type PeListItem,
|
type PeListItem,
|
||||||
} from '@/types/purchaseEvaluation'
|
} from '@/types/purchaseEvaluation'
|
||||||
|
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
|
||||||
|
|
||||||
export function PeListPanel({
|
export function PeListPanel({
|
||||||
typeFilter,
|
typeFilter,
|
||||||
@ -155,7 +156,10 @@ export function PeListPanel({
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-[13px] font-medium text-slate-900">{p.tenGoiThau}</div>
|
<div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900">
|
||||||
|
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
|
||||||
|
<span className="truncate">{p.tenGoiThau}</span>
|
||||||
|
</div>
|
||||||
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
|
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
|
||||||
<span className="font-mono">{p.maPhieu ?? '—'}</span>
|
<span className="font-mono">{p.maPhieu ?? '—'}</span>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
|
|||||||
38
fe-admin/src/components/pe/PeUrgentChips.tsx
Normal file
38
fe-admin/src/components/pe/PeUrgentChips.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// PE — chip cờ gấp: 🔴 GẤP (PRO) / 🟢 GẤP (CCM).
|
||||||
|
// SINGLE SOURCE OF TRUTH cho pill cờ gấp dùng ở MỌI danh sách phiếu (cây danh
|
||||||
|
// sách + picker workspace/chờ-duyệt + inbox) để CEO/người duyệt nhìn thấy
|
||||||
|
// urgency "từ ngoài" mà KHÔNG cần mở phiếu (anh Kiệt FDC, S77). Style khớp badge
|
||||||
|
// header PeDetailTabs (đã live S69) → đồng bộ toàn app, đổi 1 chỗ áp mọi nơi.
|
||||||
|
import { cn } from '@/lib/cn'
|
||||||
|
|
||||||
|
export function PeUrgentChips({
|
||||||
|
isUrgentByPro,
|
||||||
|
isUrgentByCcm,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
isUrgentByPro?: boolean
|
||||||
|
isUrgentByCcm?: boolean
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
if (!isUrgentByPro && !isUrgentByCcm) return null
|
||||||
|
return (
|
||||||
|
<span className={cn('inline-flex shrink-0 items-center gap-1', className)}>
|
||||||
|
{isUrgentByPro && (
|
||||||
|
<span
|
||||||
|
className="inline-flex shrink-0 items-center rounded bg-red-100 px-1.5 py-0.5 text-[11px] font-semibold text-red-700"
|
||||||
|
title="GẤP — Phòng Cung ứng (PRO) đánh dấu"
|
||||||
|
>
|
||||||
|
🔴 GẤP (PRO)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isUrgentByCcm && (
|
||||||
|
<span
|
||||||
|
className="inline-flex shrink-0 items-center rounded bg-green-100 px-1.5 py-0.5 text-[11px] font-semibold text-green-700"
|
||||||
|
title="GẤP — Phòng Kiểm soát chi phí (CCM) đánh dấu"
|
||||||
|
>
|
||||||
|
🟢 GẤP (CCM)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ import {
|
|||||||
type PeListItem,
|
type PeListItem,
|
||||||
} from '@/types/purchaseEvaluation'
|
} from '@/types/purchaseEvaluation'
|
||||||
import { PeDetailTabs } from '@/components/pe/PeDetailTabs'
|
import { PeDetailTabs } from '@/components/pe/PeDetailTabs'
|
||||||
|
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
|
||||||
import { PeWorkflowPanel } from '@/components/pe/PeWorkflowPanel'
|
import { PeWorkflowPanel } from '@/components/pe/PeWorkflowPanel'
|
||||||
|
|
||||||
export function PurchaseEvaluationsListPage() {
|
export function PurchaseEvaluationsListPage() {
|
||||||
@ -350,9 +351,8 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
{/* Plan AG6 — compact card 3 row: title+badge / mã+time / drafter+dept+contract */}
|
{/* Plan AG6 — compact card 3 row: title+badge / mã+time / drafter+dept+contract */}
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900">
|
<div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900">
|
||||||
{/* S69 — chấm gấp: ĐỎ (PRO) / XANH-lá (CCM) cạnh tên gói. */}
|
{/* S77 — cờ gấp PILL đồng bộ mọi danh sách: 🔴 GẤP (PRO) / 🟢 GẤP (CCM). */}
|
||||||
{p.isUrgentByPro && <span className="shrink-0 text-[11px]" title="GẤP — Phòng Cung ứng (PRO)">🔴</span>}
|
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
|
||||||
{p.isUrgentByCcm && <span className="shrink-0 text-[11px]" title="GẤP — Phòng Kiểm soát chi phí (CCM)">🟢</span>}
|
|
||||||
<span className="truncate">{p.tenGoiThau}</span>
|
<span className="truncate">{p.tenGoiThau}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
isEditablePhase,
|
isEditablePhase,
|
||||||
type PeListItem,
|
type PeListItem,
|
||||||
} from '@/types/purchaseEvaluation'
|
} from '@/types/purchaseEvaluation'
|
||||||
|
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
|
||||||
|
|
||||||
export function PeListPanel({
|
export function PeListPanel({
|
||||||
typeFilter,
|
typeFilter,
|
||||||
@ -155,7 +156,10 @@ export function PeListPanel({
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-[13px] font-medium text-slate-900">{p.tenGoiThau}</div>
|
<div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900">
|
||||||
|
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
|
||||||
|
<span className="truncate">{p.tenGoiThau}</span>
|
||||||
|
</div>
|
||||||
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
|
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
|
||||||
<span className="font-mono">{p.maPhieu ?? '—'}</span>
|
<span className="font-mono">{p.maPhieu ?? '—'}</span>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
|
|||||||
38
fe-user/src/components/pe/PeUrgentChips.tsx
Normal file
38
fe-user/src/components/pe/PeUrgentChips.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// PE — chip cờ gấp: 🔴 GẤP (PRO) / 🟢 GẤP (CCM).
|
||||||
|
// SINGLE SOURCE OF TRUTH cho pill cờ gấp dùng ở MỌI danh sách phiếu (cây danh
|
||||||
|
// sách + picker workspace/chờ-duyệt + inbox) để CEO/người duyệt nhìn thấy
|
||||||
|
// urgency "từ ngoài" mà KHÔNG cần mở phiếu (anh Kiệt FDC, S77). Style khớp badge
|
||||||
|
// header PeDetailTabs (đã live S69) → đồng bộ toàn app, đổi 1 chỗ áp mọi nơi.
|
||||||
|
import { cn } from '@/lib/cn'
|
||||||
|
|
||||||
|
export function PeUrgentChips({
|
||||||
|
isUrgentByPro,
|
||||||
|
isUrgentByCcm,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
isUrgentByPro?: boolean
|
||||||
|
isUrgentByCcm?: boolean
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
if (!isUrgentByPro && !isUrgentByCcm) return null
|
||||||
|
return (
|
||||||
|
<span className={cn('inline-flex shrink-0 items-center gap-1', className)}>
|
||||||
|
{isUrgentByPro && (
|
||||||
|
<span
|
||||||
|
className="inline-flex shrink-0 items-center rounded bg-red-100 px-1.5 py-0.5 text-[11px] font-semibold text-red-700"
|
||||||
|
title="GẤP — Phòng Cung ứng (PRO) đánh dấu"
|
||||||
|
>
|
||||||
|
🔴 GẤP (PRO)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isUrgentByCcm && (
|
||||||
|
<span
|
||||||
|
className="inline-flex shrink-0 items-center rounded bg-green-100 px-1.5 py-0.5 text-[11px] font-semibold text-green-700"
|
||||||
|
title="GẤP — Phòng Kiểm soát chi phí (CCM) đánh dấu"
|
||||||
|
>
|
||||||
|
🟢 GẤP (CCM)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ import {
|
|||||||
PurchaseEvaluationTypeLabel,
|
PurchaseEvaluationTypeLabel,
|
||||||
type PeListItem,
|
type PeListItem,
|
||||||
} from '@/types/purchaseEvaluation'
|
} from '@/types/purchaseEvaluation'
|
||||||
|
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
|
||||||
|
|
||||||
const fmtMoney = (v: number) => v.toLocaleString('vi-VN')
|
const fmtMoney = (v: number) => v.toLocaleString('vi-VN')
|
||||||
|
|
||||||
@ -285,8 +286,9 @@ export function InboxPage() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-[13px] font-medium text-slate-900">
|
<div className="flex min-w-0 items-center gap-1 text-[13px] font-medium text-slate-900">
|
||||||
{p.tenGoiThau}
|
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
|
||||||
|
<span className="truncate">{p.tenGoiThau}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
|
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
|
||||||
<span className="font-mono">{p.maPhieu ?? '—'}</span>
|
<span className="font-mono">{p.maPhieu ?? '—'}</span>
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import {
|
|||||||
type PeListItem,
|
type PeListItem,
|
||||||
} from '@/types/purchaseEvaluation'
|
} from '@/types/purchaseEvaluation'
|
||||||
import { PeDetailTabs } from '@/components/pe/PeDetailTabs'
|
import { PeDetailTabs } from '@/components/pe/PeDetailTabs'
|
||||||
|
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
|
||||||
import { PeWorkflowPanel } from '@/components/pe/PeWorkflowPanel'
|
import { PeWorkflowPanel } from '@/components/pe/PeWorkflowPanel'
|
||||||
|
|
||||||
export function PurchaseEvaluationsListPage() {
|
export function PurchaseEvaluationsListPage() {
|
||||||
@ -350,9 +351,8 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
{/* Plan AG6 — compact card 3 row: title+badge / mã+time / drafter+dept+contract */}
|
{/* Plan AG6 — compact card 3 row: title+badge / mã+time / drafter+dept+contract */}
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900">
|
<div className="flex min-w-0 flex-1 items-center gap-1 truncate text-[13px] font-medium text-slate-900">
|
||||||
{/* S69 — chấm gấp: ĐỎ (PRO) / XANH-lá (CCM) cạnh tên gói. */}
|
{/* S77 — cờ gấp PILL đồng bộ mọi danh sách: 🔴 GẤP (PRO) / 🟢 GẤP (CCM). */}
|
||||||
{p.isUrgentByPro && <span className="shrink-0 text-[11px]" title="GẤP — Phòng Cung ứng (PRO)">🔴</span>}
|
<PeUrgentChips isUrgentByPro={p.isUrgentByPro} isUrgentByCcm={p.isUrgentByCcm} />
|
||||||
{p.isUrgentByCcm && <span className="shrink-0 text-[11px]" title="GẤP — Phòng Kiểm soát chi phí (CCM)">🟢</span>}
|
|
||||||
<span className="truncate">{p.tenGoiThau}</span>
|
<span className="truncate">{p.tenGoiThau}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|||||||
Reference in New Issue
Block a user