refactor(WEB): 전체 actions.ts에 공통 API 유틸 적용

- buildApiUrl / executePaginatedAction 패턴으로 전환 (40+ actions 파일)
- 직접 URLSearchParams 조립 → buildApiUrl 유틸 사용
- 수동 페이지네이션 메타 변환 → executePaginatedAction 자동 처리
- HandoverReportDocumentModal, OrderDocumentModal 개선
- 급여관리 SalaryManagement 코드 개선
- CLAUDE.md Server Action 공통 유틸 규칙 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-12 20:59:59 +09:00
parent 31be9d4a25
commit cbb38d48b9
51 changed files with 1050 additions and 1405 deletions

View File

@@ -2,12 +2,11 @@
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
import { buildApiUrl } from '@/lib/api/query-params';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { cookies } from 'next/headers';
import type { VendorReceivables, CategoryType, MonthlyAmount, ReceivablesListResponse, MemoUpdateRequest } from './types';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
// ===== API 응답 타입 =====
interface CategoryAmountApi {
category: CategoryType;
@@ -56,27 +55,19 @@ function transformItem(item: VendorReceivablesApi): VendorReceivables {
};
}
// ===== year 파라미터 헬퍼 =====
function applyYearParam(searchParams: URLSearchParams, year?: number) {
if (typeof year === 'number') {
if (year === 0) searchParams.set('recent_year', 'true');
else searchParams.set('year', String(year));
}
}
// ===== 채권 현황 목록 조회 =====
export async function getReceivablesList(params?: {
year?: number; search?: string; hasReceivable?: boolean;
}): Promise<{ success: boolean; data: ReceivablesListResponse; error?: string }> {
const searchParams = new URLSearchParams();
applyYearParam(searchParams, params?.year);
if (params?.search) searchParams.set('search', params.search);
if (params?.hasReceivable !== undefined) searchParams.set('has_receivable', params.hasReceivable ? 'true' : 'false');
const queryString = searchParams.toString();
const yearValue = params?.year;
const DEFAULT_DATA: ReceivablesListResponse = { monthLabels: [], items: [] };
const result = await executeServerAction({
url: `${API_URL}/api/v1/receivables${queryString ? `?${queryString}` : ''}`,
url: buildApiUrl('/api/v1/receivables', {
recent_year: typeof yearValue === 'number' && yearValue === 0 ? 'true' : undefined,
year: typeof yearValue === 'number' && yearValue !== 0 ? yearValue : undefined,
search: params?.search,
has_receivable: params?.hasReceivable !== undefined ? String(params.hasReceivable) : undefined,
}),
transform: (data: ReceivablesListApiResponse) => ({
monthLabels: data.month_labels || [],
items: (data.items || []).map(transformItem),
@@ -93,12 +84,12 @@ export async function getReceivablesSummary(params?: {
totalCarryForward: number; totalSales: number; totalDeposits: number; totalBills: number;
totalReceivables: number; vendorCount: number; overdueVendorCount: number;
}>> {
const searchParams = new URLSearchParams();
applyYearParam(searchParams, params?.year);
const queryString = searchParams.toString();
const yearValue = params?.year;
return executeServerAction({
url: `${API_URL}/api/v1/receivables/summary${queryString ? `?${queryString}` : ''}`,
url: buildApiUrl('/api/v1/receivables/summary', {
recent_year: typeof yearValue === 'number' && yearValue === 0 ? 'true' : undefined,
year: typeof yearValue === 'number' && yearValue !== 0 ? yearValue : undefined,
}),
transform: (data: ReceivablesSummaryApi) => ({
totalCarryForward: data.total_carry_forward,
totalSales: data.total_sales,
@@ -117,7 +108,7 @@ export async function updateOverdueStatus(
updates: Array<{ id: string; isOverdue: boolean }>
): Promise<{ success: boolean; updatedCount?: number; error?: string }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/receivables/overdue-status`,
url: buildApiUrl('/api/v1/receivables/overdue-status'),
method: 'PUT',
body: { updates: updates.map(item => ({ id: parseInt(item.id, 10), is_overdue: item.isOverdue })) },
transform: (data: { updated_count?: number }) => ({ updatedCount: data?.updated_count || updates.length }),
@@ -131,7 +122,7 @@ export async function updateMemos(
memos: MemoUpdateRequest[]
): Promise<{ success: boolean; updatedCount?: number; error?: string }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/receivables/memos`,
url: buildApiUrl('/api/v1/receivables/memos'),
method: 'PUT',
body: { memos: memos.map(item => ({ id: parseInt(item.id, 10), memo: item.memo })) },
transform: (data: { updated_count?: number }) => ({ updatedCount: data?.updated_count || memos.length }),
@@ -160,20 +151,13 @@ export async function exportReceivablesExcel(params?: {
'X-API-KEY': process.env.API_KEY || '',
};
const searchParams = new URLSearchParams();
// year=0은 "최근 1년" 옵션 - recent_year 파라미터 사용
const yearValue = params?.year;
if (typeof yearValue === 'number') {
if (yearValue === 0) {
searchParams.set('recent_year', 'true');
} else {
searchParams.set('year', String(yearValue));
}
}
if (params?.search) searchParams.set('search', params.search);
const queryString = searchParams.toString();
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivables/export${queryString ? `?${queryString}` : ''}`;
const url = buildApiUrl('/api/v1/receivables/export', {
recent_year: typeof yearValue === 'number' && yearValue === 0 ? 'true' : undefined,
year: typeof yearValue === 'number' && yearValue !== 0 ? yearValue : undefined,
search: params?.search,
});
const response = await fetch(url, {
method: 'GET',