260323:1050 fix CI : Verify Build frontend #02 correct _???
CI / CD Pipeline / build (push) Successful in 15m14s
CI / CD Pipeline / release (push) Failing after 20s
CI / CD Pipeline / deploy (push) Has been skipped

This commit is contained in:
admin
2026-03-23 10:50:20 +07:00
parent 32141f519a
commit e3c476f011
31 changed files with 3587 additions and 374 deletions
@@ -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[] }) {
+30 -35
View File
@@ -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);
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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() });
},
+26 -16
View File
@@ -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
View File
@@ -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)
+11 -2
View File
@@ -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[];
}
+65
View File
@@ -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
}