chore(WEB): actions.ts 에러 핸들링 및 CEO 대시보드 개선

- 전체 모듈 actions.ts redirect 에러 핸들링 추가
- CEODashboard DetailModal 추가
- MonthlyExpenseSection 개선
- fetch-wrapper redirect 에러 처리
- redirect-error 유틸 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-08 18:41:15 +09:00
parent 9885085259
commit 0d539628f3
61 changed files with 1226 additions and 359 deletions

View File

@@ -14,7 +14,7 @@
'use server';
import { isRedirectError } from 'next/dist/client/components/redirect';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type {
AttendanceRecord,

View File

@@ -1,7 +1,7 @@
'use server';
import { isRedirectError } from 'next/dist/client/components/redirect';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type { Card, CardFormData, CardStatus } from './types';
@@ -274,7 +274,7 @@ export async function deleteCards(ids: string[]): Promise<{ success: boolean; er
return { success: true };
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[deleteCards] Error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}

View File

@@ -13,7 +13,7 @@
'use server';
import { isRedirectError } from 'next/dist/client/components/redirect';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
// ============================================
@@ -316,7 +316,7 @@ export async function deleteDepartmentsMany(
results,
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[deleteDepartmentsMany] Error:', error);
return {
success: false,

View File

@@ -16,7 +16,7 @@
'use server';
import { isRedirectError } from 'next/dist/client/components/redirect';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { cookies } from 'next/headers';
import { serverFetch, getServerApiHeaders } from '@/lib/api/fetch-wrapper';
import type { Employee, EmployeeFormData, EmployeeStats } from './types';
@@ -100,7 +100,7 @@ export async function getEmployees(params?: {
lastPage: result.data.last_page,
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] getEmployees error:', error);
return { data: [], total: 0, lastPage: 1 };
}
@@ -132,7 +132,7 @@ export async function getEmployeeById(id: string): Promise<Employee | null | { _
return transformApiToFrontend(result.data);
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] getEmployeeById error:', error);
return null;
}
@@ -179,7 +179,7 @@ export async function createEmployee(
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] createEmployee error:', error);
return {
success: false,
@@ -230,7 +230,7 @@ export async function updateEmployee(
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] updateEmployee error:', error);
return {
success: false,
@@ -268,7 +268,7 @@ export async function deleteEmployee(id: string): Promise<{ success: boolean; er
return { success: true };
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] deleteEmployee error:', error);
return {
success: false,
@@ -309,7 +309,7 @@ export async function deleteEmployees(ids: string[]): Promise<{ success: boolean
return { success: true };
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] deleteEmployees error:', error);
return {
success: false,
@@ -349,7 +349,7 @@ export async function getEmployeeStats(): Promise<EmployeeStats | null | { __aut
averageTenure: result.data.average_tenure ?? 0,
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] getEmployeeStats error:', error);
return null;
}
@@ -401,7 +401,7 @@ export async function getPositions(type?: 'rank' | 'title'): Promise<PositionIte
return result.data;
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] getPositions error:', error);
return [];
}
@@ -450,7 +450,7 @@ export async function getDepartments(): Promise<DepartmentItem[]> {
const departments = Array.isArray(result.data) ? result.data : result.data.data || [];
return departments;
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] getDepartments error:', error);
return [];
}
@@ -513,7 +513,7 @@ export async function uploadProfileImage(inputFormData: FormData): Promise<{
},
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[EmployeeActions] uploadProfileImage error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}

View File

@@ -1,7 +1,7 @@
'use server';
import { isRedirectError } from 'next/dist/client/components/redirect';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type { SalaryRecord, SalaryDetail, PaymentStatus } from './types';

View File

@@ -22,7 +22,7 @@
'use server';
import { isRedirectError } from 'next/dist/client/components/redirect';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
// ============================================
@@ -293,7 +293,7 @@ export async function getLeaves(params?: GetLeavesParams): Promise<{
error: result.message || '휴가 목록 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getLeaves] Error:', error);
return {
success: false,
@@ -332,7 +332,7 @@ export async function getLeaveById(id: number): Promise<{
error: result.message || '휴가 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getLeaveById] Error:', error);
return {
success: false,
@@ -379,7 +379,7 @@ export async function createLeave(
error: result.message || '휴가 신청에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[createLeave] Error:', error);
return {
success: false,
@@ -420,7 +420,7 @@ export async function approveLeave(
error: result.message || '휴가 승인에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[approveLeave] Error:', error);
return {
success: false,
@@ -461,7 +461,7 @@ export async function rejectLeave(
error: result.message || '휴가 반려에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[rejectLeave] Error:', error);
return {
success: false,
@@ -502,7 +502,7 @@ export async function cancelLeave(
error: result.message || '휴가 취소에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[cancelLeave] Error:', error);
return {
success: false,
@@ -545,7 +545,7 @@ export async function getMyLeaveBalance(year?: number): Promise<{
error: result.message || '잔여 휴가 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getMyLeaveBalance] Error:', error);
return {
success: false,
@@ -591,7 +591,7 @@ export async function getUserLeaveBalance(
error: result.message || '잔여 휴가 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getUserLeaveBalance] Error:', error);
return {
success: false,
@@ -635,7 +635,7 @@ export async function setLeaveBalance(
error: result.message || '잔여 휴가 설정에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[setLeaveBalance] Error:', error);
return {
success: false,
@@ -669,7 +669,7 @@ export async function deleteLeave(id: number): Promise<{ success: boolean; error
error: result.message || '휴가 삭제에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[deleteLeave] Error:', error);
return {
success: false,
@@ -701,7 +701,7 @@ export async function approveLeavesMany(ids: number[]): Promise<{
error: allSuccess ? undefined : '일부 휴가 승인에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[approveLeavesMany] Error:', error);
return {
success: false,
@@ -736,7 +736,7 @@ export async function rejectLeavesMany(
error: allSuccess ? undefined : '일부 휴가 반려에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[rejectLeavesMany] Error:', error);
return {
success: false,
@@ -795,7 +795,7 @@ export async function getLeaveBalances(params?: GetLeaveBalancesParams): Promise
error: result.message || '휴가 사용현황 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getLeaveBalances] Error:', error);
return {
success: false,
@@ -942,7 +942,7 @@ export async function getLeaveGrants(params?: GetLeaveGrantsParams): Promise<{
error: result.message || '휴가 부여 이력 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getLeaveGrants] Error:', error);
return {
success: false,
@@ -988,7 +988,7 @@ export async function createLeaveGrant(
error: result.message || '휴가 부여에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[createLeaveGrant] Error:', error);
return {
success: false,
@@ -1022,7 +1022,7 @@ export async function deleteLeaveGrant(id: number): Promise<{ success: boolean;
error: result.message || '휴가 부여 삭제에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[deleteLeaveGrant] Error:', error);
return {
success: false,
@@ -1116,7 +1116,7 @@ export async function getActiveEmployees(): Promise<{
error: result.message || '직원 목록 조회에 실패했습니다.',
};
} catch (error) {
if (isRedirectError(error)) throw error;
if (isNextRedirectError(error)) throw error;
console.error('[getActiveEmployees] Error:', error);
return {
success: false,