[CLAUDE] FE: HĐ move Lich su dieu chinh sang Panel 3 duoi Lich su duyet
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m55s

User request: 'cho hop dong dua cac thong tin lich su dieu chinh sang
duoi lich su duyet nhen'.

ContractDetailContent (Panel 2): xoa section 'Lich su dieu chinh' (cot
3/10 grid 7/3) → Chi tiet HD gio full-width. Remove import History +
ContractChangelogsTab.

WorkflowHistoryPanel (Panel 3): them section Lich su dieu chinh duoi
Lich su duyet. Import History icon + ContractChangelogsTab. Reuse same
component, chi doi vi tri render.

Mirror fe-admin + fe-user.
This commit is contained in:
pqhuy1987
2026-04-24 13:06:18 +07:00
parent d1090843a2
commit 8cf1fe214a
4 changed files with 50 additions and 51 deletions

View File

@ -1,16 +1,15 @@
// Reusable detail body — used by full-page ContractDetailPage AND embedded // Reusable detail body — used by full-page ContractDetailPage AND embedded
// in ContractsListPage 3-panel layout (Panel 2). Admin variant include thêm // in MyContractsPage 3-panel layout (Panel 2). Renders header (title + phase
// Phòng ban + Bypass CCM trong Info section. Workflow + history live separately // + actions) + Info + Comments + Attachments + transition Dialog. Workflow +
// trong WorkflowHistoryPanel (Panel 3). // approval history live separately in WorkflowHistoryPanel (Panel 3).
import { useState, type FormEvent } from 'react' import { useState, type FormEvent } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQueryClient } from '@tanstack/react-query'
import { ArrowLeft, CheckCircle2, MessageSquare, XCircle, ListChecks, History } from 'lucide-react' import { ArrowLeft, CheckCircle2, MessageSquare, XCircle, ListChecks } from 'lucide-react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { PhaseBadge } from '@/components/PhaseBadge' import { PhaseBadge } from '@/components/PhaseBadge'
import { SlaTimer } from '@/components/SlaTimer' import { SlaTimer } from '@/components/SlaTimer'
import { ContractAttachmentsSection } from '@/components/ContractAttachmentsSection' import { ContractAttachmentsSection } from '@/components/ContractAttachmentsSection'
import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab' import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab'
import { ContractChangelogsTab } from '@/components/contracts/ContractChangelogsTab'
import { Button } from '@/components/ui/Button' import { Button } from '@/components/ui/Button'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea' import { Textarea } from '@/components/ui/Textarea'
@ -49,7 +48,7 @@ export function ContractDetailContent({
}, },
onSuccess: () => { onSuccess: () => {
qc.invalidateQueries({ queryKey: ['contract', c.id] }) qc.invalidateQueries({ queryKey: ['contract', c.id] })
qc.invalidateQueries({ queryKey: ['contracts'] }) qc.invalidateQueries({ queryKey: ['my-contracts'] })
qc.invalidateQueries({ queryKey: ['inbox'] }) qc.invalidateQueries({ queryKey: ['inbox'] })
toast.success('Đã chuyển phase') toast.success('Đã chuyển phase')
setActionOpen(false) setActionOpen(false)
@ -65,7 +64,7 @@ export function ContractDetailContent({
onSuccess: () => { onSuccess: () => {
qc.invalidateQueries({ queryKey: ['contract', c.id] }) qc.invalidateQueries({ queryKey: ['contract', c.id] })
setCommentInput('') setCommentInput('')
toast.success('Đã gửi comment') toast.success('Đã gửi')
}, },
onError: err => toast.error(getErrorMessage(err)), onError: err => toast.error(getErrorMessage(err)),
}) })
@ -84,7 +83,7 @@ export function ContractDetailContent({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* Header — sticky inside scroll container */} {/* Header — sticky inside scroll container so actions luôn visible */}
<div className="sticky top-0 z-10 -mx-5 -mt-5 border-b border-slate-200 bg-white px-5 pt-5 pb-3 md:-mx-6 md:-mt-6 md:px-6 md:pt-6"> <div className="sticky top-0 z-10 -mx-5 -mt-5 border-b border-slate-200 bg-white px-5 pt-5 pb-3 md:-mx-6 md:-mt-6 md:px-6 md:pt-6">
<div className="flex flex-wrap items-start justify-between gap-3"> <div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@ -126,13 +125,11 @@ export function ContractDetailContent({
<div><dt className="text-slate-500">Giá trị</dt><dd>{fmtMoney(c.giaTri)}</dd></div> <div><dt className="text-slate-500">Giá trị</dt><dd>{fmtMoney(c.giaTri)}</dd></div>
<div><dt className="text-slate-500">NCC</dt><dd>{c.supplierName}</dd></div> <div><dt className="text-slate-500">NCC</dt><dd>{c.supplierName}</dd></div>
<div><dt className="text-slate-500">Dự án</dt><dd>{c.projectName}</dd></div> <div><dt className="text-slate-500">Dự án</dt><dd>{c.projectName}</dd></div>
<div><dt className="text-slate-500">Phòng ban</dt><dd>{c.departmentName ?? '—'}</dd></div>
<div><dt className="text-slate-500">Người soạn</dt><dd>{c.drafterName ?? '—'}</dd></div> <div><dt className="text-slate-500">Người soạn</dt><dd>{c.drafterName ?? '—'}</dd></div>
<div className="col-span-2"> <div className="col-span-2">
<dt className="text-slate-500 mb-1">SLA</dt> <dt className="text-slate-500 mb-1">SLA</dt>
<dd><SlaTimer deadline={c.slaDeadline} createdAt={c.createdAt} variant="full" /></dd> <dd><SlaTimer deadline={c.slaDeadline} createdAt={c.createdAt} variant="full" /></dd>
</div> </div>
<div><dt className="text-slate-500">Bypass CCM</dt><dd>{c.bypassProcurementAndCCM ? 'Có (HĐ Chủ đầu tư)' : 'Không'}</dd></div>
</dl> </dl>
{c.noiDung && ( {c.noiDung && (
<div className="mt-3"> <div className="mt-3">
@ -176,23 +173,14 @@ export function ContractDetailContent({
<ContractAttachmentsSection contractId={c.id} attachments={c.attachments} /> <ContractAttachmentsSection contractId={c.id} attachments={c.attachments} />
{/* Chi tiết + Lịch sử điều chỉnh — 7/3 grid bên dưới */} {/* Chi tiết HĐ full-width — Lịch sử điều chỉnh đã move sang Panel 3 */}
<div className="grid grid-cols-1 gap-4 lg:grid-cols-10"> <section className="rounded-lg border border-slate-200 bg-white p-5">
<section className="rounded-lg border border-slate-200 bg-white p-5 lg:col-span-7"> <h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700"> <ListChecks className="h-4 w-4" />
<ListChecks className="h-4 w-4" /> Chi tiết ({ContractTypeLabel[c.type] ?? '—'})
Chi tiết ({ContractTypeLabel[c.type] ?? '—'}) </h2>
</h2> <ContractDetailsTab contract={c} />
<ContractDetailsTab contract={c} /> </section>
</section>
<section className="rounded-lg border border-slate-200 bg-white p-5 lg:col-span-3">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<History className="h-4 w-4" />
Lịch sử điều chỉnh
</h2>
<ContractChangelogsTab contractId={c.id} />
</section>
</div>
<Dialog <Dialog
open={actionOpen} open={actionOpen}

View File

@ -1,8 +1,11 @@
// Panel 3 cho 3-panel layout — gom WorkflowSummaryCard (timeline policy) + // Panel 3 cho 3-panel layout — gom WorkflowSummaryCard (timeline policy) +
// approval history (ai/khi/quyết định gì) vào 1 stack. // approval history (ai/khi/quyết định gì) + ContractChangelogsTab (mọi thay
import { ArrowRight, Clock } from 'lucide-react' // đổi header/detail/comment/attachment/transition) vào 1 stack. Dùng cho cả
// MyContracts 3-panel và ContractDetailPage fullpage.
import { ArrowRight, Clock, History } from 'lucide-react'
import { PhaseBadge } from '@/components/PhaseBadge' import { PhaseBadge } from '@/components/PhaseBadge'
import { WorkflowSummaryCard } from '@/components/WorkflowSummaryCard' import { WorkflowSummaryCard } from '@/components/WorkflowSummaryCard'
import { ContractChangelogsTab } from '@/components/contracts/ContractChangelogsTab'
import type { ContractDetail } from '@/types/contracts' import type { ContractDetail } from '@/types/contracts'
const fmt = (s: string) => new Date(s).toLocaleString('vi-VN') const fmt = (s: string) => new Date(s).toLocaleString('vi-VN')
@ -40,6 +43,14 @@ export function WorkflowHistoryPanel({ contract: c }: { contract: ContractDetail
))} ))}
</ol> </ol>
</section> </section>
<section className="rounded-xl border border-slate-200 bg-white p-5 shadow-sm">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<History className="h-4 w-4" />
Lịch sử điều chỉnh
</h2>
<ContractChangelogsTab contractId={c.id} />
</section>
</div> </div>
) )
} }

View File

@ -4,13 +4,12 @@
// approval history live separately in WorkflowHistoryPanel (Panel 3). // approval history live separately in WorkflowHistoryPanel (Panel 3).
import { useState, type FormEvent } from 'react' import { useState, type FormEvent } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQueryClient } from '@tanstack/react-query'
import { ArrowLeft, CheckCircle2, MessageSquare, XCircle, ListChecks, History } from 'lucide-react' import { ArrowLeft, CheckCircle2, MessageSquare, XCircle, ListChecks } from 'lucide-react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { PhaseBadge } from '@/components/PhaseBadge' import { PhaseBadge } from '@/components/PhaseBadge'
import { SlaTimer } from '@/components/SlaTimer' import { SlaTimer } from '@/components/SlaTimer'
import { ContractAttachmentsSection } from '@/components/ContractAttachmentsSection' import { ContractAttachmentsSection } from '@/components/ContractAttachmentsSection'
import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab' import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab'
import { ContractChangelogsTab } from '@/components/contracts/ContractChangelogsTab'
import { Button } from '@/components/ui/Button' import { Button } from '@/components/ui/Button'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea' import { Textarea } from '@/components/ui/Textarea'
@ -174,23 +173,14 @@ export function ContractDetailContent({
<ContractAttachmentsSection contractId={c.id} attachments={c.attachments} /> <ContractAttachmentsSection contractId={c.id} attachments={c.attachments} />
{/* Chi tiết + Lịch sử điều chỉnh — 7/3 grid bên dưới */} {/* Chi tiết HĐ full-width — Lịch sử điều chỉnh đã move sang Panel 3 */}
<div className="grid grid-cols-1 gap-4 lg:grid-cols-10"> <section className="rounded-lg border border-slate-200 bg-white p-5">
<section className="rounded-lg border border-slate-200 bg-white p-5 lg:col-span-7"> <h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700"> <ListChecks className="h-4 w-4" />
<ListChecks className="h-4 w-4" /> Chi tiết ({ContractTypeLabel[c.type] ?? '—'})
Chi tiết ({ContractTypeLabel[c.type] ?? '—'}) </h2>
</h2> <ContractDetailsTab contract={c} />
<ContractDetailsTab contract={c} /> </section>
</section>
<section className="rounded-lg border border-slate-200 bg-white p-5 lg:col-span-3">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<History className="h-4 w-4" />
Lịch sử điều chỉnh
</h2>
<ContractChangelogsTab contractId={c.id} />
</section>
</div>
<Dialog <Dialog
open={actionOpen} open={actionOpen}

View File

@ -1,9 +1,11 @@
// Panel 3 cho 3-panel layout — gom WorkflowSummaryCard (timeline policy) + // Panel 3 cho 3-panel layout — gom WorkflowSummaryCard (timeline policy) +
// approval history (ai/khi/quyết định gì) vào 1 stack. Dùng cho cả MyContracts // approval history (ai/khi/quyết định gì) + ContractChangelogsTab (mọi thay
// 3-panel và ContractDetailPage fullpage. // đổi header/detail/comment/attachment/transition) vào 1 stack. Dùng cho cả
import { ArrowRight, Clock } from 'lucide-react' // MyContracts 3-panel và ContractDetailPage fullpage.
import { ArrowRight, Clock, History } from 'lucide-react'
import { PhaseBadge } from '@/components/PhaseBadge' import { PhaseBadge } from '@/components/PhaseBadge'
import { WorkflowSummaryCard } from '@/components/WorkflowSummaryCard' import { WorkflowSummaryCard } from '@/components/WorkflowSummaryCard'
import { ContractChangelogsTab } from '@/components/contracts/ContractChangelogsTab'
import type { ContractDetail } from '@/types/contracts' import type { ContractDetail } from '@/types/contracts'
const fmt = (s: string) => new Date(s).toLocaleString('vi-VN') const fmt = (s: string) => new Date(s).toLocaleString('vi-VN')
@ -41,6 +43,14 @@ export function WorkflowHistoryPanel({ contract: c }: { contract: ContractDetail
))} ))}
</ol> </ol>
</section> </section>
<section className="rounded-xl border border-slate-200 bg-white p-5 shadow-sm">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-700">
<History className="h-4 w-4" />
Lịch sử điều chỉnh
</h2>
<ContractChangelogsTab contractId={c.id} />
</section>
</div> </div>
) )
} }