Files
sam-react-prod/src/components/reports/actions.ts
유병철 55e0791e16 refactor(WEB): Server Action 공통화 및 보안 강화
- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일)
- sanitize 유틸 추가 (XSS 방지)
- middleware CSP 헤더 추가 및 Open Redirect 방지
- 프록시 라우트 로깅 개발환경 한정으로 변경
- 프로덕션 불필요 console.log 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:14:06 +09:00

137 lines
4.5 KiB
TypeScript

'use server';
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
import type { ComprehensiveAnalysisData } from './types';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
// ===== API 응답 타입 =====
interface TodayIssueItemApi {
id: string;
category: string;
description: string;
requires_approval: boolean;
time: string;
}
interface TodayIssueSectionApi {
filter_options: string[];
items: TodayIssueItemApi[];
}
interface CheckPointApi {
id: string;
type: 'success' | 'warning' | 'error' | 'info';
message: string;
highlight?: string;
}
interface AmountCardApi {
id: string;
label: string;
amount: number;
sub_amount?: number;
sub_label?: string;
previous_amount?: number;
previous_label?: string;
}
interface ExpenseSectionApi {
cards: AmountCardApi[];
check_points: CheckPointApi[];
}
interface ReceivableSectionApi extends ExpenseSectionApi {
has_detail_button: boolean;
detail_button_label: string;
detail_button_path: string;
}
interface ComprehensiveAnalysisDataApi {
today_issue: TodayIssueSectionApi;
monthly_expense: ExpenseSectionApi;
card_management: ExpenseSectionApi;
entertainment: ExpenseSectionApi;
welfare: ExpenseSectionApi;
receivable: ReceivableSectionApi;
debt_collection: ExpenseSectionApi;
}
// ===== API → Frontend 변환 =====
function transformCheckPoint(item: CheckPointApi): ComprehensiveAnalysisData['monthlyExpense']['checkPoints'][0] {
return { id: item.id, type: item.type, message: item.message, highlight: item.highlight };
}
function transformAmountCard(item: AmountCardApi): ComprehensiveAnalysisData['monthlyExpense']['cards'][0] {
return {
id: item.id, label: item.label, amount: item.amount,
subAmount: item.sub_amount, subLabel: item.sub_label,
previousAmount: item.previous_amount, previousLabel: item.previous_label,
};
}
function transformTodayIssueItem(item: TodayIssueItemApi): ComprehensiveAnalysisData['todayIssue']['items'][0] {
return { id: item.id, category: item.category, description: item.description, requiresApproval: item.requires_approval, time: item.time };
}
function transformExpenseSection(section: ExpenseSectionApi): ComprehensiveAnalysisData['monthlyExpense'] {
return { cards: section.cards.map(transformAmountCard), checkPoints: section.check_points.map(transformCheckPoint) };
}
function transformReceivableSection(section: ReceivableSectionApi): ComprehensiveAnalysisData['receivable'] {
return {
cards: section.cards.map(transformAmountCard),
checkPoints: section.check_points.map(transformCheckPoint),
hasDetailButton: section.has_detail_button,
detailButtonLabel: section.detail_button_label,
detailButtonPath: section.detail_button_path,
};
}
function transformAnalysisData(data: ComprehensiveAnalysisDataApi): ComprehensiveAnalysisData {
return {
todayIssue: { filterOptions: data.today_issue.filter_options, items: data.today_issue.items.map(transformTodayIssueItem) },
monthlyExpense: transformExpenseSection(data.monthly_expense),
cardManagement: transformExpenseSection(data.card_management),
entertainment: transformExpenseSection(data.entertainment),
welfare: transformExpenseSection(data.welfare),
receivable: transformReceivableSection(data.receivable),
debtCollection: transformExpenseSection(data.debt_collection),
};
}
// ===== 종합 분석 데이터 조회 =====
export async function getComprehensiveAnalysis(params?: {
date?: string;
}): Promise<ActionResult<ComprehensiveAnalysisData>> {
const searchParams = new URLSearchParams();
if (params?.date) searchParams.set('date', params.date);
const queryString = searchParams.toString();
return executeServerAction({
url: `${API_URL}/api/v1/comprehensive-analysis${queryString ? `?${queryString}` : ''}`,
transform: (data: ComprehensiveAnalysisDataApi) => transformAnalysisData(data),
errorMessage: '종합 분석 조회에 실패했습니다.',
});
}
// ===== 이슈 승인 =====
export async function approveIssue(issueId: string): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/approvals/${issueId}/approve`,
method: 'POST',
errorMessage: '승인에 실패했습니다.',
});
}
// ===== 이슈 반려 =====
export async function rejectIssue(issueId: string, reason?: string): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/approvals/${issueId}/reject`,
method: 'POST',
body: { comment: reason },
errorMessage: '반려에 실패했습니다.',
});
}