- 전체 모듈 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>
170 lines
6.2 KiB
TypeScript
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>;
|
|
} |