From ec0c983e8eaf05b01338031725b63cfe2fb99001 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 23 Apr 2026 10:54:54 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-User+FE-Admin:=20action=20buttons?= =?UTF-8?q?=20(M=E1=BB=9F=20chi=20ti=E1=BA=BFt=20+=20X=C3=B3a)=20tr=C3=AAn?= =?UTF-8?q?=20row=20Panel=201=20Thao=20t=C3=A1c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback: thêm nút edit/action ở mỗi row trong list Panel 1 trang Thao tác. Hiện absolute positioned ở góc phải-trên row, opacity-0 → 100 khi hover (group-hover). Sibling không nested để click không trigger row select propagation. ## 2 button per row - ⤴ ExternalLink → navigate /contracts/{id} (fullpage detail với Workflow + History, khác Panel 2 chỉ có Edit form) - 🗑 Trash2 → confirm() + DELETE /contracts/{id} (soft delete, blocked sau DangInKy ở BE). Nếu xóa HĐ đang select → clear ?id= ## Implementation details - pr-16 cho row button để chừa khoảng cho action group - group-hover:opacity-100 transition (smooth fade in) - Mutation invalidate ['my-contracts'] sau xóa thành công - Toast success + getErrorMessage cho fail case (vd xóa HĐ đã qua DangInKy) Build: tsc + vite pass cả 2 app (fe-user 515ms, fe-admin 937ms) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../pages/contracts/ContractCreatePage.tsx | 50 +++++++++++++++++-- .../pages/contracts/ContractCreatePage.tsx | 50 +++++++++++++++++-- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/fe-admin/src/pages/contracts/ContractCreatePage.tsx b/fe-admin/src/pages/contracts/ContractCreatePage.tsx index 2db956b..78858c2 100644 --- a/fe-admin/src/pages/contracts/ContractCreatePage.tsx +++ b/fe-admin/src/pages/contracts/ContractCreatePage.tsx @@ -12,8 +12,8 @@ // hiển thị Chi tiết section. import { useState, useMemo, type FormEvent, useEffect } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useSearchParams } from 'react-router-dom' -import { FileText, Plus, Search, Save } from 'lucide-react' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { FileText, Plus, Search, Save, ExternalLink, Trash2 } from 'lucide-react' import { toast } from 'sonner' import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab' import { PhaseBadge } from '@/components/PhaseBadge' @@ -39,6 +39,8 @@ import { const fmtMoney = (v: number) => v.toLocaleString('vi-VN') export function ContractCreatePage() { + const navigate = useNavigate() + const qc = useQueryClient() const [searchParams, setSearchParams] = useSearchParams() const typeFilter = searchParams.get('type') ? Number(searchParams.get('type')) : 2 const selectedId = searchParams.get('id') @@ -51,6 +53,23 @@ export function ContractCreatePage() { (await api.get>('/contracts', { params: { page: 1, pageSize: 100 } })).data, }) + const deleteContract = useMutation({ + mutationFn: async (id: string) => { + await api.delete(`/contracts/${id}`) + }, + onSuccess: (_, deletedId) => { + toast.success('Đã xóa HĐ') + qc.invalidateQueries({ queryKey: ['my-contracts'] }) + // Nếu đang edit HĐ vừa xóa → clear selection + if (selectedId === deletedId) { + const next = new URLSearchParams(searchParams) + next.delete('id') + setSearchParams(next, { replace: true }) + } + }, + onError: err => toast.error(getErrorMessage(err)), + }) + const detail = useQuery({ queryKey: ['contract', selectedId], queryFn: async () => (await api.get(`/contracts/${selectedId}`)).data, @@ -141,11 +160,11 @@ export function ContractCreatePage() { )}
    {rows.map(c => ( -
  • +
  • + + {/* Action buttons — hover-show, sibling không nested để click không trigger row select */} +
    + + +
  • ))}
diff --git a/fe-user/src/pages/contracts/ContractCreatePage.tsx b/fe-user/src/pages/contracts/ContractCreatePage.tsx index 2db956b..78858c2 100644 --- a/fe-user/src/pages/contracts/ContractCreatePage.tsx +++ b/fe-user/src/pages/contracts/ContractCreatePage.tsx @@ -12,8 +12,8 @@ // hiển thị Chi tiết section. import { useState, useMemo, type FormEvent, useEffect } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useSearchParams } from 'react-router-dom' -import { FileText, Plus, Search, Save } from 'lucide-react' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { FileText, Plus, Search, Save, ExternalLink, Trash2 } from 'lucide-react' import { toast } from 'sonner' import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab' import { PhaseBadge } from '@/components/PhaseBadge' @@ -39,6 +39,8 @@ import { const fmtMoney = (v: number) => v.toLocaleString('vi-VN') export function ContractCreatePage() { + const navigate = useNavigate() + const qc = useQueryClient() const [searchParams, setSearchParams] = useSearchParams() const typeFilter = searchParams.get('type') ? Number(searchParams.get('type')) : 2 const selectedId = searchParams.get('id') @@ -51,6 +53,23 @@ export function ContractCreatePage() { (await api.get>('/contracts', { params: { page: 1, pageSize: 100 } })).data, }) + const deleteContract = useMutation({ + mutationFn: async (id: string) => { + await api.delete(`/contracts/${id}`) + }, + onSuccess: (_, deletedId) => { + toast.success('Đã xóa HĐ') + qc.invalidateQueries({ queryKey: ['my-contracts'] }) + // Nếu đang edit HĐ vừa xóa → clear selection + if (selectedId === deletedId) { + const next = new URLSearchParams(searchParams) + next.delete('id') + setSearchParams(next, { replace: true }) + } + }, + onError: err => toast.error(getErrorMessage(err)), + }) + const detail = useQuery({ queryKey: ['contract', selectedId], queryFn: async () => (await api.get(`/contracts/${selectedId}`)).data, @@ -141,11 +160,11 @@ export function ContractCreatePage() { )}
    {rows.map(c => ( -
  • +
  • + + {/* Action buttons — hover-show, sibling không nested để click không trigger row select */} +
    + + +
  • ))}