260220:1700 20260220 TASK-BEFE-001 Refactor by ADR-014 #5
All checks were successful
Build and Deploy / deploy (push) Successful in 2m33s

This commit is contained in:
admin
2026-02-20 17:00:10 +07:00
parent 47520fce96
commit 0f114f19b5
6 changed files with 88 additions and 84 deletions

View File

@@ -13,13 +13,22 @@ import {
Shield, Shield,
Menu, Menu,
Layers, Layers,
BookOpen, LucideIcon,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useState } from 'react'; import { useState } from 'react';
import { Can } from '@/components/common/can'; import { Can } from '@/components/common/can';
import { useAuthStore } from '@/lib/stores/auth-store';
export const mainNavItems = [ export type NavItem = {
title: string;
href: string;
icon: LucideIcon;
permission?: string | null;
adminOnly?: boolean;
};
export const mainNavItems: NavItem[] = [
{ {
title: 'Dashboard', title: 'Dashboard',
href: '/dashboard', href: '/dashboard',
@@ -67,24 +76,7 @@ export const mainNavItems = [
href: '/admin', href: '/admin',
icon: Shield, icon: Shield,
permission: null, permission: null,
}, adminOnly: true,
{
title: 'Security',
href: '/admin/access-control/roles',
icon: Shield,
permission: 'system.manage_security',
},
{
title: 'System Logs',
href: '/admin/monitoring/system-logs/numbering',
icon: Layers,
permission: 'system.view_logs',
},
{
title: 'Reference Data',
href: '/admin/doc-control/reference',
icon: BookOpen,
permission: 'master_data.view',
}, },
]; ];
@@ -95,6 +87,8 @@ interface SidebarProps {
export function Sidebar({ className }: SidebarProps) { export function Sidebar({ className }: SidebarProps) {
const pathname = usePathname(); const pathname = usePathname();
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const user = useAuthStore((state) => state.user);
const isAdmin = user?.role === 'ADMIN' || user?.role === 'DC';
return ( return (
<div <div
@@ -119,6 +113,8 @@ export function Sidebar({ className }: SidebarProps) {
<div className="flex-1 overflow-y-auto py-4"> <div className="flex-1 overflow-y-auto py-4">
<nav className="grid gap-1 px-2"> <nav className="grid gap-1 px-2">
{mainNavItems.map((item, index) => { {mainNavItems.map((item, index) => {
if (item.adminOnly && !isAdmin) return null;
const isActive = pathname.startsWith(item.href); const isActive = pathname.startsWith(item.href);
const LinkComponent = ( const LinkComponent = (
@@ -172,6 +168,8 @@ import { Sheet, SheetContent, SheetTrigger, SheetTitle } from '@/components/ui/s
export function MobileSidebar() { export function MobileSidebar() {
const pathname = usePathname(); const pathname = usePathname();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const user = useAuthStore((state) => state.user);
const isAdmin = user?.role === 'ADMIN' || user?.role === 'DC';
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
@@ -189,6 +187,8 @@ export function MobileSidebar() {
<div className="flex-1 overflow-y-auto py-4 h-[calc(100vh-4rem)]"> <div className="flex-1 overflow-y-auto py-4 h-[calc(100vh-4rem)]">
<nav className="grid gap-1 px-2"> <nav className="grid gap-1 px-2">
{mainNavItems.map((item, index) => { {mainNavItems.map((item, index) => {
if (item.adminOnly && !isAdmin) return null;
const isActive = pathname.startsWith(item.href); const isActive = pathname.startsWith(item.href);
const LinkComponent = ( const LinkComponent = (

View File

@@ -6,7 +6,7 @@ import { SearchContractDrawingDto, CreateContractDrawingDto } from '@/types/dto/
import { SearchShopDrawingDto, CreateShopDrawingDto } from '@/types/dto/drawing/shop-drawing.dto'; import { SearchShopDrawingDto, CreateShopDrawingDto } from '@/types/dto/drawing/shop-drawing.dto';
import { SearchAsBuiltDrawingDto, CreateAsBuiltDrawingDto } from '@/types/dto/drawing/asbuilt-drawing.dto'; import { SearchAsBuiltDrawingDto, CreateAsBuiltDrawingDto } from '@/types/dto/drawing/asbuilt-drawing.dto';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { ContractDrawing, ShopDrawing, AsBuiltDrawing } from "@/types/drawing"; import { ContractDrawing, ShopDrawing, AsBuiltDrawing } from '@/types/drawing';
type DrawingType = 'CONTRACT' | 'SHOP' | 'AS_BUILT'; type DrawingType = 'CONTRACT' | 'SHOP' | 'AS_BUILT';
type DrawingSearchParams = SearchContractDrawingDto | SearchShopDrawingDto | SearchAsBuiltDrawingDto; type DrawingSearchParams = SearchContractDrawingDto | SearchShopDrawingDto | SearchAsBuiltDrawingDto;
@@ -31,38 +31,44 @@ export function useDrawings(type: DrawingType, params: DrawingSearchParams) {
response = await contractDrawingService.getAll(params as SearchContractDrawingDto); response = await contractDrawingService.getAll(params as SearchContractDrawingDto);
// Map ContractDrawing to Drawing // Map ContractDrawing to Drawing
if (response && response.data) { if (response && response.data) {
response.data = response.data.map((d: ContractDrawing) => ({ const mappedData = response.data.map((d: ContractDrawing) => ({
...d, ...d,
drawingId: d.id, drawingId: d.id,
drawingNumber: d.contractDrawingNo, drawingNumber: d.contractDrawingNo,
type: 'CONTRACT', type: 'CONTRACT',
})); }));
// Re-wrap to preserve meta
response = { ...response, data: mappedData };
} }
} else if (type === 'SHOP') { } else if (type === 'SHOP') {
response = await shopDrawingService.getAll(params as SearchShopDrawingDto); response = await shopDrawingService.getAll(params as SearchShopDrawingDto);
// Map ShopDrawing to Drawing // Map ShopDrawing to Drawing
if (response && response.data) { if (response && response.data) {
response.data = response.data.map((d: ShopDrawing) => ({ const mappedData = response.data.map((d: ShopDrawing) => ({
...d, ...d,
drawingId: d.id, drawingId: d.id,
type: 'SHOP', type: 'SHOP',
title: d.currentRevision?.title || "Untitled", title: d.currentRevision?.title || 'Untitled',
revision: d.currentRevision?.revisionNumber, revision: d.currentRevision?.revisionNumber,
legacyDrawingNumber: d.currentRevision?.legacyDrawingNumber, legacyDrawingNumber: d.currentRevision?.legacyDrawingNumber,
})); }));
// Re-wrap to preserve meta
response = { ...response, data: mappedData };
} }
} else { } else {
response = await asBuiltDrawingService.getAll(params as SearchAsBuiltDrawingDto); response = await asBuiltDrawingService.getAll(params as SearchAsBuiltDrawingDto);
// Map AsBuiltDrawing to Drawing // Map AsBuiltDrawing to Drawing
if (response && response.data) { if (response && response.data) {
response.data = response.data.map((d: AsBuiltDrawing) => ({ const mappedData = response.data.map((d: AsBuiltDrawing) => ({
...d, ...d,
drawingId: d.id, drawingId: d.id,
type: 'AS_BUILT', type: 'AS_BUILT',
title: d.currentRevision?.title || "Untitled", title: d.currentRevision?.title || 'Untitled',
revision: d.currentRevision?.revisionNumber, revision: d.currentRevision?.revisionNumber,
})); }));
} // Re-wrap to preserve meta
response = { ...response, data: mappedData };
}
} }
return response; return response;
}, },

View File

@@ -1,17 +1,17 @@
// File: lib/services/asbuilt-drawing.service.ts // File: lib/services/asbuilt-drawing.service.ts
import apiClient from "@/lib/api/client"; import apiClient from '@/lib/api/client';
import { import {
CreateAsBuiltDrawingDto, CreateAsBuiltDrawingDto,
CreateAsBuiltDrawingRevisionDto, CreateAsBuiltDrawingRevisionDto,
SearchAsBuiltDrawingDto SearchAsBuiltDrawingDto,
} from "@/types/dto/drawing/asbuilt-drawing.dto"; } from '@/types/dto/drawing/asbuilt-drawing.dto';
export const asBuiltDrawingService = { export const asBuiltDrawingService = {
/** /**
* Get As Built Drawings list * Get As Built Drawings list
*/ */
getAll: async (params: SearchAsBuiltDrawingDto) => { getAll: async (params: SearchAsBuiltDrawingDto) => {
const response = await apiClient.get("/drawings/asbuilt", { params }); const response = await apiClient.get('/drawings/asbuilt', { params });
return response.data; return response.data;
}, },
@@ -27,7 +27,7 @@ export const asBuiltDrawingService = {
* Create New As Built Drawing * Create New As Built Drawing
*/ */
create: async (data: CreateAsBuiltDrawingDto | FormData) => { create: async (data: CreateAsBuiltDrawingDto | FormData) => {
const response = await apiClient.post("/drawings/asbuilt", data); const response = await apiClient.post('/drawings/asbuilt', data);
return response.data; return response.data;
}, },
@@ -37,5 +37,5 @@ export const asBuiltDrawingService = {
createRevision: async (id: string | number, data: CreateAsBuiltDrawingRevisionDto) => { createRevision: async (id: string | number, data: CreateAsBuiltDrawingRevisionDto) => {
const response = await apiClient.post(`/drawings/asbuilt/${id}/revisions`, data); const response = await apiClient.post(`/drawings/asbuilt/${id}/revisions`, data);
return response.data; return response.data;
} },
}; };

View File

@@ -1,18 +1,16 @@
// File: lib/services/contract-drawing.service.ts // File: lib/services/contract-drawing.service.ts
import apiClient from "@/lib/api/client"; import apiClient from '@/lib/api/client';
import { import {
CreateContractDrawingDto, CreateContractDrawingDto,
UpdateContractDrawingDto, UpdateContractDrawingDto,
SearchContractDrawingDto SearchContractDrawingDto,
} from "@/types/dto/drawing/contract-drawing.dto"; } from '@/types/dto/drawing/contract-drawing.dto';
export const contractDrawingService = { export const contractDrawingService = {
/**
* ดึงรายการแบบสัญญา (Contract Drawings)
*/
getAll: async (params: SearchContractDrawingDto) => { getAll: async (params: SearchContractDrawingDto) => {
// GET /drawings/contract?projectId=1&page=1... // GET /drawings/contract?projectId=1&page=1...
const response = await apiClient.get("/drawings/contract", { params }); const response = await apiClient.get('/drawings/contract', { params });
// The interceptor returns { statusCode, message, data, meta }
return response.data; return response.data;
}, },
@@ -28,7 +26,7 @@ export const contractDrawingService = {
* สร้างแบบสัญญาใหม่ * สร้างแบบสัญญาใหม่
*/ */
create: async (data: CreateContractDrawingDto | FormData) => { create: async (data: CreateContractDrawingDto | FormData) => {
const response = await apiClient.post("/drawings/contract", data); const response = await apiClient.post('/drawings/contract', data);
return response.data; return response.data;
}, },
@@ -46,5 +44,5 @@ export const contractDrawingService = {
delete: async (id: string | number) => { delete: async (id: string | number) => {
const response = await apiClient.delete(`/drawings/contract/${id}`); const response = await apiClient.delete(`/drawings/contract/${id}`);
return response.data; return response.data;
} },
}; };

View File

@@ -1,17 +1,17 @@
// File: lib/services/shop-drawing.service.ts // File: lib/services/shop-drawing.service.ts
import apiClient from "@/lib/api/client"; import apiClient from '@/lib/api/client';
import { import {
CreateShopDrawingDto, CreateShopDrawingDto,
CreateShopDrawingRevisionDto, CreateShopDrawingRevisionDto,
SearchShopDrawingDto SearchShopDrawingDto,
} from "@/types/dto/drawing/shop-drawing.dto"; } from '@/types/dto/drawing/shop-drawing.dto';
export const shopDrawingService = { export const shopDrawingService = {
/** /**
* ดึงรายการแบบก่อสร้าง (Shop Drawings) * ดึงรายการแบบก่อสร้าง (Shop Drawings)
*/ */
getAll: async (params: SearchShopDrawingDto) => { getAll: async (params: SearchShopDrawingDto) => {
const response = await apiClient.get("/drawings/shop", { params }); const response = await apiClient.get('/drawings/shop', { params });
return response.data; return response.data;
}, },
@@ -27,7 +27,7 @@ export const shopDrawingService = {
* สร้าง Shop Drawing ใหม่ (พร้อม Revision 0) * สร้าง Shop Drawing ใหม่ (พร้อม Revision 0)
*/ */
create: async (data: CreateShopDrawingDto | FormData) => { create: async (data: CreateShopDrawingDto | FormData) => {
const response = await apiClient.post("/drawings/shop", data); const response = await apiClient.post('/drawings/shop', data);
return response.data; return response.data;
}, },
@@ -37,5 +37,5 @@ export const shopDrawingService = {
createRevision: async (id: string | number, data: CreateShopDrawingRevisionDto) => { createRevision: async (id: string | number, data: CreateShopDrawingRevisionDto) => {
const response = await apiClient.post(`/drawings/shop/${id}/revisions`, data); const response = await apiClient.post(`/drawings/shop/${id}/revisions`, data);
return response.data; return response.data;
} },
}; };

View File

@@ -1,11 +1,11 @@
// File: lib/services/workflow-engine.service.ts // File: lib/services/workflow-engine.service.ts
import apiClient from "@/lib/api/client"; import apiClient from '@/lib/api/client';
import { import {
CreateWorkflowDefinitionDto, CreateWorkflowDefinitionDto,
UpdateWorkflowDefinitionDto, UpdateWorkflowDefinitionDto,
EvaluateWorkflowDto, EvaluateWorkflowDto,
GetAvailableActionsDto GetAvailableActionsDto,
} from "@/types/dto/workflow-engine/workflow-engine.dto"; } from '@/types/dto/workflow-engine/workflow-engine.dto';
export const workflowEngineService = { export const workflowEngineService = {
// --- Engine Execution (Low-Level) --- // --- Engine Execution (Low-Level) ---
@@ -15,8 +15,8 @@ export const workflowEngineService = {
* POST /workflow-engine/available-actions * POST /workflow-engine/available-actions
*/ */
getAvailableActions: async (data: GetAvailableActionsDto) => { getAvailableActions: async (data: GetAvailableActionsDto) => {
const response = await apiClient.post("/workflow-engine/available-actions", data); const response = await apiClient.post('/workflow-engine/available-actions', data);
return response.data; // string[] e.g. ['APPROVE', 'REJECT'] return response.data?.data || response.data; // string[] e.g. ['APPROVE', 'REJECT']
}, },
/** /**
@@ -24,8 +24,8 @@ export const workflowEngineService = {
* POST /workflow-engine/evaluate * POST /workflow-engine/evaluate
*/ */
evaluate: async (data: EvaluateWorkflowDto) => { evaluate: async (data: EvaluateWorkflowDto) => {
const response = await apiClient.post("/workflow-engine/evaluate", data); const response = await apiClient.post('/workflow-engine/evaluate', data);
return response.data; // { nextState: '...', events: [...] } return response.data?.data || response.data; // { nextState: '...', events: [...] }
}, },
// --- Definition Management (Admin / Workflow Editor) --- // --- Definition Management (Admin / Workflow Editor) ---
@@ -35,8 +35,8 @@ export const workflowEngineService = {
* GET /workflow-engine/definitions * GET /workflow-engine/definitions
*/ */
getDefinitions: async () => { getDefinitions: async () => {
const response = await apiClient.get("/workflow-engine/definitions"); const response = await apiClient.get('/workflow-engine/definitions');
return response.data; return response.data?.data || response.data;
}, },
/** /**
@@ -45,7 +45,7 @@ export const workflowEngineService = {
*/ */
getDefinitionById: async (id: string | number) => { getDefinitionById: async (id: string | number) => {
const response = await apiClient.get(`/workflow-engine/definitions/${id}`); const response = await apiClient.get(`/workflow-engine/definitions/${id}`);
return response.data; return response.data?.data || response.data;
}, },
/** /**
@@ -53,8 +53,8 @@ export const workflowEngineService = {
* POST /workflow-engine/definitions * POST /workflow-engine/definitions
*/ */
createDefinition: async (data: CreateWorkflowDefinitionDto) => { createDefinition: async (data: CreateWorkflowDefinitionDto) => {
const response = await apiClient.post("/workflow-engine/definitions", data); const response = await apiClient.post('/workflow-engine/definitions', data);
return response.data; return response.data?.data || response.data;
}, },
/** /**
@@ -63,7 +63,7 @@ export const workflowEngineService = {
*/ */
updateDefinition: async (id: string | number, data: UpdateWorkflowDefinitionDto) => { updateDefinition: async (id: string | number, data: UpdateWorkflowDefinitionDto) => {
const response = await apiClient.patch(`/workflow-engine/definitions/${id}`, data); const response = await apiClient.patch(`/workflow-engine/definitions/${id}`, data);
return response.data; return response.data?.data || response.data;
}, },
/** /**
@@ -72,6 +72,6 @@ export const workflowEngineService = {
*/ */
deleteDefinition: async (id: string | number) => { deleteDefinition: async (id: string | number) => {
const response = await apiClient.delete(`/workflow-engine/definitions/${id}`); const response = await apiClient.delete(`/workflow-engine/definitions/${id}`);
return response.data; return response.data?.data || response.data;
} },
}; };