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>
This commit is contained in:
@@ -15,8 +15,7 @@
|
||||
* - GET /api/v1/orders/select - 수주 선택 목록 조회
|
||||
*/
|
||||
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
import type {
|
||||
ProductInspection,
|
||||
InspectionStats,
|
||||
@@ -307,81 +306,60 @@ export async function getInspections(params?: {
|
||||
}> {
|
||||
const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.size) searchParams.set('per_page', String(params.size));
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
if (params?.status && params.status !== '전체') {
|
||||
searchParams.set('status', mapFrontendStatus(params.status));
|
||||
}
|
||||
if (params?.dateFrom) searchParams.set('date_from', params.dateFrom);
|
||||
if (params?.dateTo) searchParams.set('date_to', params.dateTo);
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.size) searchParams.set('per_page', String(params.size));
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
if (params?.status && params.status !== '전체') {
|
||||
searchParams.set('status', mapFrontendStatus(params.status));
|
||||
}
|
||||
if (params?.dateFrom) searchParams.set('date_from', params.dateFrom);
|
||||
if (params?.dateTo) searchParams.set('date_to', params.dateTo);
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}${queryString ? `?${queryString}` : ''}`;
|
||||
const queryString = searchParams.toString();
|
||||
const result = await executeServerAction<PaginatedResponse>({
|
||||
url: `${API_BASE}${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '제품검사 목록 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[InspectionActions] API 실패, Mock 데이터 사용');
|
||||
let filtered = [...mockInspections];
|
||||
if (params?.status && params.status !== '전체') {
|
||||
filtered = filtered.filter(i => i.status === params.status);
|
||||
}
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.siteName.toLowerCase().includes(q) ||
|
||||
i.client.toLowerCase().includes(q) ||
|
||||
i.qualityDocNumber.toLowerCase().includes(q) ||
|
||||
i.inspector.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const page = params?.page || 1;
|
||||
const size = params?.size || 20;
|
||||
const start = (page - 1) * size;
|
||||
const paged = filtered.slice(start, start + size);
|
||||
return {
|
||||
success: true,
|
||||
data: paged,
|
||||
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
||||
};
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, data: [], pagination: defaultPagination, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], pagination: defaultPagination, error: result.message || '목록 조회 실패' };
|
||||
}
|
||||
|
||||
const paginatedData: PaginatedResponse = result.data || { items: [], current_page: 1, last_page: 1, per_page: 20, total: 0 };
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: paginatedData.items.map(transformApiToFrontend),
|
||||
pagination: {
|
||||
currentPage: paginatedData.current_page,
|
||||
lastPage: paginatedData.last_page,
|
||||
perPage: paginatedData.per_page,
|
||||
total: paginatedData.total,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspections error:', error);
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
let filtered = [...mockInspections];
|
||||
if (params?.status && params.status !== '전체') {
|
||||
filtered = filtered.filter(i => i.status === params.status);
|
||||
}
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.siteName.toLowerCase().includes(q) ||
|
||||
i.client.toLowerCase().includes(q) ||
|
||||
i.qualityDocNumber.toLowerCase().includes(q) ||
|
||||
i.inspector.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const page = params?.page || 1;
|
||||
const size = params?.size || 20;
|
||||
const start = (page - 1) * size;
|
||||
return {
|
||||
success: true,
|
||||
data: mockInspections,
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: mockInspections.length },
|
||||
data: filtered.slice(start, start + size),
|
||||
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
||||
};
|
||||
}
|
||||
return { success: false, data: [], pagination: defaultPagination, error: '서버 오류가 발생했습니다.' };
|
||||
return { success: false, data: [], pagination: defaultPagination, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
const d = result.data || { items: [], current_page: 1, last_page: 1, per_page: 20, total: 0 };
|
||||
return {
|
||||
success: true,
|
||||
data: d.items.map(transformApiToFrontend),
|
||||
pagination: {
|
||||
currentPage: d.current_page,
|
||||
lastPage: d.last_page,
|
||||
perPage: d.per_page,
|
||||
total: d.total,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 통계 조회 =====
|
||||
@@ -395,45 +373,30 @@ export async function getInspectionStats(params?: {
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.dateFrom) searchParams.set('date_from', params.dateFrom);
|
||||
if (params?.dateTo) searchParams.set('date_to', params.dateTo);
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.dateFrom) searchParams.set('date_from', params.dateFrom);
|
||||
if (params?.dateTo) searchParams.set('date_to', params.dateTo);
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}/stats${queryString ? `?${queryString}` : ''}`;
|
||||
const queryString = searchParams.toString();
|
||||
const result = await executeServerAction<InspectionStatsApi>({
|
||||
url: `${API_BASE}/stats${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '제품검사 통계 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[InspectionActions] Stats API 실패, Mock 데이터 사용');
|
||||
return { success: true, data: mockStats };
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.message || '통계 조회 실패' };
|
||||
}
|
||||
|
||||
const statsApi: InspectionStatsApi = result.data;
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
receptionCount: statsApi.reception_count,
|
||||
inProgressCount: statsApi.in_progress_count,
|
||||
completedCount: statsApi.completed_count,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspectionStats error:', error);
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true, data: mockStats };
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
return { success: false, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
const s = result.data;
|
||||
return {
|
||||
success: true,
|
||||
data: s ? {
|
||||
receptionCount: s.reception_count,
|
||||
inProgressCount: s.in_progress_count,
|
||||
completedCount: s.completed_count,
|
||||
} : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 캘린더 스케줄 조회 =====
|
||||
@@ -449,52 +412,37 @@ export async function getInspectionCalendar(params?: {
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.year) searchParams.set('year', String(params.year));
|
||||
if (params?.month) searchParams.set('month', String(params.month));
|
||||
if (params?.inspector) searchParams.set('inspector', params.inspector);
|
||||
if (params?.status && params.status !== '전체') {
|
||||
searchParams.set('status', mapFrontendStatus(params.status));
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}/calendar${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[InspectionActions] Calendar API 실패, Mock 데이터 사용');
|
||||
return { success: true, data: mockCalendarItems };
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, data: [], error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], error: result.message || '캘린더 조회 실패' };
|
||||
}
|
||||
|
||||
const items: CalendarItemApi[] = result.data || [];
|
||||
return {
|
||||
success: true,
|
||||
data: items.map((item) => ({
|
||||
id: String(item.id),
|
||||
startDate: item.start_date,
|
||||
endDate: item.end_date,
|
||||
inspector: item.inspector,
|
||||
siteName: item.site_name,
|
||||
status: mapApiStatus(item.status as ProductInspectionApi['status']),
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspectionCalendar error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true, data: mockCalendarItems };
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.year) searchParams.set('year', String(params.year));
|
||||
if (params?.month) searchParams.set('month', String(params.month));
|
||||
if (params?.inspector) searchParams.set('inspector', params.inspector);
|
||||
if (params?.status && params.status !== '전체') {
|
||||
searchParams.set('status', mapFrontendStatus(params.status));
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const result = await executeServerAction<CalendarItemApi[]>({
|
||||
url: `${API_BASE}/calendar${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '캘린더 스케줄 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true, data: mockCalendarItems };
|
||||
return { success: false, data: [], error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
const items = result.data || [];
|
||||
return {
|
||||
success: true,
|
||||
data: items.map((item) => ({
|
||||
id: String(item.id),
|
||||
startDate: item.start_date,
|
||||
endDate: item.end_date,
|
||||
inspector: item.inspector,
|
||||
siteName: item.site_name,
|
||||
status: mapApiStatus(item.status as ProductInspectionApi['status']),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 상세 조회 =====
|
||||
@@ -505,36 +453,23 @@ export async function getInspectionById(id: string): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const url = `${API_BASE}/${id}`;
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
const result = await executeServerAction<ProductInspectionApi>({
|
||||
url: `${API_BASE}/${id}`,
|
||||
errorMessage: '제품검사 상세 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[InspectionActions] Detail API 실패, Mock 데이터 사용');
|
||||
const mockItem = mockInspections.find(i => i.id === id);
|
||||
if (mockItem) return { success: true, data: mockItem };
|
||||
return { success: false, error: '해당 데이터를 찾을 수 없습니다.' };
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success || !result.data) {
|
||||
return { success: false, error: result.message || '상세 조회 실패' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspectionById error:', error);
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
const mockItem = mockInspections.find(i => i.id === id);
|
||||
if (mockItem) return { success: true, data: mockItem };
|
||||
return { success: false, error: '해당 데이터를 찾을 수 없습니다.' };
|
||||
}
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
return { success: false, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
return result.data
|
||||
? { success: true, data: transformApiToFrontend(result.data) }
|
||||
: { success: false, error: '상세 조회 실패' };
|
||||
}
|
||||
|
||||
// ===== 등록 =====
|
||||
@@ -545,32 +480,18 @@ export async function createInspection(data: InspectionFormData): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const apiData = transformFormToApi(data);
|
||||
const apiData = transformFormToApi(data);
|
||||
const result = await executeServerAction<ProductInspectionApi>({
|
||||
url: API_BASE,
|
||||
method: 'POST',
|
||||
body: apiData,
|
||||
errorMessage: '제품검사 등록에 실패했습니다.',
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(API_BASE, {
|
||||
method: 'POST',
|
||||
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, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] createInspection error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
if (!result.success) return { success: false, error: result.error, __authError: result.__authError };
|
||||
return result.data
|
||||
? { success: true, data: transformApiToFrontend(result.data) }
|
||||
: { success: true };
|
||||
}
|
||||
|
||||
// ===== 수정 =====
|
||||
@@ -584,93 +505,80 @@ export async function updateInspection(
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const apiData: Record<string, unknown> = {};
|
||||
const apiData: Record<string, unknown> = {};
|
||||
|
||||
if (data.qualityDocNumber !== undefined) apiData.quality_doc_number = data.qualityDocNumber;
|
||||
if (data.siteName !== undefined) apiData.site_name = data.siteName;
|
||||
if (data.client !== undefined) apiData.client = data.client;
|
||||
if (data.manager !== undefined) apiData.manager = data.manager;
|
||||
if (data.managerContact !== undefined) apiData.manager_contact = data.managerContact;
|
||||
if (data.qualityDocNumber !== undefined) apiData.quality_doc_number = data.qualityDocNumber;
|
||||
if (data.siteName !== undefined) apiData.site_name = data.siteName;
|
||||
if (data.client !== undefined) apiData.client = data.client;
|
||||
if (data.manager !== undefined) apiData.manager = data.manager;
|
||||
if (data.managerContact !== undefined) apiData.manager_contact = data.managerContact;
|
||||
|
||||
if (data.constructionSite) {
|
||||
apiData.construction_site = {
|
||||
site_name: data.constructionSite.siteName,
|
||||
land_location: data.constructionSite.landLocation,
|
||||
lot_number: data.constructionSite.lotNumber,
|
||||
};
|
||||
}
|
||||
if (data.materialDistributor) {
|
||||
apiData.material_distributor = {
|
||||
company_name: data.materialDistributor.companyName,
|
||||
company_address: data.materialDistributor.companyAddress,
|
||||
representative_name: data.materialDistributor.representativeName,
|
||||
phone: data.materialDistributor.phone,
|
||||
};
|
||||
}
|
||||
if (data.constructorInfo) {
|
||||
apiData.constructor_info = {
|
||||
company_name: data.constructorInfo.companyName,
|
||||
company_address: data.constructorInfo.companyAddress,
|
||||
name: data.constructorInfo.name,
|
||||
phone: data.constructorInfo.phone,
|
||||
};
|
||||
}
|
||||
if (data.supervisor) {
|
||||
apiData.supervisor = {
|
||||
office_name: data.supervisor.officeName,
|
||||
office_address: data.supervisor.officeAddress,
|
||||
name: data.supervisor.name,
|
||||
phone: data.supervisor.phone,
|
||||
};
|
||||
}
|
||||
if (data.scheduleInfo) {
|
||||
apiData.schedule_info = {
|
||||
visit_request_date: data.scheduleInfo.visitRequestDate,
|
||||
start_date: data.scheduleInfo.startDate,
|
||||
end_date: data.scheduleInfo.endDate,
|
||||
inspector: data.scheduleInfo.inspector,
|
||||
site_postal_code: data.scheduleInfo.sitePostalCode,
|
||||
site_address: data.scheduleInfo.siteAddress,
|
||||
site_address_detail: data.scheduleInfo.siteAddressDetail,
|
||||
};
|
||||
}
|
||||
if (data.orderItems) {
|
||||
apiData.order_items = data.orderItems.map((item) => ({
|
||||
order_number: item.orderNumber,
|
||||
floor: item.floor,
|
||||
symbol: item.symbol,
|
||||
order_width: item.orderWidth,
|
||||
order_height: item.orderHeight,
|
||||
construction_width: item.constructionWidth,
|
||||
construction_height: item.constructionHeight,
|
||||
change_reason: item.changeReason,
|
||||
}));
|
||||
}
|
||||
|
||||
const { response, error } = await serverFetch(`${API_BASE}/${id}`, {
|
||||
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, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] updateInspection error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
if (data.constructionSite) {
|
||||
apiData.construction_site = {
|
||||
site_name: data.constructionSite.siteName,
|
||||
land_location: data.constructionSite.landLocation,
|
||||
lot_number: data.constructionSite.lotNumber,
|
||||
};
|
||||
}
|
||||
if (data.materialDistributor) {
|
||||
apiData.material_distributor = {
|
||||
company_name: data.materialDistributor.companyName,
|
||||
company_address: data.materialDistributor.companyAddress,
|
||||
representative_name: data.materialDistributor.representativeName,
|
||||
phone: data.materialDistributor.phone,
|
||||
};
|
||||
}
|
||||
if (data.constructorInfo) {
|
||||
apiData.constructor_info = {
|
||||
company_name: data.constructorInfo.companyName,
|
||||
company_address: data.constructorInfo.companyAddress,
|
||||
name: data.constructorInfo.name,
|
||||
phone: data.constructorInfo.phone,
|
||||
};
|
||||
}
|
||||
if (data.supervisor) {
|
||||
apiData.supervisor = {
|
||||
office_name: data.supervisor.officeName,
|
||||
office_address: data.supervisor.officeAddress,
|
||||
name: data.supervisor.name,
|
||||
phone: data.supervisor.phone,
|
||||
};
|
||||
}
|
||||
if (data.scheduleInfo) {
|
||||
apiData.schedule_info = {
|
||||
visit_request_date: data.scheduleInfo.visitRequestDate,
|
||||
start_date: data.scheduleInfo.startDate,
|
||||
end_date: data.scheduleInfo.endDate,
|
||||
inspector: data.scheduleInfo.inspector,
|
||||
site_postal_code: data.scheduleInfo.sitePostalCode,
|
||||
site_address: data.scheduleInfo.siteAddress,
|
||||
site_address_detail: data.scheduleInfo.siteAddressDetail,
|
||||
};
|
||||
}
|
||||
if (data.orderItems) {
|
||||
apiData.order_items = data.orderItems.map((item) => ({
|
||||
order_number: item.orderNumber,
|
||||
floor: item.floor,
|
||||
symbol: item.symbol,
|
||||
order_width: item.orderWidth,
|
||||
order_height: item.orderHeight,
|
||||
construction_width: item.constructionWidth,
|
||||
construction_height: item.constructionHeight,
|
||||
change_reason: item.changeReason,
|
||||
}));
|
||||
}
|
||||
|
||||
const result = await executeServerAction<ProductInspectionApi>({
|
||||
url: `${API_BASE}/${id}`,
|
||||
method: 'PUT',
|
||||
body: apiData,
|
||||
errorMessage: '제품검사 수정에 실패했습니다.',
|
||||
});
|
||||
|
||||
if (!result.success) return { success: false, error: result.error, __authError: result.__authError };
|
||||
return result.data
|
||||
? { success: true, data: transformApiToFrontend(result.data) }
|
||||
: { success: true };
|
||||
}
|
||||
|
||||
// ===== 삭제 =====
|
||||
@@ -680,27 +588,13 @@ export async function deleteInspection(id: string): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE}/${id}`, { method: 'DELETE' });
|
||||
const result = await executeServerAction({
|
||||
url: `${API_BASE}/${id}`,
|
||||
method: 'DELETE',
|
||||
errorMessage: '제품검사 삭제에 실패했습니다.',
|
||||
});
|
||||
|
||||
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('[InspectionActions] deleteInspection error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
return { success: result.success, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
// ===== 검사 완료 처리 =====
|
||||
@@ -714,35 +608,22 @@ export async function completeInspection(
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const apiData: Record<string, unknown> = {};
|
||||
if (data?.result) {
|
||||
apiData.result = data.result === '합격' ? 'pass' : 'fail';
|
||||
}
|
||||
|
||||
const { response, error } = await serverFetch(`${API_BASE}/${id}/complete`, {
|
||||
method: 'PATCH',
|
||||
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, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] completeInspection error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
const apiData: Record<string, unknown> = {};
|
||||
if (data?.result) {
|
||||
apiData.result = data.result === '합격' ? 'pass' : 'fail';
|
||||
}
|
||||
|
||||
const result = await executeServerAction<ProductInspectionApi>({
|
||||
url: `${API_BASE}/${id}/complete`,
|
||||
method: 'PATCH',
|
||||
body: apiData,
|
||||
errorMessage: '검사 완료 처리에 실패했습니다.',
|
||||
});
|
||||
|
||||
if (!result.success) return { success: false, error: result.error, __authError: result.__authError };
|
||||
return result.data
|
||||
? { success: true, data: transformApiToFrontend(result.data) }
|
||||
: { success: true };
|
||||
}
|
||||
|
||||
// ===== 수주 선택 목록 조회 =====
|
||||
@@ -755,51 +636,38 @@ export async function getOrderSelectList(params?: {
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/select${queryString ? `?${queryString}` : ''}`;
|
||||
const queryString = searchParams.toString();
|
||||
const result = await executeServerAction<OrderSelectItemApi[]>({
|
||||
url: `${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/select${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '수주 선택 목록 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[InspectionActions] OrderSelect API 실패, Mock 데이터 사용');
|
||||
let filtered = [...mockOrderSelectItems];
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.orderNumber.toLowerCase().includes(q) || i.siteName.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
return { success: true, data: filtered };
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
let filtered = [...mockOrderSelectItems];
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.orderNumber.toLowerCase().includes(q) || i.siteName.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, data: [], error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
return { success: true, data: filtered };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], error: result.message || '수주 목록 조회 실패' };
|
||||
}
|
||||
|
||||
const items: OrderSelectItemApi[] = result.data || [];
|
||||
return {
|
||||
success: true,
|
||||
data: items.map((item) => ({
|
||||
id: String(item.id),
|
||||
orderNumber: item.order_number,
|
||||
siteName: item.site_name,
|
||||
deliveryDate: item.delivery_date,
|
||||
locationCount: item.location_count,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getOrderSelectList error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true, data: mockOrderSelectItems };
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
return { success: false, data: [], error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
const items = result.data || [];
|
||||
return {
|
||||
success: true,
|
||||
data: items.map((item) => ({
|
||||
id: String(item.id),
|
||||
orderNumber: item.order_number,
|
||||
siteName: item.site_name,
|
||||
deliveryDate: item.delivery_date,
|
||||
locationCount: item.location_count,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
* - PATCH /api/v1/performance-reports/memo - 메모 일괄 적용
|
||||
*/
|
||||
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
import type {
|
||||
PerformanceReport,
|
||||
PerformanceReportStats,
|
||||
@@ -58,80 +57,56 @@ export async function getPerformanceReports(params?: {
|
||||
}> {
|
||||
const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.size) searchParams.set('per_page', String(params.size));
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
if (params?.year) searchParams.set('year', String(params.year));
|
||||
if (params?.quarter && params.quarter !== '전체') {
|
||||
searchParams.set('quarter', params.quarter);
|
||||
}
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.size) searchParams.set('per_page', String(params.size));
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
if (params?.year) searchParams.set('year', String(params.year));
|
||||
if (params?.quarter && params.quarter !== '전체') {
|
||||
searchParams.set('quarter', params.quarter);
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}${queryString ? `?${queryString}` : ''}`;
|
||||
const queryString = searchParams.toString();
|
||||
interface ApiListData { items?: PerformanceReport[]; current_page?: number; last_page?: number; per_page?: number; total?: number }
|
||||
const result = await executeServerAction<ApiListData>({
|
||||
url: `${API_BASE}${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '실적신고 목록 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[PerformanceReportActions] API 실패, Mock 데이터 사용');
|
||||
let filtered = [...mockPerformanceReports];
|
||||
if (params?.year) {
|
||||
filtered = filtered.filter(i => i.year === params.year);
|
||||
}
|
||||
if (params?.quarter && params.quarter !== '전체') {
|
||||
filtered = filtered.filter(i => i.quarter === params.quarter);
|
||||
}
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.siteName.toLowerCase().includes(q) ||
|
||||
i.client.toLowerCase().includes(q) ||
|
||||
i.qualityDocNumber.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const page = params?.page || 1;
|
||||
const size = params?.size || 20;
|
||||
const start = (page - 1) * size;
|
||||
const paged = filtered.slice(start, start + size);
|
||||
return {
|
||||
success: true,
|
||||
data: paged,
|
||||
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
||||
};
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, data: [], pagination: defaultPagination, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], pagination: defaultPagination, error: result.message || '목록 조회 실패' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result.data?.items || [],
|
||||
pagination: {
|
||||
currentPage: result.data?.current_page || 1,
|
||||
lastPage: result.data?.last_page || 1,
|
||||
perPage: result.data?.per_page || 20,
|
||||
total: result.data?.total || 0,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] getPerformanceReports error:', error);
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
let filtered = [...mockPerformanceReports];
|
||||
if (params?.year) filtered = filtered.filter(i => i.year === params.year);
|
||||
if (params?.quarter && params.quarter !== '전체') filtered = filtered.filter(i => i.quarter === params.quarter);
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.siteName.toLowerCase().includes(q) || i.client.toLowerCase().includes(q) || i.qualityDocNumber.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const page = params?.page || 1;
|
||||
const size = params?.size || 20;
|
||||
const start = (page - 1) * size;
|
||||
return {
|
||||
success: true,
|
||||
data: mockPerformanceReports,
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: mockPerformanceReports.length },
|
||||
data: filtered.slice(start, start + size),
|
||||
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
||||
};
|
||||
}
|
||||
return { success: false, data: [], pagination: defaultPagination, error: '서버 오류가 발생했습니다.' };
|
||||
return { success: false, data: [], pagination: defaultPagination, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
const d = result.data;
|
||||
return {
|
||||
success: true,
|
||||
data: d?.items || [],
|
||||
pagination: {
|
||||
currentPage: d?.current_page || 1,
|
||||
lastPage: d?.last_page || 1,
|
||||
perPage: d?.per_page || 20,
|
||||
total: d?.total || 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 통계 조회 =====
|
||||
@@ -145,39 +120,23 @@ export async function getPerformanceReportStats(params?: {
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.year) searchParams.set('year', String(params.year));
|
||||
if (params?.quarter && params.quarter !== '전체') {
|
||||
searchParams.set('quarter', params.quarter);
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}/stats${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[PerformanceReportActions] Stats API 실패, Mock 데이터 사용');
|
||||
return { success: true, data: mockPerformanceReportStats };
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.message || '통계 조회 실패' };
|
||||
}
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] getPerformanceReportStats error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true, data: mockPerformanceReportStats };
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.year) searchParams.set('year', String(params.year));
|
||||
if (params?.quarter && params.quarter !== '전체') {
|
||||
searchParams.set('quarter', params.quarter);
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const result = await executeServerAction<PerformanceReportStats>({
|
||||
url: `${API_BASE}/stats${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '실적신고 통계 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true, data: mockPerformanceReportStats };
|
||||
return { success: false, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
return { success: true, data: result.data };
|
||||
}
|
||||
|
||||
// ===== 누락체크 목록 조회 =====
|
||||
@@ -195,70 +154,50 @@ export async function getMissedReports(params?: {
|
||||
}> {
|
||||
const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.size) searchParams.set('per_page', String(params.size));
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.size) searchParams.set('per_page', String(params.size));
|
||||
if (params?.q) searchParams.set('q', params.q);
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}/missed${queryString ? `?${queryString}` : ''}`;
|
||||
const queryString = searchParams.toString();
|
||||
interface ApiMissedData { items?: MissedReport[]; current_page?: number; last_page?: number; per_page?: number; total?: number }
|
||||
const result = await executeServerAction<ApiMissedData>({
|
||||
url: `${API_BASE}/missed${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '누락체크 목록 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
if (error || !response || !response.ok) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
console.warn('[PerformanceReportActions] Missed API 실패, Mock 데이터 사용');
|
||||
let filtered = [...mockMissedReports];
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.siteName.toLowerCase().includes(q) ||
|
||||
i.client.toLowerCase().includes(q) ||
|
||||
i.qualityDocNumber.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const page = params?.page || 1;
|
||||
const size = params?.size || 20;
|
||||
const start = (page - 1) * size;
|
||||
const paged = filtered.slice(start, start + size);
|
||||
return {
|
||||
success: true,
|
||||
data: paged,
|
||||
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
||||
};
|
||||
}
|
||||
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
||||
return { success: false, data: [], pagination: defaultPagination, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], pagination: defaultPagination, error: result.message || '누락체크 조회 실패' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result.data?.items || [],
|
||||
pagination: {
|
||||
currentPage: result.data?.current_page || 1,
|
||||
lastPage: result.data?.last_page || 1,
|
||||
perPage: result.data?.per_page || 20,
|
||||
total: result.data?.total || 0,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] getMissedReports error:', error);
|
||||
if (!result.success) {
|
||||
if (USE_MOCK_FALLBACK) {
|
||||
let filtered = [...mockMissedReports];
|
||||
if (params?.q) {
|
||||
const q = params.q.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.siteName.toLowerCase().includes(q) || i.client.toLowerCase().includes(q) || i.qualityDocNumber.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
const page = params?.page || 1;
|
||||
const size = params?.size || 20;
|
||||
const start = (page - 1) * size;
|
||||
return {
|
||||
success: true,
|
||||
data: mockMissedReports,
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: mockMissedReports.length },
|
||||
data: filtered.slice(start, start + size),
|
||||
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
||||
};
|
||||
}
|
||||
return { success: false, data: [], pagination: defaultPagination, error: '서버 오류가 발생했습니다.' };
|
||||
return { success: false, data: [], pagination: defaultPagination, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
const d = result.data;
|
||||
return {
|
||||
success: true,
|
||||
data: d?.items || [],
|
||||
pagination: {
|
||||
currentPage: d?.current_page || 1,
|
||||
lastPage: d?.last_page || 1,
|
||||
perPage: d?.per_page || 20,
|
||||
total: d?.total || 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 선택 확정 =====
|
||||
@@ -268,34 +207,14 @@ export async function confirmReports(ids: string[]): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE}/confirm`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
if (!response) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '확정 처리에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: result.message || '확정 처리에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] confirmReports error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_BASE}/confirm`,
|
||||
method: 'PATCH',
|
||||
body: { ids },
|
||||
errorMessage: '확정 처리에 실패했습니다.',
|
||||
});
|
||||
if (!result.success && USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: result.success, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
// ===== 확정 해제 =====
|
||||
@@ -305,34 +224,14 @@ export async function unconfirmReports(ids: string[]): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE}/unconfirm`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
if (!response) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '확정 해제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: result.message || '확정 해제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] unconfirmReports error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_BASE}/unconfirm`,
|
||||
method: 'PATCH',
|
||||
body: { ids },
|
||||
errorMessage: '확정 해제에 실패했습니다.',
|
||||
});
|
||||
if (!result.success && USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: result.success, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
// ===== 배포 =====
|
||||
@@ -342,34 +241,14 @@ export async function distributeReports(ids: string[]): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE}/distribute`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ids }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
if (!response) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '배포에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: result.message || '배포에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] distributeReports error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_BASE}/distribute`,
|
||||
method: 'POST',
|
||||
body: { ids },
|
||||
errorMessage: '배포에 실패했습니다.',
|
||||
});
|
||||
if (!result.success && USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: result.success, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
// ===== 메모 일괄 적용 =====
|
||||
@@ -379,32 +258,12 @@ export async function updateMemo(ids: string[], memo: string): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(`${API_BASE}/memo`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids, memo }),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
if (!response) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '메모 저장에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: result.message || '메모 저장에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PerformanceReportActions] updateMemo error:', error);
|
||||
if (USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_BASE}/memo`,
|
||||
method: 'PATCH',
|
||||
body: { ids, memo },
|
||||
errorMessage: '메모 저장에 실패했습니다.',
|
||||
});
|
||||
if (!result.success && USE_MOCK_FALLBACK) return { success: true };
|
||||
return { success: result.success, error: result.error, __authError: result.__authError };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user