Files
sam-react-prod/src/components/settings/NotificationSettings/actions.ts
byeongcheolryu 0d539628f3 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>
2026-01-08 18:41:15 +09:00

170 lines
6.2 KiB
TypeScript

'use server';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type { NotificationSettings } from './types';
import { DEFAULT_NOTIFICATION_SETTINGS } from './types';
// ===== 알림 설정 조회 =====
export async function getNotificationSettings(): Promise<{
success: boolean;
data: NotificationSettings;
error?: string;
__authError?: boolean;
}> {
try {
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/settings/notifications`,
{
method: 'GET',
cache: 'no-store',
}
);
if (error) {
return {
success: false,
data: DEFAULT_NOTIFICATION_SETTINGS,
error: error.message,
__authError: error.code === 'UNAUTHORIZED',
};
}
if (!response || !response.ok) {
console.warn('[NotificationActions] GET settings error:', response?.status);
return {
success: true,
data: DEFAULT_NOTIFICATION_SETTINGS,
};
}
const result = await response.json();
if (!result.success || !result.data) {
return {
success: true,
data: DEFAULT_NOTIFICATION_SETTINGS,
};
}
// API → Frontend 변환
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[NotificationActions] getNotificationSettings error:', error);
return {
success: true,
data: DEFAULT_NOTIFICATION_SETTINGS,
};
}
}
// ===== 알림 설정 저장 =====
export async function saveNotificationSettings(
settings: NotificationSettings
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
try {
const apiData = transformFrontendToApi(settings);
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/settings/notifications`,
{
method: 'PUT',
body: JSON.stringify(apiData),
}
);
if (error) {
return {
success: false,
error: error.message,
__authError: error.code === 'UNAUTHORIZED',
};
}
if (!response) {
return {
success: false,
error: '알림 설정 저장에 실패했습니다.',
};
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '알림 설정 저장에 실패했습니다.',
};
}
return { success: true };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[NotificationActions] saveNotificationSettings error:', error);
return {
success: false,
error: '서버 오류가 발생했습니다.',
};
}
}
// ===== API → Frontend 변환 (기본값과 병합) =====
function transformApiToFrontend(apiData: Record<string, unknown>): NotificationSettings {
// API 응답에 soundType이 없을 수 있으므로 기본값과 병합
const data = apiData as Partial<NotificationSettings>;
return {
notice: {
enabled: data.notice?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.notice.enabled,
notice: { ...DEFAULT_NOTIFICATION_SETTINGS.notice.notice, ...data.notice?.notice },
event: { ...DEFAULT_NOTIFICATION_SETTINGS.notice.event, ...data.notice?.event },
},
schedule: {
enabled: data.schedule?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.schedule.enabled,
vatReport: { ...DEFAULT_NOTIFICATION_SETTINGS.schedule.vatReport, ...data.schedule?.vatReport },
incomeTaxReport: { ...DEFAULT_NOTIFICATION_SETTINGS.schedule.incomeTaxReport, ...data.schedule?.incomeTaxReport },
},
vendor: {
enabled: data.vendor?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.vendor.enabled,
newVendor: { ...DEFAULT_NOTIFICATION_SETTINGS.vendor.newVendor, ...data.vendor?.newVendor },
creditRating: { ...DEFAULT_NOTIFICATION_SETTINGS.vendor.creditRating, ...data.vendor?.creditRating },
},
attendance: {
enabled: data.attendance?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.attendance.enabled,
annualLeave: { ...DEFAULT_NOTIFICATION_SETTINGS.attendance.annualLeave, ...data.attendance?.annualLeave },
clockIn: { ...DEFAULT_NOTIFICATION_SETTINGS.attendance.clockIn, ...data.attendance?.clockIn },
late: { ...DEFAULT_NOTIFICATION_SETTINGS.attendance.late, ...data.attendance?.late },
absent: { ...DEFAULT_NOTIFICATION_SETTINGS.attendance.absent, ...data.attendance?.absent },
},
order: {
enabled: data.order?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.order.enabled,
salesOrder: { ...DEFAULT_NOTIFICATION_SETTINGS.order.salesOrder, ...data.order?.salesOrder },
purchaseOrder: { ...DEFAULT_NOTIFICATION_SETTINGS.order.purchaseOrder, ...data.order?.purchaseOrder },
approvalRequest: { ...DEFAULT_NOTIFICATION_SETTINGS.order.approvalRequest, ...data.order?.approvalRequest },
},
approval: {
enabled: data.approval?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.approval.enabled,
approvalRequest: { ...DEFAULT_NOTIFICATION_SETTINGS.approval.approvalRequest, ...data.approval?.approvalRequest },
draftApproved: { ...DEFAULT_NOTIFICATION_SETTINGS.approval.draftApproved, ...data.approval?.draftApproved },
draftRejected: { ...DEFAULT_NOTIFICATION_SETTINGS.approval.draftRejected, ...data.approval?.draftRejected },
draftCompleted: { ...DEFAULT_NOTIFICATION_SETTINGS.approval.draftCompleted, ...data.approval?.draftCompleted },
},
production: {
enabled: data.production?.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.production.enabled,
safetyStock: { ...DEFAULT_NOTIFICATION_SETTINGS.production.safetyStock, ...data.production?.safetyStock },
productionComplete: { ...DEFAULT_NOTIFICATION_SETTINGS.production.productionComplete, ...data.production?.productionComplete },
},
};
}
// ===== Frontend → API 변환 =====
function transformFrontendToApi(settings: NotificationSettings): Record<string, unknown> {
// 프론트엔드 형식이 이미 API 형식과 동일하다고 가정
// 필요시 camelCase → snake_case 변환
return settings as Record<string, unknown>;
}