[CLAUDE] Hrm: P11-C Vehicle+Driver catalogs (Mig 44) + gotcha #57 filtered-unique 3 HRM catalog (Mig 45)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m18s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m18s
P11-C: extend HrmConfigs +2 kind (Vehicle/Driver) declarative. Mig 44 AddVehicleAndDriverCatalogs (2 table filtered-unique Code, tables 91->93). Domain entity + EF config (filtered day-1) + 2 DbSet + HrmConfigFeatures Region5/6 CRUD + Controller +2 route-group (GET public / write Roles=Admin) + MenuKeys +2 +All (auto Admin perm) + DbInitializer 2 menu leaf + idempotent seed 2 veh/2 drv. FE declarative KIND_CONFIG +2 kind x2 app (SHA256 mirror) + 4-place (types/page/menuKeys/Layout staticMap), :kind-driven no new route. gotcha #57 (bundled; OtPolicy missed in backlog, caught via grep) - Mig 45 FilterHrmCatalogUniqueIndexesByIsDeleted: LeaveType+ShiftPattern+OtPolicy bare .IsUnique() -> .HasFilter([IsDeleted]=0) (recreate-on-soft-deleted-slot 500 fix, mirror Holiday Mig 43). Tests +5 HrmConfigFilteredUniqueTests (181->186 PASS) test-before RED->GREEN. Reviewer caught FE<->BE Driver required-field mismatch -> fixed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
import { useState, type ComponentType, type FormEvent } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { Settings2, Calendar, Clock, Pencil, Plus, Plane, Repeat, Trash2, Search } from 'lucide-react'
|
||||
import { Settings2, Calendar, Clock, Pencil, Plus, Plane, Repeat, Trash2, Search, Car, IdCard } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
@ -109,9 +109,36 @@ const KIND_CONFIG: Record<Kind, {
|
||||
],
|
||||
columns: ['Mã', 'Tên', 'Hệ số WD/WE/HL', 'Max h/year', 'Trạng thái'],
|
||||
},
|
||||
'vehicles': {
|
||||
label: 'Xe công',
|
||||
description: 'Danh mục xe công ty — dùng khi đăng ký đặt xe.',
|
||||
icon: Car,
|
||||
fields: [
|
||||
{ key: 'code', label: 'Mã *', type: 'text', required: true, placeholder: 'XE-01...' },
|
||||
{ key: 'name', label: 'Tên xe *', type: 'text', required: true, placeholder: 'Toyota Innova 7 chỗ' },
|
||||
{ key: 'licensePlate', label: 'Biển số *', type: 'text', required: true, placeholder: '30A-12345' },
|
||||
{ key: 'seatCount', label: 'Số chỗ', type: 'number', placeholder: '7' },
|
||||
{ key: 'description', label: 'Mô tả', type: 'textarea' },
|
||||
],
|
||||
columns: ['Mã', 'Tên xe', 'Biển số', 'Số chỗ', 'Trạng thái'],
|
||||
},
|
||||
'drivers': {
|
||||
label: 'Tài xế',
|
||||
description: 'Danh mục tài xế — dùng khi đăng ký đặt xe.',
|
||||
icon: IdCard,
|
||||
fields: [
|
||||
{ key: 'code', label: 'Mã *', type: 'text', required: true, placeholder: 'TX-01...' },
|
||||
{ key: 'name', label: 'Họ tên *', type: 'text', required: true, placeholder: 'Nguyễn Văn Tài' },
|
||||
{ key: 'phoneNumber', label: 'SĐT *', type: 'text', required: true, placeholder: '0901xxxxxx' },
|
||||
{ key: 'licenseNumber', label: 'Số GPLX *', type: 'text', required: true, placeholder: '012345678' },
|
||||
{ key: 'licenseClass', label: 'Hạng *', type: 'text', required: true, placeholder: 'B2, C, D, E' },
|
||||
{ key: 'description', label: 'Mô tả', type: 'textarea' },
|
||||
],
|
||||
columns: ['Mã', 'Họ tên', 'SĐT', 'Số GPLX', 'Hạng', 'Trạng thái'],
|
||||
},
|
||||
}
|
||||
|
||||
const KINDS: Kind[] = ['leave-types', 'holidays', 'shifts', 'ot-policies']
|
||||
const KINDS: Kind[] = ['leave-types', 'holidays', 'shifts', 'ot-policies', 'vehicles', 'drivers']
|
||||
|
||||
type ConfigRow = Record<string, unknown> & { id: string; isActive?: boolean }
|
||||
|
||||
@ -427,6 +454,29 @@ function renderCells(kind: Kind, row: ConfigRow) {
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (kind === 'vehicles') {
|
||||
return (
|
||||
<>
|
||||
<td className="px-3 py-2 font-mono text-xs">{row.code as string}</td>
|
||||
<td className="px-3 py-2">{row.name as string}</td>
|
||||
<td className="px-3 py-2 font-mono text-xs text-slate-600">{row.licensePlate as string}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{(row.seatCount as number) ?? '—'}</td>
|
||||
<td className="px-3 py-2">{statusBadge}</td>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (kind === 'drivers') {
|
||||
return (
|
||||
<>
|
||||
<td className="px-3 py-2 font-mono text-xs">{row.code as string}</td>
|
||||
<td className="px-3 py-2">{row.name as string}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{(row.phoneNumber as string) || '—'}</td>
|
||||
<td className="px-3 py-2 font-mono text-xs text-slate-600">{(row.licenseNumber as string) || '—'}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{(row.licenseClass as string) || '—'}</td>
|
||||
<td className="px-3 py-2">{statusBadge}</td>
|
||||
</>
|
||||
)
|
||||
}
|
||||
// ot-policies
|
||||
const mw = Number(row.multiplierWeekday ?? 0)
|
||||
const me = Number(row.multiplierWeekend ?? 0)
|
||||
|
||||
Reference in New Issue
Block a user