260323:1050 fix CI : Verify Build frontend #02 correct _???
This commit is contained in:
@@ -30,7 +30,7 @@ import {
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
import { _Organization } from '@/types/organization';
|
||||
import { Organization } from '@/types/organization';
|
||||
import { getApiErrorMessage } from '@/types/api-error';
|
||||
|
||||
export default function UsersPage() {
|
||||
@@ -49,7 +49,7 @@ export default function UsersPage() {
|
||||
|
||||
const { data: organizations = [] } = useOrganizations();
|
||||
const userList = Array.isArray(users) ? users : [];
|
||||
const organizationList = Array.isArray(organizations) ? organizations : [];
|
||||
const organizationList: Organization[] = Array.isArray(organizations) ? organizations : [];
|
||||
|
||||
const deleteMutation = useDeleteUser();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
@@ -23,8 +23,17 @@ export default function NewTemplatePage() {
|
||||
|
||||
const handleSave = async (data: Partial<NumberingTemplate>) => {
|
||||
try {
|
||||
await numberingApi.saveTemplate(data);
|
||||
router.push('/admin/numbering');
|
||||
// Correcting type mismatch by ensuring all required fields for SaveTemplateDto are present
|
||||
await numberingApi.saveTemplate({
|
||||
projectId: data.projectId!,
|
||||
correspondenceTypeId: data.correspondenceTypeId ?? null,
|
||||
formatTemplate: data.formatTemplate!,
|
||||
disciplineId: data.disciplineId,
|
||||
description: data.description,
|
||||
resetSequenceYearly: data.resetSequenceYearly,
|
||||
isActive: data.isActive,
|
||||
});
|
||||
router.push('/admin/doc-control/numbering');
|
||||
} catch (_error) {
|
||||
toast.error('Failed to create template');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Card } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Plus, Edit, Play } from 'lucide-react';
|
||||
import { NumberingTemplate } from '@/lib/api/numbering';
|
||||
import { NumberingTemplate, SaveTemplateDto } from '@/lib/api/numbering';
|
||||
import { useTemplates, useSaveTemplate } from '@/hooks/use-numbering';
|
||||
import { TemplateEditor } from '@/components/numbering/template-editor';
|
||||
import { SequenceViewer } from '@/components/numbering/sequence-viewer';
|
||||
@@ -71,7 +71,7 @@ export default function NumberingPage() {
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const handleSave = async (data: Partial<NumberingTemplate>) => {
|
||||
const handleSave = async (data: SaveTemplateDto) => {
|
||||
try {
|
||||
await saveTemplateMutation.mutateAsync(data);
|
||||
toast.success(data.id ? 'Template updated' : 'Template created');
|
||||
|
||||
@@ -3,32 +3,33 @@
|
||||
import { GenericCrudTable } from '@/components/admin/reference/generic-crud-table';
|
||||
import { masterDataService } from '@/lib/services/master-data.service';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { CorrespondenceType } from '@/types/master-data';
|
||||
|
||||
export default function CorrespondenceTypesPage() {
|
||||
const columns: ColumnDef<unknown>[] = [
|
||||
const columns: ColumnDef<CorrespondenceType>[] = [
|
||||
{
|
||||
accessorKey: 'typeCode',
|
||||
accessorKey: 'type_code',
|
||||
header: 'Code',
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('typeCode')}</span>,
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('type_code')}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'typeName',
|
||||
accessorKey: 'type_name',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
accessorKey: 'sortOrder',
|
||||
accessorKey: 'sort_order',
|
||||
header: 'Sort Order',
|
||||
},
|
||||
{
|
||||
accessorKey: 'isActive',
|
||||
accessorKey: 'is_active',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full text-xs ${
|
||||
row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{row.getValue('isActive') ? 'Active' : 'Inactive'}
|
||||
{row.getValue('is_active') ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
@@ -51,10 +52,10 @@ export default function CorrespondenceTypesPage() {
|
||||
deleteFn={(id) => masterDataService.deleteCorrespondenceType(id)}
|
||||
columns={columns}
|
||||
fields={[
|
||||
{ name: 'typeCode', label: 'Code', type: 'text', required: true },
|
||||
{ name: 'typeName', label: 'Name', type: 'text', required: true },
|
||||
{ name: 'sortOrder', label: 'Sort Order', type: 'text' },
|
||||
{ name: 'isActive', label: 'Active', type: 'checkbox' },
|
||||
{ name: 'type_code', label: 'Code', type: 'text', required: true },
|
||||
{ name: 'type_name', label: 'Name', type: 'text', required: true },
|
||||
{ name: 'sort_order', label: 'Sort Order', type: 'text' },
|
||||
{ name: 'is_active', label: 'Active', type: 'checkbox' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { GenericCrudTable } from '@/components/admin/reference/generic-crud-tabl
|
||||
import { masterDataService } from '@/lib/services/master-data.service';
|
||||
import { useContracts } from '@/hooks/use-master-data';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { Discipline } from '@/types/master-data';
|
||||
import { useState } from 'react';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
|
||||
@@ -14,36 +15,36 @@ export default function DisciplinesPage() {
|
||||
// Ensure we consistently use an array
|
||||
const contracts = Array.isArray(contractsData) ? contractsData : [];
|
||||
|
||||
const columns: ColumnDef<unknown>[] = [
|
||||
const columns: ColumnDef<Discipline>[] = [
|
||||
{
|
||||
accessorKey: 'disciplineCode',
|
||||
accessorKey: 'discipline_code',
|
||||
header: 'Code',
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('disciplineCode')}</span>,
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('discipline_code')}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'codeNameTh',
|
||||
accessorKey: 'code_name_th',
|
||||
header: 'Name (TH)',
|
||||
},
|
||||
{
|
||||
accessorKey: 'codeNameEn',
|
||||
accessorKey: 'code_name_en',
|
||||
header: 'Name (EN)',
|
||||
},
|
||||
{
|
||||
accessorKey: 'isActive',
|
||||
accessorKey: 'is_active',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full text-xs ${
|
||||
row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{row.getValue('isActive') ? 'Active' : 'Inactive'}
|
||||
{row.getValue('is_active') ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const contractOptions = contracts.map((c: unknown) => ({
|
||||
const contractOptions = contracts.map((c: { id: number; contractCode: string; contractName: string }) => ({
|
||||
label: `${c.contractName} (${c.contractCode})`,
|
||||
value: String(c.id),
|
||||
}));
|
||||
@@ -58,12 +59,12 @@ export default function DisciplinesPage() {
|
||||
fetchFn={async () => {
|
||||
const items = await masterDataService.getDisciplines(selectedContractId ? selectedContractId : undefined);
|
||||
// ADR-019: Map contractId INT → contract UUID for edit mode select matching
|
||||
return (items as Record<string, unknown>[]).map((item) => {
|
||||
const rec = item as { contract?: { id?: number; uuid?: string }; contractId?: number };
|
||||
return items.map((item) => {
|
||||
const rec = item as Discipline & { contract?: { id?: number; uuid?: string }; contractId?: number | string };
|
||||
return {
|
||||
...item,
|
||||
contractId: rec.contract?.id || rec.contract?.uuid || String(rec.contractId),
|
||||
};
|
||||
} as Discipline;
|
||||
});
|
||||
}}
|
||||
createFn={(data) =>
|
||||
@@ -85,7 +86,7 @@ export default function DisciplinesPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Contracts</SelectItem>
|
||||
{contracts.map((c: unknown) => (
|
||||
{contracts.map((c: { id: number; contractCode: string; contractName: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.contractName} ({c.contractCode})
|
||||
</SelectItem>
|
||||
@@ -103,19 +104,19 @@ export default function DisciplinesPage() {
|
||||
options: contractOptions,
|
||||
},
|
||||
{
|
||||
name: 'disciplineCode',
|
||||
name: 'discipline_code',
|
||||
label: 'Code',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'codeNameTh',
|
||||
name: 'code_name_th',
|
||||
label: 'Name (TH)',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{ name: 'codeNameEn', label: 'Name (EN)', type: 'text' },
|
||||
{ name: 'isActive', label: 'Active', type: 'checkbox' },
|
||||
{ name: 'code_name_en', label: 'Name (EN)', type: 'text' },
|
||||
{ name: 'is_active', label: 'Active', type: 'checkbox' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
import { GenericCrudTable } from '@/components/admin/reference/generic-crud-table';
|
||||
import { masterDataService } from '@/lib/services/master-data.service';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { DrawingCategory } from '@/types/master-data';
|
||||
|
||||
export default function DrawingCategoriesPage() {
|
||||
const columns: ColumnDef<unknown>[] = [
|
||||
const columns: ColumnDef<DrawingCategory>[] = [
|
||||
{
|
||||
accessorKey: 'subTypeCode',
|
||||
accessorKey: 'sub_type_code',
|
||||
header: 'Code',
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('subTypeCode')}</span>,
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('sub_type_code')}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'subTypeName',
|
||||
accessorKey: 'sub_type_name',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
accessorKey: 'subTypeNumber',
|
||||
accessorKey: 'sub_type_number',
|
||||
header: 'Running Code',
|
||||
cell: ({ row }) => <span className="font-mono">{row.getValue('subTypeNumber') || '-'}</span>,
|
||||
cell: ({ row }) => <span className="font-mono">{row.getValue('sub_type_number') || '-'}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -29,21 +30,25 @@ export default function DrawingCategoriesPage() {
|
||||
title="Drawing Categories Management"
|
||||
description="Manage drawing sub-types and categories"
|
||||
queryKey={['drawing-categories']}
|
||||
fetchFn={() => masterDataService.getSubTypes(1)} // Default contract ID 1
|
||||
fetchFn={async () => {
|
||||
const data = await masterDataService.getSubTypes(1);
|
||||
return data as (DrawingCategory & { uuid?: string })[];
|
||||
}}
|
||||
createFn={(data: Record<string, unknown>) =>
|
||||
masterDataService.createSubType({
|
||||
...(data as unknown as Parameters<typeof masterDataService.createSubType>[0]),
|
||||
contractId: 1,
|
||||
correspondenceTypeId: 3,
|
||||
})
|
||||
} // Assuming 3 is Drawings, hardcoded for now to prevent error
|
||||
updateFn={() => Promise.reject('Not implemented yet')}
|
||||
deleteFn={() => Promise.reject('Not implemented yet')} // Delete might be restricted
|
||||
columns={columns}
|
||||
}
|
||||
updateFn={(id, data) => masterDataService.updateRfaType(id, data as Parameters<typeof masterDataService.updateRfaType>[1])}
|
||||
deleteFn={(id) => masterDataService.deleteRfaType(id)}
|
||||
columns={columns as unknown as ColumnDef<{ id?: number; uuid?: string }>[]}
|
||||
fields={[
|
||||
{ name: 'subTypeCode', label: 'Code', type: 'text', required: true },
|
||||
{ name: 'subTypeName', label: 'Name', type: 'text', required: true },
|
||||
{ name: 'subTypeNumber', label: 'Running Code', type: 'text' },
|
||||
{ name: 'sub_type_code', label: 'Code', type: 'text', required: true },
|
||||
{ name: 'sub_type_name', label: 'Name', type: 'text', required: true },
|
||||
{ name: 'sub_type_number', label: 'Running Code', type: 'text' },
|
||||
{ name: 'is_active', label: 'Active', type: 'checkbox' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { masterDataService } from '@/lib/services/master-data.service';
|
||||
import { useContracts } from '@/hooks/use-master-data';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { useState } from 'react';
|
||||
import { RfaType } from '@/types/master-data';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
|
||||
export default function RfaTypesPage() {
|
||||
@@ -14,18 +15,18 @@ export default function RfaTypesPage() {
|
||||
// Ensure we consistently use an array
|
||||
const contracts = Array.isArray(contractsData) ? contractsData : [];
|
||||
|
||||
const columns: ColumnDef<unknown>[] = [
|
||||
const columns: ColumnDef<RfaType>[] = [
|
||||
{
|
||||
accessorKey: 'typeCode',
|
||||
accessorKey: 'type_code',
|
||||
header: 'Code',
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('typeCode')}</span>,
|
||||
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('type_code')}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'typeNameTh',
|
||||
accessorKey: 'type_name_th',
|
||||
header: 'Name (TH)',
|
||||
},
|
||||
{
|
||||
accessorKey: 'typeNameEn',
|
||||
accessorKey: 'type_name_en',
|
||||
header: 'Name (EN)',
|
||||
},
|
||||
{
|
||||
@@ -33,22 +34,22 @@ export default function RfaTypesPage() {
|
||||
header: 'Remark',
|
||||
},
|
||||
{
|
||||
accessorKey: 'isActive',
|
||||
accessorKey: 'is_active',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full text-xs ${
|
||||
row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{row.getValue('isActive') ? 'Active' : 'Inactive'}
|
||||
{row.getValue('is_active') ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const contractOptions = contracts.map((c: unknown) => ({
|
||||
label: `${c.contractName} (${c.contractCode})`,
|
||||
const contractOptions = contracts.map((c: { id: number | string; contract_name?: string; contract_code?: string; contractName?: string; contractCode?: string }) => ({
|
||||
label: `${c.contractName || c.contract_name} (${c.contractCode || c.contract_code})`,
|
||||
value: String(c.id),
|
||||
}));
|
||||
|
||||
@@ -61,12 +62,12 @@ export default function RfaTypesPage() {
|
||||
fetchFn={async () => {
|
||||
const items = await masterDataService.getRfaTypes(selectedContractId ? selectedContractId : undefined);
|
||||
// ADR-019: Map contractId INT → contract UUID for edit mode select matching
|
||||
return (items as Record<string, unknown>[]).map((item) => {
|
||||
const rec = item as { contract?: { id?: number; uuid?: string }; contractId?: number };
|
||||
return items.map((item) => {
|
||||
const rec = item as RfaType & { contract?: { id?: number | string; uuid?: string }; contract_id?: number | string };
|
||||
return {
|
||||
...item,
|
||||
contractId: rec.contract?.id || rec.contract?.uuid || String(rec.contractId),
|
||||
};
|
||||
contractId: rec.contract?.id || rec.contract?.uuid || (rec.contract_id ? String(rec.contract_id) : null),
|
||||
} as RfaType;
|
||||
});
|
||||
}}
|
||||
createFn={(data) =>
|
||||
@@ -86,9 +87,9 @@ export default function RfaTypesPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Contracts</SelectItem>
|
||||
{contracts.map((c: unknown) => (
|
||||
{contracts.map((c: { id: number | string; contract_name?: string; contract_code?: string; contractName?: string; contractCode?: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.contractName} ({c.contractCode})
|
||||
{c.contractName || c.contract_name} ({c.contractCode || c.contract_code})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -103,11 +104,11 @@ export default function RfaTypesPage() {
|
||||
required: true,
|
||||
options: contractOptions,
|
||||
},
|
||||
{ name: 'typeCode', label: 'Code', type: 'text', required: true },
|
||||
{ name: 'typeNameTh', label: 'Name (TH)', type: 'text', required: true },
|
||||
{ name: 'typeNameEn', label: 'Name (EN)', type: 'text' },
|
||||
{ name: 'type_code', label: 'Code', type: 'text', required: true },
|
||||
{ name: 'type_name_th', label: 'Name (TH)', type: 'text', required: true },
|
||||
{ name: 'type_name_en', label: 'Name (EN)', type: 'text' },
|
||||
{ name: 'remark', label: 'Remark', type: 'textarea' },
|
||||
{ name: 'isActive', label: 'Active', type: 'checkbox' },
|
||||
{ name: 'is_active', label: 'Active', type: 'checkbox' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { projectService } from '@/lib/services/project.service';
|
||||
import { CreateTagDto } from '@/types/dto/master/tag.dto';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Tag } from '@/types/master-data';
|
||||
|
||||
export default function TagsPage() {
|
||||
const { data: projectsData } = useQuery({
|
||||
@@ -15,19 +16,19 @@ export default function TagsPage() {
|
||||
|
||||
const projectOptions = [
|
||||
{ label: 'Global (All Projects)', value: '__none__' },
|
||||
...(projectsData || []).map((p: Record<string, unknown>) => ({
|
||||
label: (p.projectName || p.projectCode || p.project_name || p.project_code || `Project ${p.id}`) as string,
|
||||
...(projectsData || []).map((p: { id: number | string; projectName?: string; projectCode?: string }) => ({
|
||||
label: (p.projectName || p.projectCode || `Project ${p.id}`) as string,
|
||||
value: String(p.id), // p.id = UUID string via serialization
|
||||
})),
|
||||
];
|
||||
|
||||
const columns: ColumnDef<Record<string, unknown>>[] = [
|
||||
const columns: ColumnDef<Tag>[] = [
|
||||
{
|
||||
accessorKey: 'project_id',
|
||||
header: 'Project',
|
||||
cell: ({ row }) => {
|
||||
const item = row.original as Record<string, unknown>;
|
||||
const project = item.project as Record<string, unknown> | null;
|
||||
const item = row.original as Tag & { project?: { id?: number | string; projectName?: string; projectCode?: string } };
|
||||
const project = item.project;
|
||||
if (!project) return <span className="text-muted-foreground italic">Global</span>;
|
||||
return (project.projectName || project.projectCode || `Project ${project.id}`) as React.ReactNode;
|
||||
},
|
||||
@@ -73,12 +74,12 @@ export default function TagsPage() {
|
||||
fetchFn={async () => {
|
||||
const items = await masterDataService.getTags();
|
||||
// ADR-019: Map project_id INT → project UUID for edit mode select matching
|
||||
return (items as Record<string, unknown>[]).map((item) => {
|
||||
const rec = item as { project?: { id?: number; uuid?: string }; project_id?: number };
|
||||
return items.map((item) => {
|
||||
const rec = item as Tag & { project?: { id?: number | string; uuid?: string }; project_id?: number | string };
|
||||
return {
|
||||
...item,
|
||||
project_id: rec.project?.id || rec.project?.uuid || (rec.project_id ? String(rec.project_id) : null),
|
||||
};
|
||||
} as Tag;
|
||||
});
|
||||
}}
|
||||
createFn={(data: Record<string, unknown>) =>
|
||||
|
||||
@@ -14,7 +14,11 @@ interface NumberingError {
|
||||
errorMessage: string;
|
||||
stackTrace?: string;
|
||||
createdAt: string;
|
||||
context?: unknown;
|
||||
context?: {
|
||||
projectId?: number | string;
|
||||
contractId?: number | string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
const logService = {
|
||||
|
||||
@@ -194,7 +194,7 @@ export default function CreateCirculationPage() {
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="assigneeIds"
|
||||
render={({ _field }) => (
|
||||
render={({ field: _field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Assignees</FormLabel>
|
||||
<Popover open={assigneeOpen} onOpenChange={setAssigneeOpen}>
|
||||
|
||||
@@ -72,7 +72,6 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
|
||||
const {
|
||||
data: rawData,
|
||||
isLoading,
|
||||
_refetch,
|
||||
} = useQuery({
|
||||
queryKey,
|
||||
queryFn: fetchFn,
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
import { Circulation, CirculationListResponse } from '@/types/circulation';
|
||||
import { DataTable } from '@/components/common/data-table';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { _StatusBadge } from '@/components/common/status-badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Eye, _CheckCircle2 } from 'lucide-react';
|
||||
import { Eye } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { format } from 'date-fns';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import Link from 'next/link';
|
||||
import { PendingTask } from '@/types/dashboard';
|
||||
import { _AlertCircle, ArrowRight } from 'lucide-react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
interface PendingTasksProps {
|
||||
tasks: PendingTask[] | undefined;
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
useReactTable,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
_getPaginationRowModel,
|
||||
OnChangeFn,
|
||||
} from '@tanstack/react-table';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { DrawingRevision } from '@/types/drawing';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Download, _FileText } from 'lucide-react';
|
||||
import { Download } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export function RevisionHistory({ revisions }: { revisions: DrawingRevision[] }) {
|
||||
|
||||
@@ -16,6 +16,11 @@ import {
|
||||
useShopSubCategories,
|
||||
useProjects,
|
||||
} from '@/hooks/use-master-data';
|
||||
import {
|
||||
ShopMainCategory,
|
||||
ShopSubCategory,
|
||||
ContractDrawingCategory,
|
||||
} from '@/types/master-data';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
@@ -232,13 +237,11 @@ export function DrawingUploadForm() {
|
||||
<SelectValue placeholder="Select Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contractCategories?.map(
|
||||
(c: { id: number; catName?: string; catCode?: string; name?: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.catName || c.catCode || c.name}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{contractCategories?.map((c: ContractDrawingCategory) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.catName || c.catCode || c.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formErrors.mapCatId && <p className="text-sm text-destructive">{formErrors.mapCatId.message}</p>}
|
||||
@@ -287,13 +290,11 @@ export function DrawingUploadForm() {
|
||||
<SelectValue placeholder="Select Main Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{shopMainCats?.map(
|
||||
(c: { id: number; mainCategoryName?: string; mainCategoryCode?: string; name?: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.mainCategoryName || c.mainCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{shopMainCats?.map((c: ShopMainCategory) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.mainCategoryName || c.mainCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formErrors.mainCategoryId && (
|
||||
@@ -307,13 +308,11 @@ export function DrawingUploadForm() {
|
||||
<SelectValue placeholder="Select Sub Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{shopSubCats?.map(
|
||||
(c: { id: number; subCategoryName?: string; subCategoryCode?: string; name?: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.subCategoryName || c.subCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{shopSubCats?.map((c: ShopSubCategory) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.subCategoryName || c.subCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formErrors.subCategoryId && (
|
||||
@@ -365,13 +364,11 @@ export function DrawingUploadForm() {
|
||||
<SelectValue placeholder="Select Main Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{shopMainCats?.map(
|
||||
(c: { id: number; mainCategoryName?: string; mainCategoryCode?: string; name?: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.mainCategoryName || c.mainCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{shopMainCats?.map((c: ShopMainCategory) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.mainCategoryName || c.mainCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formErrors.mainCategoryId && (
|
||||
@@ -385,13 +382,11 @@ export function DrawingUploadForm() {
|
||||
<SelectValue placeholder="Select Sub Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{shopSubCats?.map(
|
||||
(c: { id: number; subCategoryName?: string; subCategoryCode?: string; name?: string }) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.subCategoryName || c.subCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{shopSubCats?.map((c: ShopSubCategory) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.subCategoryName || c.subCategoryCode || c.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formErrors.subCategoryId && (
|
||||
|
||||
@@ -4,9 +4,10 @@ import { useEffect, useState } from 'react';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
import { format } from 'date-fns';
|
||||
import { NumberingAuditLog } from '@/types/dto/numbering.dto';
|
||||
|
||||
export function AuditLogsTable() {
|
||||
const [logs, setLogs] = useState<unknown[]>([]); // Replace with AuditLog type
|
||||
const [logs, setLogs] = useState<NumberingAuditLog[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -51,7 +52,7 @@ export function AuditLogsTable() {
|
||||
<TableRow key={log.id}>
|
||||
<TableCell>{format(new Date(log.createdAt), 'yyyy-MM-dd HH:mm:ss')}</TableCell>
|
||||
<TableCell>{log.operation}</TableCell>
|
||||
<TableCell>{log.generatedNumber}</TableCell>
|
||||
<TableCell>{log.documentNumber}</TableCell>
|
||||
<TableCell>{log.createdBy || 'System'}</TableCell>
|
||||
<TableCell>{log.status}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle, _CardDescription } from '@/components/ui/card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
import { NumberingMetrics } from '@/types/dto/numbering.dto';
|
||||
|
||||
@@ -8,8 +8,9 @@ import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { NumberingTemplate } from '@/lib/api/numbering';
|
||||
import { NumberingTemplate, SaveTemplateDto } from '@/lib/api/numbering';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||
import { CorrespondenceType, Discipline } from '@/types/master-data';
|
||||
|
||||
// Aligned with Backend replacement logic
|
||||
const VARIABLES = [
|
||||
@@ -29,9 +30,9 @@ export interface TemplateEditorProps {
|
||||
template?: NumberingTemplate;
|
||||
projectId: number | string;
|
||||
projectName: string;
|
||||
correspondenceTypes: unknown[];
|
||||
disciplines: unknown[];
|
||||
onSave: (data: Partial<NumberingTemplate>) => void;
|
||||
correspondenceTypes: CorrespondenceType[];
|
||||
disciplines: Discipline[];
|
||||
onSave: (data: SaveTemplateDto) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ export function TemplateEditor({
|
||||
|
||||
// Dynamic context based on selection (optional visual enhancement)
|
||||
if (v.key === '{TYPE}' && typeId) {
|
||||
const t = (correspondenceTypes as { id: number; typeCode: string; typeName: string }[]).find(
|
||||
const t = correspondenceTypes.find(
|
||||
(ct) => ct.id?.toString() === typeId
|
||||
);
|
||||
if (t) replacement = t.typeCode;
|
||||
@@ -117,11 +118,10 @@ export function TemplateEditor({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__default__">Default (All Types)</SelectItem>
|
||||
{correspondenceTypes.map((type: unknown) => {
|
||||
const typedType = type as { id: number; typeCode: string; typeName: string };
|
||||
{correspondenceTypes.map((type) => {
|
||||
return (
|
||||
<SelectItem key={typedType.id} value={typedType.id.toString()}>
|
||||
{typedType.typeCode} - {typedType.typeName}
|
||||
<SelectItem key={type.id} value={type.id.toString()}>
|
||||
{type.typeCode} - {type.typeName}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
@@ -141,7 +141,7 @@ export function TemplateEditor({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">All Disciplines</SelectItem>
|
||||
{disciplines.map((d: unknown) => (
|
||||
{disciplines.map((d) => (
|
||||
<SelectItem key={d.id} value={d.id.toString()}>
|
||||
{d.disciplineCode} - {d.codeNameEn || d.codeNameTh}
|
||||
</SelectItem>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -72,7 +73,12 @@ export function TemplateTester({ open, onOpenChange, template }: TemplateTesterP
|
||||
isDefault: result.isDefault,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const errMsg = error?.response?.data?.message || error?.message || 'Unknown error';
|
||||
let errMsg = 'Unknown error';
|
||||
if (axios.isAxiosError(error)) {
|
||||
errMsg = error.response?.data?.message || error.message;
|
||||
} else if (error instanceof Error) {
|
||||
errMsg = error.message;
|
||||
}
|
||||
setTestResult({ number: `Error: ${errMsg}`, isDefault: false });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { _Check, ChevronRight, _Circle } from 'lucide-react';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
import { numberingApi, NumberingTemplate } from '@/lib/api/numbering';
|
||||
import { numberingApi, SaveTemplateDto } from '@/lib/api/numbering';
|
||||
import { ManualOverrideDto, VoidReplaceDto, CancelNumberDto, AuditQueryParams } from '@/types/dto/numbering.dto';
|
||||
|
||||
export const numberingKeys = {
|
||||
@@ -20,7 +20,7 @@ export const useTemplates = () => {
|
||||
export const useSaveTemplate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<NumberingTemplate>) => numberingApi.saveTemplate(data),
|
||||
mutationFn: (data: SaveTemplateDto) => numberingApi.saveTemplate(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: numberingKeys.templates() });
|
||||
},
|
||||
|
||||
@@ -14,6 +14,16 @@ import {
|
||||
UpdateOrganizationDto,
|
||||
SearchOrganizationDto,
|
||||
} from '@/types/dto/organization/organization.dto';
|
||||
import {
|
||||
CorrespondenceType,
|
||||
Discipline,
|
||||
RfaType,
|
||||
Tag,
|
||||
DrawingCategory,
|
||||
ShopMainCategory,
|
||||
ShopSubCategory,
|
||||
ContractDrawingCategory,
|
||||
} from '@/types/master-data';
|
||||
|
||||
const extractArrayData = <T>(value: unknown): T[] => {
|
||||
let current: unknown = value;
|
||||
@@ -37,10 +47,10 @@ export const masterDataService = {
|
||||
// --- Tags Management ---
|
||||
|
||||
/** ดึงรายการ Tags ทั้งหมด (Search & Pagination) */
|
||||
getTags: async (params?: SearchTagDto) => {
|
||||
getTags: async (params?: SearchTagDto): Promise<Tag[]> => {
|
||||
const response = await apiClient.get('/master/tags', { params });
|
||||
// Support both wrapped and unwrapped scenarios
|
||||
return response.data.data || response.data;
|
||||
return extractArrayData<Tag>(response.data);
|
||||
},
|
||||
|
||||
/** สร้าง Tag ใหม่ */
|
||||
@@ -112,11 +122,11 @@ export const masterDataService = {
|
||||
// --- Disciplines Management (Admin / Req 6B) ---
|
||||
|
||||
/** ดึงรายชื่อสาขางาน (มักจะกรองตาม Contract ID) */
|
||||
getDisciplines: async (contractId?: number | string) => {
|
||||
getDisciplines: async (contractId?: number | string): Promise<Discipline[]> => {
|
||||
const response = await apiClient.get('/master/disciplines', {
|
||||
params: { contractId },
|
||||
});
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<Discipline>(response.data);
|
||||
},
|
||||
|
||||
/** สร้างสาขางานใหม่ */
|
||||
@@ -134,11 +144,11 @@ export const masterDataService = {
|
||||
// --- Sub-Types Management (Admin / Req 6B) ---
|
||||
|
||||
/** ดึงรายชื่อประเภทย่อย (กรองตาม Contract และ Type) */
|
||||
getSubTypes: async (contractId?: number | string, typeId?: number) => {
|
||||
getSubTypes: async (contractId?: number | string, typeId?: number): Promise<DrawingCategory[]> => {
|
||||
const response = await apiClient.get('/master/sub-types', {
|
||||
params: { contractId, correspondenceTypeId: typeId },
|
||||
});
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<DrawingCategory>(response.data);
|
||||
},
|
||||
|
||||
/** สร้างประเภทย่อยใหม่ */
|
||||
@@ -150,11 +160,11 @@ export const masterDataService = {
|
||||
// --- RFA Types Management (Admin) ---
|
||||
|
||||
/** ดึงประเภท RFA ทั้งหมด */
|
||||
getRfaTypes: async (contractId?: number | string) => {
|
||||
getRfaTypes: async (contractId?: number | string): Promise<RfaType[]> => {
|
||||
const response = await apiClient.get('/master/rfa-types', {
|
||||
params: { contractId },
|
||||
});
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<RfaType>(response.data);
|
||||
},
|
||||
|
||||
/** สร้างประเภท RFA ใหม่ */
|
||||
@@ -173,9 +183,9 @@ export const masterDataService = {
|
||||
// --- Document Numbering Format (Admin Config) ---
|
||||
|
||||
// --- Correspondence Types Management ---
|
||||
getCorrespondenceTypes: async () => {
|
||||
getCorrespondenceTypes: async (): Promise<CorrespondenceType[]> => {
|
||||
const response = await apiClient.get('/master/correspondence-types');
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<CorrespondenceType>(response.data);
|
||||
},
|
||||
|
||||
createCorrespondenceType: async (data: CreateCorrespondenceTypeDto) => {
|
||||
@@ -206,22 +216,22 @@ export const masterDataService = {
|
||||
|
||||
// --- Drawing Categories ---
|
||||
|
||||
getContractDrawingCategories: async (projectId?: number | string) => {
|
||||
getContractDrawingCategories: async (projectId?: number | string): Promise<ContractDrawingCategory[]> => {
|
||||
const response = await apiClient.get('/drawings/contract/categories', {
|
||||
params: { projectId },
|
||||
});
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<ContractDrawingCategory>(response.data);
|
||||
},
|
||||
|
||||
getShopMainCategories: async (projectId: number) => {
|
||||
getShopMainCategories: async (projectId: number): Promise<ShopMainCategory[]> => {
|
||||
const response = await apiClient.get('/drawings/shop/main-categories', { params: { projectId } });
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<ShopMainCategory>(response.data);
|
||||
},
|
||||
|
||||
getShopSubCategories: async (projectId: number, mainCategoryId?: number) => {
|
||||
getShopSubCategories: async (projectId: number, mainCategoryId?: number): Promise<ShopSubCategory[]> => {
|
||||
const response = await apiClient.get('/drawings/shop/sub-categories', {
|
||||
params: { projectId, mainCategoryId },
|
||||
});
|
||||
return extractArrayData(response.data);
|
||||
return extractArrayData<ShopSubCategory>(response.data);
|
||||
},
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// File: proxy.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { _NextRequest } from 'next/server';
|
||||
|
||||
import { auth } from '@/lib/auth';
|
||||
|
||||
// รายการ Route ที่ไม่ต้อง Login ก็เข้าได้ (Public Routes)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AuditLog } from '@/lib/services/audit-log.service';
|
||||
|
||||
|
||||
export interface AuditErrorRecord {
|
||||
code: string;
|
||||
@@ -7,8 +7,17 @@ export interface AuditErrorRecord {
|
||||
context?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface NumberingAuditLog {
|
||||
id: number;
|
||||
documentNumber: string;
|
||||
operation: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
createdBy?: string;
|
||||
}
|
||||
|
||||
export interface NumberingMetrics {
|
||||
audit: AuditLog[];
|
||||
audit: NumberingAuditLog[];
|
||||
errors: AuditErrorRecord[];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Master Data Entity Types
|
||||
*/
|
||||
|
||||
export interface CorrespondenceType {
|
||||
id: number;
|
||||
typeCode: string;
|
||||
typeName: string;
|
||||
isActive: boolean;
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export interface Discipline {
|
||||
id: number;
|
||||
disciplineCode: string;
|
||||
codeNameEn: string;
|
||||
codeNameTh?: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface RfaType {
|
||||
id: number;
|
||||
typeCode: string;
|
||||
typeNameTh: string;
|
||||
typeNameEn?: string;
|
||||
remark?: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: number;
|
||||
tag_name: string;
|
||||
color_code?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface DrawingCategory {
|
||||
id: number;
|
||||
subTypeCode: string;
|
||||
subTypeName: string;
|
||||
subTypeNumber?: string;
|
||||
}
|
||||
|
||||
export interface ShopMainCategory {
|
||||
id: number;
|
||||
mainCategoryCode: string;
|
||||
mainCategoryName: string;
|
||||
name?: string; // Fallback for legacy data
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface ShopSubCategory {
|
||||
id: number;
|
||||
subCategoryCode: string;
|
||||
subCategoryName: string;
|
||||
name?: string; // Fallback for legacy data
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface ContractDrawingCategory {
|
||||
id: number;
|
||||
catCode: string;
|
||||
catName: string;
|
||||
name?: string; // Fallback for legacy data
|
||||
}
|
||||
Reference in New Issue
Block a user