[CLAUDE] Contract: KHKK UAT vong-3 — khung the, cay chi PE da duyet, don chu thua (0-mig)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m29s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m29s
4 defect anh gui khi soi prod:
=== 1. TRA LAI KHUNG THE (anh: "no mat luon cai khung ben ngoai roi, y tao bo cai vien xanh
thoi, con bo khung table thuan van nen giu lai") ===
🔴 LOI CUA LEAD, khai thang: `.card-accent` (`index.css:112`) goi HAI thu —
① KHUNG the: bo goc 0.75rem + vien 1px #e9eef4 + nen trang + do bong -> anh GIU
② DAI ACCENT doc 3px ben trai (`border-left`) -> anh BO
Luot truoc lead go CA CLASS => mat luon ①, panel troi khong khung. Nay dung lai ① bang
utility thuan, GIU DUNG so do goc (#e9eef4, rounded-xl) de khong lech tong voi cac the khac
con dung `.card-accent`.
🔸 Bo luon hover-nhac-the cua class goc: hieu-ung do thiet-ke cho KPI-card nho; dat len panel
noi-dung cao ca man thi nhap-nhay vo co moi lan re chuot.
🔸 Bai: "go class" ≠ "go thuoc tinh". Class gop N thuoc tinh thi go ca class la go ca N —
phai tach ra cai nao owner thuc su khong muon.
=== 2. CAY MAN THAO TAC CHI GIU PE DA DUYET (anh: "cho nay chi can NCC da duyet la dc roi
nhe, con lai bo het di") ===
`PipelineTreePanel` +2 prop OPT-IN:
· `approvedPeOnly` — chi phieu PE phase DaDuyet. Man nay de CHON PE ma lap ke hoach; phieu
chua duyet xong thi lap khong duoc, hien ra la moi bam nham.
🔴 Loc phase TRUOC khi dung cay => so dem o moi tang (Nam/Du an/Hang muc) tu khop tap da loc.
Loc SAU khi dung thi badge dem se NOI DOI.
· `stagesOnly={[1]}` — chi folder Duyet NCC; 3 giai-doan kia (Ke hoach HD + 8 ngan nhom ·
Duyet HD · HD cung) la nhieu o man nay. Loc HIEN THI, KHONG dung bo dung.
🔴 CA HAI OPT-IN. Hau-kiem 4 host cay module Hop dong: approvedPeOnly=0, stagesOnly=0 => 0 doi
hanh vi.
=== 3. DUA O HANG MUC (DU AN) SANG COT PHAI (anh: "dua qua ben phai nhe") ===
Luoi 2 cot doc theo chieu NGANG nen THU TU KHAI quyet dinh cot. Xep lai de Hang muc (du an)
nam NGAY DUOI Du an o cot PHAI — hai thu la mot cap (du an -> goi viec cua no), tach 2 cot
thi mat phai nhay cheo.
=== 4. BO 2 DONG CHU THUA (anh: "Bo het may chu thua nay di") ===
· "Da an ke hoach o trang thai Da duyet — xem du o menu…" (chan tang KHKK): giai-thich CO-CHE
loc ben trong; nguoi dung chi can thay dung phieu.
· "File can cu ho so / shopdrawing cua ke hoach. Gan vao 1 can cu…" (dau muc 3): nut ⬆ moi dong
can cu da tu noi no lam gi (co `title`), khong can cau huong-dan chinh inh tren dau khoi.
Verify dist bang NOI DUNG: khung the `border-[#e9eef4]` = 1/1 ca 2 app; 2 chuoi CU = 0/0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -359,12 +359,16 @@ export function KhkkDetailContent({
|
||||
// `plan.workflowSteps` VẪN có trong DTO (panel 3 dùng) — chỉ bỏ chỗ VẼ ở đây.
|
||||
|
||||
return (
|
||||
// [S175 — anh: "Viền xanh này trong thao tác → Chọn kế hoạch"] GỠ `card-accent`.
|
||||
// Class này vẽ DẢI ACCENT DỌC 3px bên trái (`index.css:112`); ở màn Thao tác nó nằm sát
|
||||
// mép panel 2 nên đọc ra thành một vệt xanh đậm chạy dọc màn hình, không ra "thẻ".
|
||||
// 🔴 GỠ HẲN CLASS, KHÔNG đè `border-0`: `.card-accent` nằm NGOÀI `@layer` nên thắng
|
||||
// utility Tailwind v4 — đè là vô hiệu (gotcha #66, đã cắn thật ở đợt B với panel duyệt).
|
||||
<div className="overflow-hidden">
|
||||
// [S175 — anh: "Viền xanh này trong thao tác" → rồi "ý tao bỏ cái viền xanh thôi, còn bộ
|
||||
// khung table thuần vẫn nên giữ lại"] `.card-accent` (`index.css:112`) gói **HAI thứ**:
|
||||
// ① KHUNG thẻ — bo góc 0.75rem + viền 1px `#e9eef4` + nền trắng + đổ bóng → anh GIỮ
|
||||
// ② DẢI ACCENT DỌC 3px bên trái (`border-left`) → anh BỎ
|
||||
// 🔴 Lượt trước lead gỡ CẢ CLASS ⇒ mất luôn ①, panel trôi không khung. Nay dựng lại ①
|
||||
// bằng utility thuần, giữ đúng số đo gốc (`#e9eef4`, `rounded-xl`) để không lệch tông với
|
||||
// các thẻ khác còn dùng `.card-accent`.
|
||||
// 🔸 Bỏ luôn `hover` nhấc-thẻ của class gốc: hiệu-ứng đó thiết-kế cho KPI-card nhỏ, đặt
|
||||
// lên panel nội-dung cao cả màn thì nhấp-nháy vô cớ mỗi lần rê chuột.
|
||||
<div className="overflow-hidden rounded-xl border border-[#e9eef4] bg-white shadow-sm">
|
||||
{/* Dải 4 giai-đoạn — GĐ2 đang sáng. Cùng component với màn Duyệt NCC (không nhân bản). */}
|
||||
<PePipelineStrip current={2} />
|
||||
|
||||
@ -429,15 +433,19 @@ export function KhkkDetailContent({
|
||||
· "Thêm hạng mục vào đây" → +ô **Hạng mục** ngay cạnh Dự án
|
||||
· "Đưa lên trên phiếu" → **NCC** + mã phiếu PE dời lên dải header (xem `:361`) */}
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-4 md:grid-cols-2">
|
||||
{/* [S175 — anh: "đưa qua bên phải nhé"] Lưới 2 cột đọc theo chiều NGANG
|
||||
(`grid-cols-2` xếp trái→phải), nên THỨ TỰ KHAI quyết định cột.
|
||||
Xếp lại để **Hạng mục (dự án) nằm NGAY DƯỚI Dự án ở cột PHẢI** — hai thứ này
|
||||
là một cặp (dự án ⟶ gói việc của nó), tách 2 cột thì mắt phải nhảy chéo. */}
|
||||
<Field label="Mã kế hoạch" value={plan.maKeHoach ?? '—'} mono />
|
||||
<Field label="Dự án" value={plan.projectName ?? '—'} />
|
||||
<Field label="Người soạn" value={plan.drafterFullName ?? '—'} />
|
||||
{/* [S175 — anh: "hạng mục của Dự án, Duyệt NCC ấy"] Đây là HẠNG MỤC CÔNG VIỆC của
|
||||
dự án, lấy từ phiếu Duyệt NCC nguồn (`PE.WorkItemId`).
|
||||
🔴 KHÁC hẳn `line.tenHangMuc` — cái đó là danh-mục SP-002 dùng phân loại HỢP
|
||||
ĐỒNG (86 dòng, chọn ở mục 2). Hai thứ trùng tên tiếng Việt nhưng khác trục;
|
||||
lượt trước lead bày nhầm cái SP-002 ở đây nên anh bảo "Bỏ đi". */}
|
||||
<Field label="Hạng mục (dự án)" value={plan.peWorkItemName ?? '—'} />
|
||||
<Field label="Người soạn" value={plan.drafterFullName ?? '—'} />
|
||||
<Field label="Ngày tạo" value={formatDateTime(plan.createdAt)} />
|
||||
</div>
|
||||
|
||||
@ -701,11 +709,9 @@ export function KhkkDetailContent({
|
||||
+ `:3601-3608`): dòng chữ mô-tả ở TRÊN, nút viền ĐỨT nền brand-nhạt ở DƯỚI danh
|
||||
sách — thay cho nút đặc nằm ở góc header. Nút viền đứt đọc ra là "chỗ thả thêm
|
||||
vào", nút đặc ở header đọc ra là "hành động của cả khối"; đây là cái thứ nhất. */}
|
||||
{!readOnly && (
|
||||
<p className="mb-2 text-[12px] text-slate-500">
|
||||
File căn cứ hồ sơ / shopdrawing của kế hoạch. Gắn vào 1 căn cứ cụ thể bằng nút ⬆ ở dòng căn cứ đó.
|
||||
</p>
|
||||
)}
|
||||
{/* 🧊 [S175 — anh: "Bỏ hết mấy chữ thừa này đi"] Dòng mô-tả "File căn cứ hồ sơ /
|
||||
shopdrawing của kế hoạch…" ĐÃ GỠ. Nút ⬆ ở mỗi dòng căn cứ đã tự nói nó làm gì
|
||||
(có `title`), không cần một câu hướng-dẫn nằm chình ình trên đầu khối. */}
|
||||
{mergedRows.length === 0 ? (
|
||||
<div className="text-sm italic text-slate-400">Chưa có căn cứ hồ sơ hay file đính kèm nào.</div>
|
||||
) : (
|
||||
|
||||
@ -160,10 +160,18 @@ export function KhkkWorkListPanel({
|
||||
accent="bg-amber-50 text-amber-600"
|
||||
icon={<FolderTree className="h-3 w-3" />}
|
||||
>
|
||||
{/* [S175 — anh: "chỗ này chỉ cần NCC đã duyệt là đc rồi nhé, còn lại bỏ hết đi"]
|
||||
· `approvedPeOnly` ⇒ chỉ phiếu PE phase `DaDuyet`. Màn này để CHỌN PE mà lập
|
||||
kế hoạch; phiếu chưa duyệt xong thì lập không được, hiện ra là mời bấm nhầm.
|
||||
· `stagesOnly={[1]}` ⇒ chỉ folder **Duyệt NCC**; 3 giai-đoạn kia (Kế hoạch HĐ
|
||||
+ 8 ngăn nhóm · Duyệt HĐ · HĐ cứng) là nhiễu ở màn này.
|
||||
🔴 CẢ HAI đều OPT-IN — 5 host cây còn lại không truyền nên giữ nguyên hành vi. */}
|
||||
<PipelineTreePanel
|
||||
currentStage={2}
|
||||
hideHeader
|
||||
hideSearch
|
||||
approvedPeOnly
|
||||
stagesOnly={[1]}
|
||||
bodyMaxHClass="max-h-none"
|
||||
stage2Options={{ onOpenPe: onPickPe, onOpenKhkk: onPickPlan }}
|
||||
/>
|
||||
@ -269,11 +277,9 @@ export function KhkkWorkListPanel({
|
||||
{/* 🔴 KHÔNG in "x/total" nữa: `total` gồm cả phiếu đã duyệt vừa bị lọc ⇒ tỉ số đó
|
||||
sẽ đọc thành "đang cắt bớt" trong khi thật ra là "đang lọc bỏ". Nói đúng việc
|
||||
đang làm, và chỉ nói khi thật sự có cái bị giấu. */}
|
||||
{plans.data && plans.data.items.length > planItems.length && (
|
||||
<p className="border-t border-slate-100 px-3 py-1.5 text-[10px] italic text-slate-500">
|
||||
Đã ẩn kế hoạch ở trạng thái “Đã duyệt” — xem đủ ở menu Kế hoạch ký kết HĐ.
|
||||
</p>
|
||||
)}
|
||||
{/* 🧊 [S175 — anh: "Bỏ hết mấy chữ thừa này đi"] Dòng "Đã ẩn kế hoạch ở trạng
|
||||
thái Đã duyệt…" ĐÃ GỠ. Nó giải-thích CƠ-CHẾ lọc bên trong; người dùng chỉ cần
|
||||
thấy đúng phiếu, không cần biết cái gì bị ẩn vì sao. */}
|
||||
</Tier>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -16,7 +16,7 @@ import { cn } from '@/lib/cn'
|
||||
import { usePipelineStages, type BuildStagesOptions } from '@/hooks/usePipelineStages'
|
||||
import { PipelineStageFolders, type PipelineStageNo } from '@/components/pipeline/PipelineStageFolders'
|
||||
import type { Paged } from '@/types/master'
|
||||
import type { PeListItem } from '@/types/purchaseEvaluation'
|
||||
import { PurchaseEvaluationPhase, type PeListItem } from '@/types/purchaseEvaluation'
|
||||
|
||||
const STORAGE_KEY = 'pipeline_tree_expanded_v1'
|
||||
const PE_INDEX_PAGE_SIZE = 200
|
||||
@ -90,6 +90,8 @@ export function PipelineTreePanel({
|
||||
stage2Options,
|
||||
hideHeader,
|
||||
hideSearch,
|
||||
approvedPeOnly,
|
||||
stagesOnly,
|
||||
}: {
|
||||
currentStage: PipelineStageNo
|
||||
className?: string
|
||||
@ -116,6 +118,14 @@ export function PipelineTreePanel({
|
||||
// dùng tới — người dùng gõ phát nào là cây biến mất phát đó.
|
||||
// 6 host còn lại KHÔNG truyền ⇒ giữ nguyên ô lọc cây như cũ.
|
||||
hideSearch?: boolean
|
||||
// [S175 — anh: "chỗ này chỉ cần NCC đã duyệt là đc rồi nhé, còn lại bỏ hết đi"]
|
||||
// Chỉ giữ phiếu Duyệt NCC ở phase DaDuyet. Màn Thao tác dùng cây này để CHỌN PE mà lập
|
||||
// kế hoạch — phiếu chưa duyệt xong thì lập không được, hiện ra chỉ tổ mời bấm nhầm.
|
||||
// 🔴 OPT-IN: 5 host còn lại không truyền ⇒ cây giữ NGUYÊN mọi phase như cũ.
|
||||
approvedPeOnly?: boolean
|
||||
// [S175] Chỉ vẽ các folder giai-đoạn trong danh sách này (vd `[1]` = chỉ Duyệt NCC).
|
||||
// Không truyền ⇒ vẽ đủ 4 giai-đoạn như cũ.
|
||||
stagesOnly?: PipelineStageNo[]
|
||||
}) {
|
||||
const { canPe, buildStages } = usePipelineStages()
|
||||
const [search, setSearch] = useState('')
|
||||
@ -154,13 +164,18 @@ export function PipelineTreePanel({
|
||||
|
||||
const term = search.trim().toLowerCase()
|
||||
const rows = useMemo(() => {
|
||||
const all = peIndex.data?.items ?? []
|
||||
// [S175] Lọc phase TRƯỚC khi dựng cây ⇒ số đếm ở mọi tầng (Năm/Dự án/Hạng mục) tự khớp
|
||||
// tập đã lọc. Lọc SAU khi dựng thì badge đếm sẽ nói dối.
|
||||
const base = peIndex.data?.items ?? []
|
||||
const all = approvedPeOnly
|
||||
? base.filter(p => p.phase === PurchaseEvaluationPhase.DaDuyet)
|
||||
: base
|
||||
if (!term) return all
|
||||
return all.filter(p =>
|
||||
[p.projectCode, p.projectName, p.workItemName, p.maPhieu, p.tenGoiThau]
|
||||
.some(v => (v ?? '').toLowerCase().includes(term)),
|
||||
)
|
||||
}, [peIndex.data, term])
|
||||
}, [peIndex.data, term, approvedPeOnly])
|
||||
|
||||
const years = useMemo(() => buildTree(rows), [rows])
|
||||
const packageCount = years.reduce((n, y) => n + y.projects.reduce((m, p) => m + p.workItems.length, 0), 0)
|
||||
@ -342,7 +357,12 @@ export function PipelineTreePanel({
|
||||
className="ml-3 border-l border-slate-200"
|
||||
currentStage={currentStage}
|
||||
hideEmpty={hideEmpty}
|
||||
stages={buildStages(wg.items, stage2Options ?? {})}
|
||||
stages={(() => {
|
||||
const built = buildStages(wg.items, stage2Options ?? {})
|
||||
// [S175] `stagesOnly` = lọc HIỂN THỊ, KHÔNG đụng bộ dựng —
|
||||
// 5 host kia không truyền thì `built` đi thẳng như cũ.
|
||||
return stagesOnly ? built.filter(st => stagesOnly.includes(st.n)) : built
|
||||
})()}
|
||||
/>
|
||||
</details>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user