- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션 - 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts) - ApiErrorContext 추가로 전역 에러 처리 개선 - HR EmployeeForm 컴포넌트 개선 - 참조함(ReferenceBox) 기능 수정 - juil 테스트 URL 페이지 추가 - claudedocs 문서 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
705 lines
20 KiB
TypeScript
705 lines
20 KiB
TypeScript
/**
|
|
* 작업지시 관리 서버 액션
|
|
*
|
|
* API Endpoints:
|
|
* - GET /api/v1/work-orders - 목록 조회
|
|
* - GET /api/v1/work-orders/stats - 통계 조회
|
|
* - GET /api/v1/work-orders/{id} - 상세 조회
|
|
* - POST /api/v1/work-orders - 등록
|
|
* - PUT /api/v1/work-orders/{id} - 수정
|
|
* - DELETE /api/v1/work-orders/{id} - 삭제
|
|
* - PATCH /api/v1/work-orders/{id}/status - 상태 변경
|
|
* - PATCH /api/v1/work-orders/{id}/assign - 담당자 배정
|
|
* - PATCH /api/v1/work-orders/{id}/bending/toggle - 벤딩 필드 토글
|
|
* - POST /api/v1/work-orders/{id}/issues - 이슈 등록
|
|
* - PATCH /api/v1/work-orders/{id}/issues/{issueId}/resolve - 이슈 해결
|
|
*/
|
|
|
|
'use server';
|
|
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
import type {
|
|
WorkOrder,
|
|
WorkOrderStats,
|
|
WorkOrderStatus,
|
|
ProcessType,
|
|
WorkOrderApiPaginatedResponse,
|
|
WorkOrderStatsApi,
|
|
} from './types';
|
|
import {
|
|
transformApiToFrontend,
|
|
transformFrontendToApi,
|
|
transformStatsApiToFrontend,
|
|
} from './types';
|
|
|
|
// ===== 페이지네이션 타입 =====
|
|
interface PaginationMeta {
|
|
currentPage: number;
|
|
lastPage: number;
|
|
perPage: number;
|
|
total: number;
|
|
}
|
|
|
|
// ===== 작업지시 목록 조회 =====
|
|
export async function getWorkOrders(params?: {
|
|
page?: number;
|
|
perPage?: number;
|
|
status?: WorkOrderStatus | 'all';
|
|
processType?: ProcessType | 'all';
|
|
search?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: WorkOrder[];
|
|
pagination: PaginationMeta;
|
|
error?: string;
|
|
}> {
|
|
const emptyResponse = {
|
|
success: false,
|
|
data: [] as WorkOrder[],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
};
|
|
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (params?.page) searchParams.set('page', String(params.page));
|
|
if (params?.perPage) searchParams.set('per_page', String(params.perPage));
|
|
if (params?.status && params.status !== 'all') {
|
|
searchParams.set('status', params.status);
|
|
}
|
|
if (params?.processType && params.processType !== 'all') {
|
|
searchParams.set('process_type', params.processType);
|
|
}
|
|
if (params?.search) searchParams.set('search', params.search);
|
|
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
|
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders${queryString ? `?${queryString}` : ''}`;
|
|
|
|
console.log('[WorkOrderActions] GET work-orders:', url);
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response) {
|
|
return { ...emptyResponse, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.warn('[WorkOrderActions] GET work-orders error:', response.status);
|
|
return { ...emptyResponse, error: `API 오류: ${response.status}` };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
...emptyResponse,
|
|
error: result.message || '작업지시 목록 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
const paginatedData: WorkOrderApiPaginatedResponse = result.data || {
|
|
data: [],
|
|
current_page: 1,
|
|
last_page: 1,
|
|
per_page: 20,
|
|
total: 0,
|
|
};
|
|
|
|
const workOrders = (paginatedData.data || []).map(transformApiToFrontend);
|
|
|
|
return {
|
|
success: true,
|
|
data: workOrders,
|
|
pagination: {
|
|
currentPage: paginatedData.current_page,
|
|
lastPage: paginatedData.last_page,
|
|
perPage: paginatedData.per_page,
|
|
total: paginatedData.total,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] getWorkOrders error:', error);
|
|
return { ...emptyResponse, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 작업지시 통계 조회 =====
|
|
export async function getWorkOrderStats(): Promise<{
|
|
success: boolean;
|
|
data?: WorkOrderStats;
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/stats`;
|
|
|
|
console.log('[WorkOrderActions] GET stats:', url);
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.warn('[WorkOrderActions] GET stats error:', response.status);
|
|
return { success: false, error: `API 오류: ${response.status}` };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '통계 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
const statsApi: WorkOrderStatsApi = result.data;
|
|
|
|
return {
|
|
success: true,
|
|
data: transformStatsApiToFrontend(statsApi),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] getWorkOrderStats error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 작업지시 상세 조회 =====
|
|
export async function getWorkOrderById(id: string): Promise<{
|
|
success: boolean;
|
|
data?: WorkOrder;
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}`;
|
|
|
|
console.log('[WorkOrderActions] GET work-order:', url);
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.error('[WorkOrderActions] GET work-order error:', response.status);
|
|
return { success: false, error: `API 오류: ${response.status}` };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success || !result.data) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '작업지시 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] getWorkOrderById error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 작업지시 등록 =====
|
|
export async function createWorkOrder(
|
|
data: Partial<WorkOrder> & {
|
|
salesOrderId?: number;
|
|
assigneeId?: number;
|
|
teamId?: number;
|
|
}
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
const apiData = {
|
|
...transformFrontendToApi(data),
|
|
sales_order_id: data.salesOrderId,
|
|
assignee_id: data.assigneeId,
|
|
team_id: data.teamId,
|
|
};
|
|
|
|
console.log('[WorkOrderActions] POST work-order request:', apiData);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(apiData),
|
|
}
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] POST work-order response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '작업지시 등록에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] createWorkOrder error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 작업지시 수정 =====
|
|
export async function updateWorkOrder(
|
|
id: string,
|
|
data: Partial<WorkOrder>
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
const apiData = transformFrontendToApi(data);
|
|
|
|
console.log('[WorkOrderActions] PUT work-order request:', apiData);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}`,
|
|
{
|
|
method: 'PUT',
|
|
body: JSON.stringify(apiData),
|
|
}
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] PUT work-order response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '작업지시 수정에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] updateWorkOrder error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 작업지시 삭제 =====
|
|
export async function deleteWorkOrder(id: string): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}`,
|
|
{ method: 'DELETE' }
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] DELETE work-order response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '작업지시 삭제에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] deleteWorkOrder error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 작업지시 상태 변경 =====
|
|
export async function updateWorkOrderStatus(
|
|
id: string,
|
|
status: WorkOrderStatus
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
console.log('[WorkOrderActions] PATCH status request:', { status });
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}/status`,
|
|
{
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ status }),
|
|
}
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] PATCH status response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '상태 변경에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] updateWorkOrderStatus error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 담당자 배정 =====
|
|
export async function assignWorkOrder(
|
|
id: string,
|
|
assigneeId: number,
|
|
teamId?: number
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
const body: { assignee_id: number; team_id?: number } = { assignee_id: assigneeId };
|
|
if (teamId) body.team_id = teamId;
|
|
|
|
console.log('[WorkOrderActions] PATCH assign request:', body);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}/assign`,
|
|
{
|
|
method: 'PATCH',
|
|
body: JSON.stringify(body),
|
|
}
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] PATCH assign response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '담당자 배정에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] assignWorkOrder error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 벤딩 필드 토글 =====
|
|
export async function toggleBendingField(
|
|
id: string,
|
|
field: string
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
console.log('[WorkOrderActions] PATCH bending toggle request:', { field });
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}/bending/toggle`,
|
|
{
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ field }),
|
|
}
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] PATCH bending toggle response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '벤딩 필드 토글에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] toggleBendingField error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 이슈 등록 =====
|
|
export async function addWorkOrderIssue(
|
|
id: string,
|
|
data: {
|
|
title: string;
|
|
description?: string;
|
|
priority?: 'low' | 'medium' | 'high';
|
|
}
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
console.log('[WorkOrderActions] POST issue request:', data);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${id}/issues`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
}
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] POST issue response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '이슈 등록에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] addWorkOrderIssue error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 이슈 해결 =====
|
|
export async function resolveWorkOrderIssue(
|
|
workOrderId: string,
|
|
issueId: string
|
|
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
|
|
try {
|
|
console.log('[WorkOrderActions] PATCH issue resolve:', { workOrderId, issueId });
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/work-orders/${workOrderId}/issues/${issueId}/resolve`,
|
|
{ method: 'PATCH' }
|
|
);
|
|
|
|
if (error || !response) {
|
|
return { success: false, error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('[WorkOrderActions] PATCH issue resolve response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return {
|
|
success: false,
|
|
error: result.message || '이슈 해결 처리에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: transformApiToFrontend(result.data),
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] resolveWorkOrderIssue error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 수주 목록 조회 (작업지시 생성용) =====
|
|
export interface SalesOrderForWorkOrder {
|
|
id: number;
|
|
orderNo: string;
|
|
client: string;
|
|
projectName: string;
|
|
dueDate: string;
|
|
status: string;
|
|
itemCount: number;
|
|
splitCount: number;
|
|
}
|
|
|
|
export async function getSalesOrdersForWorkOrder(params?: {
|
|
q?: string;
|
|
status?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: SalesOrderForWorkOrder[];
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
// 작업지시 생성 가능한 상태만 조회 (예: 회계확인 완료)
|
|
searchParams.set('for_work_order', '1');
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
if (params?.status) searchParams.set('status', params.status);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/sales-orders${queryString ? `?${queryString}` : ''}`;
|
|
|
|
console.log('[WorkOrderActions] GET sales-orders for work-order:', url);
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response) {
|
|
return { success: false, data: [], error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.warn('[WorkOrderActions] GET sales-orders error:', response.status);
|
|
return { success: false, data: [], error: `API 오류: ${response.status}` };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
error: result.message || '수주 목록 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
// API 응답 변환
|
|
const salesOrders: SalesOrderForWorkOrder[] = (result.data?.data || result.data || []).map(
|
|
(item: {
|
|
id: number;
|
|
order_no: string;
|
|
client?: { name: string };
|
|
project_name?: string;
|
|
due_date?: string;
|
|
status: string;
|
|
items_count?: number;
|
|
split_count?: number;
|
|
}) => ({
|
|
id: item.id,
|
|
orderNo: item.order_no,
|
|
client: item.client?.name || '-',
|
|
projectName: item.project_name || '-',
|
|
dueDate: item.due_date || '-',
|
|
status: item.status,
|
|
itemCount: item.items_count || 0,
|
|
splitCount: item.split_count || 0,
|
|
})
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
data: salesOrders,
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] getSalesOrdersForWorkOrder error:', error);
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 부서 + 사용자 조회 (담당자 선택용) =====
|
|
export interface DepartmentUser {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
}
|
|
|
|
export interface DepartmentWithUsers {
|
|
id: number;
|
|
name: string;
|
|
code: string | null;
|
|
users: DepartmentUser[];
|
|
children: DepartmentWithUsers[];
|
|
}
|
|
|
|
export async function getDepartmentsWithUsers(): Promise<{
|
|
success: boolean;
|
|
data: DepartmentWithUsers[];
|
|
error?: string;
|
|
}> {
|
|
try {
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/departments/tree?with_users=1`;
|
|
|
|
console.log('[WorkOrderActions] GET departments with users:', url);
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response) {
|
|
return { success: false, data: [], error: error?.message || 'API 요청 실패' };
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.warn('[WorkOrderActions] GET departments error:', response.status);
|
|
return { success: false, data: [], error: `API 오류: ${response.status}` };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
error: result.message || '부서 목록 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
// API 응답을 프론트엔드 형식으로 변환
|
|
const transformDepartment = (dept: {
|
|
id: number;
|
|
name: string;
|
|
code: string | null;
|
|
users?: { id: number; name: string; email: string }[];
|
|
children?: unknown[];
|
|
}): DepartmentWithUsers => ({
|
|
id: dept.id,
|
|
name: dept.name,
|
|
code: dept.code,
|
|
users: (dept.users || []).map((u) => ({
|
|
id: u.id,
|
|
name: u.name,
|
|
email: u.email,
|
|
})),
|
|
children: (dept.children || []).map((child) =>
|
|
transformDepartment(child as typeof dept)
|
|
),
|
|
});
|
|
|
|
const departments = (result.data || []).map(transformDepartment);
|
|
|
|
return {
|
|
success: true,
|
|
data: departments,
|
|
};
|
|
} catch (error) {
|
|
console.error('[WorkOrderActions] getDepartmentsWithUsers error:', error);
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|