import { useState, type FormEvent } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Pencil, Plus, Trash2 } from 'lucide-react' import { toast } from 'sonner' import { PageHeader } from '@/components/PageHeader' import { DataTable, Pagination, type Column } from '@/components/DataTable' import { PermissionGuard } from '@/components/PermissionGuard' import { Button } from '@/components/ui/Button' import { Input } from '@/components/ui/Input' import { Label } from '@/components/ui/Label' import { Textarea } from '@/components/ui/Textarea' import { Dialog } from '@/components/ui/Dialog' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { MenuKeys } from '@/lib/menuKeys' import type { Paged, Project } from '@/types/master' type FormState = { id?: string code: string name: string startDate: string endDate: string budgetTotal: string note: string } const emptyForm: FormState = { code: '', name: '', startDate: '', endDate: '', budgetTotal: '', note: '' } const fmtMoney = (v: number | null) => (v == null ? '—' : v.toLocaleString('vi-VN')) const fmtDate = (s: string | null) => (s ? new Date(s).toLocaleDateString('vi-VN') : '—') export function ProjectsPage() { const qc = useQueryClient() const [page, setPage] = useState(1) const [search, setSearch] = useState('') const [sortBy, setSortBy] = useState() const [sortDesc, setSortDesc] = useState(true) const [open, setOpen] = useState(false) const [form, setForm] = useState(emptyForm) const isEdit = !!form.id const list = useQuery({ queryKey: ['projects', { page, search, sortBy, sortDesc }], queryFn: async () => { const res = await api.get>('/projects', { params: { page, pageSize: 20, search: search || undefined, sortBy, sortDesc }, }) return res.data }, }) const mutate = useMutation({ mutationFn: async (d: FormState) => { const payload = { id: d.id, code: d.code, name: d.name, startDate: d.startDate || null, endDate: d.endDate || null, managerUserId: null, budgetTotal: d.budgetTotal ? Number(d.budgetTotal) : null, note: d.note || null, } if (d.id) await api.put(`/projects/${d.id}`, payload) else await api.post('/projects', payload) }, onSuccess: () => { qc.invalidateQueries({ queryKey: ['projects'] }) toast.success(isEdit ? 'Đã cập nhật dự án' : 'Đã thêm dự án') setOpen(false) setForm(emptyForm) }, onError: err => toast.error(getErrorMessage(err)), }) const remove = useMutation({ mutationFn: (id: string) => api.delete(`/projects/${id}`), onSuccess: () => { qc.invalidateQueries({ queryKey: ['projects'] }) toast.success('Đã xóa') }, onError: err => toast.error(getErrorMessage(err)), }) function openEdit(p: Project) { setForm({ id: p.id, code: p.code, name: p.name, startDate: p.startDate ? p.startDate.slice(0, 10) : '', endDate: p.endDate ? p.endDate.slice(0, 10) : '', budgetTotal: p.budgetTotal?.toString() ?? '', note: p.note ?? '', }) setOpen(true) } const columns: Column[] = [ { key: 'code', header: 'Mã DA', sortable: true, render: p => {p.code}, width: 'w-32' }, { key: 'name', header: 'Tên dự án', sortable: true, render: p => p.name }, { key: 'startDate', header: 'Bắt đầu', render: p => fmtDate(p.startDate), width: 'w-32' }, { key: 'endDate', header: 'Kết thúc', render: p => fmtDate(p.endDate), width: 'w-32' }, { key: 'budgetTotal', header: 'Ngân sách', align: 'right', render: p => fmtMoney(p.budgetTotal) }, { key: 'actions', header: '', align: 'right', width: 'w-28', render: p => (
), }, ] return (
} />
{ setSearch(e.target.value); setPage(1) }} className="max-w-sm" />
p.id} isLoading={list.isLoading} sortBy={sortBy} sortDesc={sortDesc} onSortChange={(k, d) => { setSortBy(k); setSortDesc(d) }} /> setOpen(false)} title={isEdit ? 'Sửa dự án' : 'Thêm dự án mới'} size="md" footer={ <> } >
{ e.preventDefault(); mutate.mutate(form) }} >
setForm({ ...form, code: e.target.value })} required />
setForm({ ...form, budgetTotal: e.target.value })} />
setForm({ ...form, name: e.target.value })} required />
setForm({ ...form, startDate: e.target.value })} />
setForm({ ...form, endDate: e.target.value })} />