Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { BadDebtRecord, CollectionStatus } from './types';
|
||||
@@ -288,7 +288,7 @@ export async function getBadDebts(params?: {
|
||||
|
||||
return result.data.data.map(transformApiToFrontend);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] getBadDebts error:', error);
|
||||
return [];
|
||||
}
|
||||
@@ -322,7 +322,7 @@ export async function getBadDebtById(id: string): Promise<BadDebtRecord | null>
|
||||
|
||||
return transformApiToFrontend(result.data);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] getBadDebtById error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -356,7 +356,7 @@ export async function getBadDebtSummary(): Promise<BadDebtSummaryApiData | null>
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] getBadDebtSummary error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -401,7 +401,7 @@ export async function createBadDebt(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] createBadDebt error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -450,7 +450,7 @@ export async function updateBadDebt(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] updateBadDebt error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -486,7 +486,7 @@ export async function deleteBadDebt(id: string): Promise<{ success: boolean; err
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] deleteBadDebt error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -525,7 +525,7 @@ export async function toggleBadDebt(id: string): Promise<{ success: boolean; dat
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] toggleBadDebt error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -574,7 +574,7 @@ export async function addBadDebtMemo(
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] addBadDebtMemo error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -611,7 +611,7 @@ export async function deleteBadDebtMemo(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BadDebtActions] deleteBadDebtMemo error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { BankTransaction, TransactionKind } from './types';
|
||||
|
||||
@@ -157,7 +157,7 @@ export async function getBankTransactionList(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BankTransactionActions] getBankTransactionList error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -226,7 +226,7 @@ export async function getBankTransactionSummary(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BankTransactionActions] getBankTransactionSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -274,7 +274,7 @@ export async function getBankAccountOptions(): Promise<{
|
||||
data: result.data as BankAccountOption[],
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BankTransactionActions] getBankAccountOptions error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { BillRecord, BillApiData, BillStatus } from './types';
|
||||
import { transformApiToFrontend, transformFrontendToApi } from './types';
|
||||
@@ -105,7 +105,7 @@ export async function getBills(params: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getBills] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -148,7 +148,7 @@ export async function getBill(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data as BillApiData),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getBill] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
@@ -195,7 +195,7 @@ export async function createBill(
|
||||
data: transformApiToFrontend(result.data as BillApiData),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createBill] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
@@ -243,7 +243,7 @@ export async function updateBill(
|
||||
data: transformApiToFrontend(result.data as BillApiData),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateBill] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
@@ -273,7 +273,7 @@ export async function deleteBill(id: string): Promise<{ success: boolean; error?
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteBill] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
@@ -312,7 +312,7 @@ export async function updateBillStatus(
|
||||
data: transformApiToFrontend(result.data as BillApiData),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateBillStatus] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
@@ -376,7 +376,7 @@ export async function getBillSummary(params: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getBillSummary] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
@@ -419,7 +419,7 @@ export async function getClients(): Promise<{
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getClients] Error:', error);
|
||||
return { success: false, error: 'Server error' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { CardTransaction } from './types';
|
||||
|
||||
@@ -160,7 +160,7 @@ export async function getCardTransactionList(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CardTransactionActions] getCardTransactionList error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -229,7 +229,7 @@ export async function getCardTransactionSummary(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CardTransactionActions] getCardTransactionSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -281,7 +281,7 @@ export async function bulkUpdateAccountCode(
|
||||
updatedCount: result.data?.updated_count || 0,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CardTransactionActions] bulkUpdateAccountCode error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { NoteReceivableItem, DailyAccountItem, MatchStatus } from './types';
|
||||
@@ -118,7 +118,7 @@ export async function getNoteReceivables(params?: {
|
||||
data: items,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DailyReportActions] getNoteReceivables error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -176,7 +176,7 @@ export async function getDailyAccounts(params?: {
|
||||
data: items,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DailyReportActions] getDailyAccounts error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -258,7 +258,7 @@ export async function getDailyReportSummary(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DailyReportActions] getDailyReportSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -315,7 +315,7 @@ export async function exportDailyReportExcel(params?: {
|
||||
filename,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DailyReportActions] exportDailyReportExcel error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { DepositRecord, DepositType, DepositStatus } from './types';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { ExpectedExpenseRecord, TransactionType, PaymentStatus, ApprovalStatus } from './types';
|
||||
|
||||
@@ -190,7 +190,7 @@ export async function getExpectedExpenses(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] getExpectedExpenses error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -239,7 +239,7 @@ export async function getExpectedExpenseById(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] getExpectedExpenseById error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -281,7 +281,7 @@ export async function createExpectedExpense(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] createExpectedExpense error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -324,7 +324,7 @@ export async function updateExpectedExpense(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] updateExpectedExpense error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -356,7 +356,7 @@ export async function deleteExpectedExpense(id: string): Promise<{ success: bool
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] deleteExpectedExpense error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -400,7 +400,7 @@ export async function deleteExpectedExpenses(ids: string[]): Promise<{
|
||||
deletedCount: result.data?.deleted_count,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] deleteExpectedExpenses error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -448,7 +448,7 @@ export async function updateExpectedPaymentDate(
|
||||
updatedCount: result.data?.updated_count,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] updateExpectedPaymentDate error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -507,7 +507,7 @@ export async function getExpectedExpenseSummary(params?: {
|
||||
data: result.data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] getExpectedExpenseSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -552,7 +552,7 @@ export async function getClients(): Promise<{
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] getClients error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -596,7 +596,7 @@ export async function getBankAccounts(): Promise<{
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ExpectedExpenseActions] getBankAccounts error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { PurchaseRecord, PurchaseType } from './types';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { VendorReceivables, CategoryType, MonthlyAmount, ReceivablesListResponse, MemoUpdateRequest } from './types';
|
||||
@@ -128,7 +128,7 @@ export async function getReceivablesList(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivablesActions] getReceivablesList error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -209,7 +209,7 @@ export async function getReceivablesSummary(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivablesActions] getReceivablesSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -265,7 +265,7 @@ export async function updateOverdueStatus(
|
||||
updatedCount: result.data?.updated_count || updates.length,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivablesActions] updateOverdueStatus error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -321,7 +321,7 @@ export async function updateMemos(
|
||||
updatedCount: result.data?.updated_count || memos.length,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivablesActions] updateMemos error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -388,7 +388,7 @@ export async function exportReceivablesExcel(params?: {
|
||||
filename,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivablesActions] exportReceivablesExcel error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
SalesRecord,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { VendorLedgerItem, VendorLedgerDetail, VendorLedgerSummary, TransactionEntry } from './types';
|
||||
@@ -226,7 +226,7 @@ export async function getVendorLedgerList(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VendorLedgerActions] getVendorLedgerList error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -290,7 +290,7 @@ export async function getVendorLedgerSummary(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VendorLedgerActions] getVendorLedgerSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -354,7 +354,7 @@ export async function getVendorLedgerDetail(clientId: string, params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VendorLedgerActions] getVendorLedgerDetail error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -415,7 +415,7 @@ export async function exportVendorLedgerExcel(params?: {
|
||||
filename,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VendorLedgerActions] exportVendorLedgerExcel error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -474,7 +474,7 @@ export async function exportVendorLedgerDetailPdf(clientId: string, params?: {
|
||||
filename,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VendorLedgerActions] exportVendorLedgerDetailPdf error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
Vendor,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { WithdrawalRecord, WithdrawalType } from './types';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { ApprovalRecord, ApprovalType, ApprovalStatus } from './types';
|
||||
|
||||
@@ -224,7 +224,7 @@ export async function getInbox(params?: {
|
||||
lastPage: result.data.last_page,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ApprovalBoxActions] getInbox error:', error);
|
||||
return { data: [], total: 0, lastPage: 1 };
|
||||
}
|
||||
@@ -253,7 +253,7 @@ export async function getInboxSummary(): Promise<InboxSummary | null> {
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ApprovalBoxActions] getInboxSummary error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -291,7 +291,7 @@ export async function approveDocument(id: string, comment?: string): Promise<{ s
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ApprovalBoxActions] approveDocument error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -339,7 +339,7 @@ export async function rejectDocument(id: string, comment: string): Promise<{ suc
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ApprovalBoxActions] rejectDocument error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
@@ -185,7 +185,7 @@ export async function uploadFiles(files: File[]): Promise<{
|
||||
|
||||
return { success: true, data: uploadedFiles };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] uploadFiles error:', error);
|
||||
return { success: false, error: '파일 업로드 중 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -237,7 +237,7 @@ export async function getExpenseEstimateItems(yearMonth?: string): Promise<{
|
||||
finalDifference: result.data.final_difference,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] getExpenseEstimateItems error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -278,7 +278,7 @@ export async function getEmployees(search?: string): Promise<ApprovalPerson[]> {
|
||||
|
||||
return result.data.data.map(transformEmployee);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] getEmployees error:', error);
|
||||
return [];
|
||||
}
|
||||
@@ -359,7 +359,7 @@ export async function createApproval(formData: DocumentFormData): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] createApproval error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -402,7 +402,7 @@ export async function submitApproval(id: number): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] submitApproval error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -440,7 +440,7 @@ export async function createAndSubmitApproval(formData: DocumentFormData): Promi
|
||||
data: createResult.data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] createAndSubmitApproval error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -488,7 +488,7 @@ export async function getApprovalById(id: number): Promise<{
|
||||
|
||||
return { success: true, data: formDataResult };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] getApprovalById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -567,7 +567,7 @@ export async function updateApproval(id: number, formData: DocumentFormData): Pr
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] updateApproval error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -602,7 +602,7 @@ export async function updateAndSubmitApproval(id: number, formData: DocumentForm
|
||||
data: updateResult.data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] updateAndSubmitApproval error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -641,7 +641,7 @@ export async function deleteApproval(id: number): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DocumentCreateActions] deleteApproval error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { DraftRecord, DocumentStatus, Approver } from './types';
|
||||
|
||||
@@ -227,7 +227,7 @@ export async function getDrafts(params?: {
|
||||
lastPage: result.data.last_page,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DraftBoxActions] getDrafts error:', error);
|
||||
return { data: [], total: 0, lastPage: 1 };
|
||||
}
|
||||
@@ -256,7 +256,7 @@ export async function getDraftsSummary(): Promise<DraftsSummary | null> {
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DraftBoxActions] getDraftsSummary error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ export async function getDraftById(id: string): Promise<DraftRecord | null> {
|
||||
|
||||
return transformApiToFrontend(result.data);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DraftBoxActions] getDraftById error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -320,7 +320,7 @@ export async function deleteDraft(id: string): Promise<{ success: boolean; error
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DraftBoxActions] deleteDraft error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -385,7 +385,7 @@ export async function submitDraft(id: string): Promise<{ success: boolean; error
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DraftBoxActions] submitDraft error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -450,7 +450,7 @@ export async function cancelDraft(id: string): Promise<{ success: boolean; error
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DraftBoxActions] cancelDraft error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { ReferenceRecord, ApprovalType, DocumentStatus } from './types';
|
||||
|
||||
@@ -187,7 +187,7 @@ export async function getReferences(params?: {
|
||||
lastPage: result.data.last_page,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReferenceBoxActions] getReferences error:', error);
|
||||
return { data: [], total: 0, lastPage: 1 };
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export async function getReferenceSummary(): Promise<{ all: number; read: number
|
||||
unread: unreadResult.total,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReferenceBoxActions] getReferenceSummary error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -246,7 +246,7 @@ export async function markAsRead(id: string): Promise<{ success: boolean; error?
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReferenceBoxActions] markAsRead error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -286,7 +286,7 @@ export async function markAsUnread(id: string): Promise<{ success: boolean; erro
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReferenceBoxActions] markAsUnread error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch, getServerApiHeaders } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
// ============================================
|
||||
@@ -143,7 +143,7 @@ export async function checkIn(
|
||||
error: result.message || '출근 기록에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[checkIn] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -197,7 +197,7 @@ export async function checkOut(
|
||||
error: result.message || '퇴근 기록에 실패했습니다.',
|
||||
};
|
||||
} catch (err) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[checkOut] Error:', err);
|
||||
return {
|
||||
success: false,
|
||||
@@ -253,7 +253,7 @@ export async function getTodayAttendance(): Promise<{
|
||||
error: result.message || '근태 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getTodayAttendance] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Board, BoardApiData, BoardFormData } from './types';
|
||||
|
||||
@@ -135,7 +135,7 @@ export async function getBoards(filters?: {
|
||||
const boards = result.data.map(transformApiToFrontend);
|
||||
return { success: true, data: boards };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getBoards error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -184,7 +184,7 @@ export async function getTenantBoards(filters?: {
|
||||
const boards = result.data.map(transformApiToFrontend);
|
||||
return { success: true, data: boards };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getTenantBoards error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -224,7 +224,7 @@ export async function getBoardByCode(code: string): Promise<{ success: boolean;
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getBoardByCode error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -264,7 +264,7 @@ export async function getBoardById(id: string): Promise<{ success: boolean; data
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getBoardById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -313,7 +313,7 @@ export async function createBoard(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] createBoard error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -363,7 +363,7 @@ export async function updateBoard(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] updateBoard error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -405,7 +405,7 @@ export async function deleteBoard(id: string): Promise<{ success: boolean; error
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] deleteBoard error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -433,7 +433,7 @@ export async function deleteBoardsBulk(ids: string[]): Promise<{ success: boolea
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] deleteBoardsBulk error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
PostApiData,
|
||||
@@ -62,7 +62,7 @@ export async function getDynamicBoardPosts(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] getDynamicBoardPosts error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export async function getDynamicBoardPost(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] getDynamicBoardPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -142,7 +142,7 @@ export async function createDynamicBoardPost(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] createDynamicBoardPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export async function updateDynamicBoardPost(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] updateDynamicBoardPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -222,7 +222,7 @@ export async function deleteDynamicBoardPost(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] deleteDynamicBoardPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -261,7 +261,7 @@ export async function getDynamicBoardComments(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] getDynamicBoardComments error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -299,7 +299,7 @@ export async function createDynamicBoardComment(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] createDynamicBoardComment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -338,7 +338,7 @@ export async function updateDynamicBoardComment(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] updateDynamicBoardComment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -375,7 +375,7 @@ export async function deleteDynamicBoardComment(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DynamicBoardActions] deleteDynamicBoardComment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
PostApiData,
|
||||
@@ -103,7 +103,7 @@ export async function getPosts(
|
||||
|
||||
return { success: true, data: result.data, posts };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getPosts error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export async function getMyPosts(
|
||||
|
||||
return { success: true, data: result.data, posts };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getMyPosts error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -193,7 +193,7 @@ export async function getPost(
|
||||
|
||||
return { success: true, data: transformApiToPost(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] getPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -236,7 +236,7 @@ export async function createPost(
|
||||
|
||||
return { success: true, data: transformApiToPost(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] createPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -280,7 +280,7 @@ export async function updatePost(
|
||||
|
||||
return { success: true, data: transformApiToPost(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] updatePost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -316,7 +316,7 @@ export async function deletePost(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[BoardActions] deletePost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Loader2, LayoutDashboard, Settings } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
@@ -17,9 +18,9 @@ import {
|
||||
VatSection,
|
||||
CalendarSection,
|
||||
} from './sections';
|
||||
import type { CEODashboardData, CalendarScheduleItem, DashboardSettings } from './types';
|
||||
import type { CEODashboardData, CalendarScheduleItem, DashboardSettings, DetailModalConfig } from './types';
|
||||
import { DEFAULT_DASHBOARD_SETTINGS } from './types';
|
||||
import { ScheduleDetailModal } from './modals';
|
||||
import { ScheduleDetailModal, DetailModal } from './modals';
|
||||
import { DashboardSettingsDialog } from './dialogs/DashboardSettingsDialog';
|
||||
|
||||
// 목데이터
|
||||
@@ -368,6 +369,7 @@ const mockData: CEODashboardData = {
|
||||
};
|
||||
|
||||
export function CEODashboard() {
|
||||
const router = useRouter();
|
||||
const [isLoading] = useState(false);
|
||||
const [data] = useState<CEODashboardData>(mockData);
|
||||
|
||||
@@ -377,20 +379,23 @@ export function CEODashboard() {
|
||||
|
||||
// 항목 설정 모달 상태
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
const [dashboardSettings, setDashboardSettings] = useState<DashboardSettings>(() => {
|
||||
// localStorage에서 설정 불러오기
|
||||
if (typeof window !== 'undefined') {
|
||||
const saved = localStorage.getItem('ceo-dashboard-settings');
|
||||
if (saved) {
|
||||
try {
|
||||
return JSON.parse(saved);
|
||||
} catch {
|
||||
return DEFAULT_DASHBOARD_SETTINGS;
|
||||
}
|
||||
const [dashboardSettings, setDashboardSettings] = useState<DashboardSettings>(DEFAULT_DASHBOARD_SETTINGS);
|
||||
|
||||
// 상세 모달 상태
|
||||
const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
|
||||
const [detailModalConfig, setDetailModalConfig] = useState<DetailModalConfig | null>(null);
|
||||
|
||||
// 클라이언트에서만 localStorage에서 설정 불러오기 (hydration 에러 방지)
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('ceo-dashboard-settings');
|
||||
if (saved) {
|
||||
try {
|
||||
setDashboardSettings(JSON.parse(saved));
|
||||
} catch {
|
||||
// 파싱 실패 시 기본값 유지
|
||||
}
|
||||
}
|
||||
return DEFAULT_DASHBOARD_SETTINGS;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 항목 설정 클릭
|
||||
const handleSettingClick = useCallback(() => {
|
||||
@@ -411,15 +416,273 @@ export function CEODashboard() {
|
||||
setIsSettingsModalOpen(false);
|
||||
}, []);
|
||||
|
||||
// 일일 일보 클릭
|
||||
// 일일 일보 클릭 → 일일 일보 페이지로 이동
|
||||
const handleDailyReportClick = useCallback(() => {
|
||||
// TODO: 일일 일보 상세 팝업 열기
|
||||
console.log('일일 일보 클릭');
|
||||
router.push('/ko/accounting/daily-report');
|
||||
}, [router]);
|
||||
|
||||
// 상세 모달 닫기
|
||||
const handleDetailModalClose = useCallback(() => {
|
||||
setIsDetailModalOpen(false);
|
||||
setDetailModalConfig(null);
|
||||
}, []);
|
||||
|
||||
// 당월 예상 지출 클릭
|
||||
// 당월 예상 지출 카드 클릭 (개별 카드 클릭 시 상세 모달)
|
||||
const handleMonthlyExpenseCardClick = useCallback((cardId: string) => {
|
||||
// 카드 ID에 따라 다른 상세 데이터 설정
|
||||
const cardConfigs: Record<string, DetailModalConfig> = {
|
||||
me1: {
|
||||
title: '당월 매입 상세',
|
||||
summaryCards: [
|
||||
{ label: '당월 매입', value: 3123000, unit: '원' },
|
||||
{ label: '전월 대비', value: '-12.5%', isComparison: true, isPositive: false },
|
||||
],
|
||||
barChart: {
|
||||
title: '월별 매입 추이',
|
||||
data: [
|
||||
{ name: '1월', value: 45000000 },
|
||||
{ name: '2월', value: 52000000 },
|
||||
{ name: '3월', value: 48000000 },
|
||||
{ name: '4월', value: 61000000 },
|
||||
{ name: '5월', value: 55000000 },
|
||||
{ name: '6월', value: 58000000 },
|
||||
{ name: '7월', value: 50000000 },
|
||||
],
|
||||
dataKey: 'value',
|
||||
xAxisKey: 'name',
|
||||
color: '#60A5FA',
|
||||
},
|
||||
pieChart: {
|
||||
title: '자재 유형별 구매 비율',
|
||||
data: [
|
||||
{ name: '원자재', value: 55000000, percentage: 55, color: '#60A5FA' },
|
||||
{ name: '부자재', value: 35000000, percentage: 35, color: '#34D399' },
|
||||
{ name: '포장재', value: 10000000, percentage: 10, color: '#FBBF24' },
|
||||
],
|
||||
},
|
||||
table: {
|
||||
title: '일별 매입 내역',
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', align: 'center' },
|
||||
{ key: 'date', label: '매입일', align: 'center', format: 'date' },
|
||||
{ key: 'vendor', label: '거래처', align: 'left' },
|
||||
{ key: 'amount', label: '매입금액', align: 'right', format: 'currency' },
|
||||
{ key: 'type', label: '매입유형', align: 'center' },
|
||||
],
|
||||
data: [
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '원재료매입' },
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '부재료매입' },
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '미설정' },
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '원재료매입' },
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '부재료매입' },
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '미설정' },
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 11000000, type: '원재료매입' },
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'type',
|
||||
options: [
|
||||
{ value: 'all', label: '전체' },
|
||||
{ value: '원재료매입', label: '원재료매입' },
|
||||
{ value: '부재료매입', label: '부재료매입' },
|
||||
{ value: '미설정', label: '미설정' },
|
||||
],
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
key: 'sortOrder',
|
||||
options: [
|
||||
{ value: 'latest', label: '최신순' },
|
||||
{ value: 'oldest', label: '오래된순' },
|
||||
],
|
||||
defaultValue: 'latest',
|
||||
},
|
||||
],
|
||||
showTotal: true,
|
||||
totalLabel: '합계',
|
||||
totalValue: 111000000,
|
||||
totalColumnKey: 'amount',
|
||||
},
|
||||
},
|
||||
me2: {
|
||||
title: '당월 카드 상세',
|
||||
summaryCards: [
|
||||
{ label: '당월 카드', value: 30123000, unit: '원' },
|
||||
{ label: '전월 대비', value: '+10.5%', isComparison: true, isPositive: true },
|
||||
],
|
||||
barChart: {
|
||||
title: '월별 카드 사용 추이',
|
||||
data: [
|
||||
{ name: '1월', value: 25000000 },
|
||||
{ name: '2월', value: 28000000 },
|
||||
{ name: '3월', value: 22000000 },
|
||||
{ name: '4월', value: 30000000 },
|
||||
{ name: '5월', value: 27000000 },
|
||||
{ name: '6월', value: 29000000 },
|
||||
{ name: '7월', value: 30000000 },
|
||||
],
|
||||
dataKey: 'value',
|
||||
xAxisKey: 'name',
|
||||
color: '#34D399',
|
||||
},
|
||||
pieChart: {
|
||||
title: '카드 사용 유형별 비율',
|
||||
data: [
|
||||
{ name: '접대비', value: 12000000, percentage: 40, color: '#60A5FA' },
|
||||
{ name: '복리후생비', value: 9000000, percentage: 30, color: '#34D399' },
|
||||
{ name: '소모품비', value: 9123000, percentage: 30, color: '#FBBF24' },
|
||||
],
|
||||
},
|
||||
table: {
|
||||
title: '일별 카드 사용 내역',
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', align: 'center' },
|
||||
{ key: 'date', label: '사용일', align: 'center', format: 'date' },
|
||||
{ key: 'store', label: '가맹점', align: 'left' },
|
||||
{ key: 'amount', label: '사용금액', align: 'right', format: 'currency' },
|
||||
{ key: 'category', label: '분류', align: 'center' },
|
||||
],
|
||||
data: [
|
||||
{ date: '2025-12-12', store: '가맹점명', amount: 5000000, category: '접대비' },
|
||||
{ date: '2025-12-11', store: '가맹점명', amount: 3000000, category: '복리후생비' },
|
||||
{ date: '2025-12-10', store: '가맹점명', amount: 4000000, category: '소모품비' },
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'category',
|
||||
options: [
|
||||
{ value: 'all', label: '전체' },
|
||||
{ value: '접대비', label: '접대비' },
|
||||
{ value: '복리후생비', label: '복리후생비' },
|
||||
{ value: '소모품비', label: '소모품비' },
|
||||
],
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
key: 'sortOrder',
|
||||
options: [
|
||||
{ value: 'latest', label: '최신순' },
|
||||
{ value: 'oldest', label: '오래된순' },
|
||||
],
|
||||
defaultValue: 'latest',
|
||||
},
|
||||
],
|
||||
showTotal: true,
|
||||
totalLabel: '합계',
|
||||
totalValue: 30123000,
|
||||
totalColumnKey: 'amount',
|
||||
},
|
||||
},
|
||||
me3: {
|
||||
title: '당월 발행어음 상세',
|
||||
summaryCards: [
|
||||
{ label: '당월 발행어음', value: 30123000, unit: '원' },
|
||||
{ label: '전월 대비', value: '+10.5%', isComparison: true, isPositive: true },
|
||||
],
|
||||
barChart: {
|
||||
title: '월별 발행어음 추이',
|
||||
data: [
|
||||
{ name: '1월', value: 20000000 },
|
||||
{ name: '2월', value: 25000000 },
|
||||
{ name: '3월', value: 22000000 },
|
||||
{ name: '4월', value: 28000000 },
|
||||
{ name: '5월', value: 26000000 },
|
||||
{ name: '6월', value: 30000000 },
|
||||
{ name: '7월', value: 30000000 },
|
||||
],
|
||||
dataKey: 'value',
|
||||
xAxisKey: 'name',
|
||||
color: '#F472B6',
|
||||
},
|
||||
table: {
|
||||
title: '발행어음 내역',
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', align: 'center' },
|
||||
{ key: 'date', label: '발행일', align: 'center', format: 'date' },
|
||||
{ key: 'vendor', label: '수취인', align: 'left' },
|
||||
{ key: 'amount', label: '금액', align: 'right', format: 'currency' },
|
||||
{ key: 'dueDate', label: '만기일', align: 'center', format: 'date' },
|
||||
],
|
||||
data: [
|
||||
{ date: '2025-12-12', vendor: '회사명', amount: 10000000, dueDate: '2026-03-12' },
|
||||
{ date: '2025-12-10', vendor: '회사명', amount: 10123000, dueDate: '2026-03-10' },
|
||||
{ date: '2025-12-08', vendor: '회사명', amount: 10000000, dueDate: '2026-03-08' },
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'sortOrder',
|
||||
options: [
|
||||
{ value: 'latest', label: '최신순' },
|
||||
{ value: 'oldest', label: '오래된순' },
|
||||
],
|
||||
defaultValue: 'latest',
|
||||
},
|
||||
],
|
||||
showTotal: true,
|
||||
totalLabel: '합계',
|
||||
totalValue: 30123000,
|
||||
totalColumnKey: 'amount',
|
||||
},
|
||||
},
|
||||
me4: {
|
||||
title: '총 예상 지출 상세',
|
||||
summaryCards: [
|
||||
{ label: '총 예상 지출', value: 350000000, unit: '원' },
|
||||
{ label: '전월 대비', value: '+10.5%', isComparison: true, isPositive: true },
|
||||
],
|
||||
barChart: {
|
||||
title: '월별 총 지출 추이',
|
||||
data: [
|
||||
{ name: '1월', value: 280000000 },
|
||||
{ name: '2월', value: 300000000 },
|
||||
{ name: '3월', value: 290000000 },
|
||||
{ name: '4월', value: 320000000 },
|
||||
{ name: '5월', value: 310000000 },
|
||||
{ name: '6월', value: 340000000 },
|
||||
{ name: '7월', value: 350000000 },
|
||||
],
|
||||
dataKey: 'value',
|
||||
xAxisKey: 'name',
|
||||
color: '#A78BFA',
|
||||
},
|
||||
pieChart: {
|
||||
title: '지출 항목별 비율',
|
||||
data: [
|
||||
{ name: '매입', value: 305000000, percentage: 87, color: '#60A5FA' },
|
||||
{ name: '카드', value: 30000000, percentage: 9, color: '#34D399' },
|
||||
{ name: '발행어음', value: 15000000, percentage: 4, color: '#FBBF24' },
|
||||
],
|
||||
},
|
||||
table: {
|
||||
title: '지출 항목별 내역',
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', align: 'center' },
|
||||
{ key: 'category', label: '항목', align: 'left' },
|
||||
{ key: 'amount', label: '금액', align: 'right', format: 'currency' },
|
||||
{ key: 'ratio', label: '비율', align: 'center' },
|
||||
],
|
||||
data: [
|
||||
{ category: '매입', amount: 305000000, ratio: '87%' },
|
||||
{ category: '카드', amount: 30000000, ratio: '9%' },
|
||||
{ category: '발행어음', amount: 15000000, ratio: '4%' },
|
||||
],
|
||||
showTotal: true,
|
||||
totalLabel: '합계',
|
||||
totalValue: 350000000,
|
||||
totalColumnKey: 'amount',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const config = cardConfigs[cardId];
|
||||
if (config) {
|
||||
setDetailModalConfig(config);
|
||||
setIsDetailModalOpen(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 당월 예상 지출 클릭 (deprecated - 개별 카드 클릭으로 대체)
|
||||
const handleMonthlyExpenseClick = useCallback(() => {
|
||||
// TODO: 당월 예상 지출 상세 팝업 열기
|
||||
console.log('당월 예상 지출 클릭');
|
||||
}, []);
|
||||
|
||||
@@ -537,7 +800,7 @@ export function CEODashboard() {
|
||||
{dashboardSettings.monthlyExpense && (
|
||||
<MonthlyExpenseSection
|
||||
data={data.monthlyExpense}
|
||||
onClick={handleMonthlyExpenseClick}
|
||||
onCardClick={handleMonthlyExpenseCardClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -600,6 +863,15 @@ export function CEODashboard() {
|
||||
settings={dashboardSettings}
|
||||
onSave={handleSettingsSave}
|
||||
/>
|
||||
|
||||
{/* 상세 모달 */}
|
||||
{detailModalConfig && (
|
||||
<DetailModal
|
||||
isOpen={isDetailModalOpen}
|
||||
onClose={handleDetailModalClose}
|
||||
config={detailModalConfig}
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
384
src/components/business/CEODashboard/modals/DetailModal.tsx
Normal file
384
src/components/business/CEODashboard/modals/DetailModal.tsx
Normal file
@@ -0,0 +1,384 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
} from 'recharts';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type {
|
||||
DetailModalConfig,
|
||||
SummaryCardData,
|
||||
BarChartConfig,
|
||||
PieChartConfig,
|
||||
TableConfig,
|
||||
TableFilterConfig,
|
||||
} from '../types';
|
||||
|
||||
interface DetailModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
config: DetailModalConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 금액 포맷 함수
|
||||
*/
|
||||
const formatCurrency = (value: number): string => {
|
||||
return new Intl.NumberFormat('ko-KR').format(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 요약 카드 컴포넌트
|
||||
*/
|
||||
const SummaryCard = ({ data }: { data: SummaryCardData }) => {
|
||||
const displayValue = typeof data.value === 'number'
|
||||
? formatCurrency(data.value) + (data.unit || '원')
|
||||
: data.value;
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-sm text-gray-500 mb-1">{data.label}</p>
|
||||
<p className={cn(
|
||||
"text-2xl font-bold",
|
||||
data.isComparison && (data.isPositive ? "text-blue-600" : "text-red-600")
|
||||
)}>
|
||||
{data.isComparison && !data.isPositive && typeof data.value === 'string' && !data.value.startsWith('-') ? '-' : ''}
|
||||
{displayValue}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 막대 차트 컴포넌트
|
||||
*/
|
||||
const BarChartSection = ({ config }: { config: BarChartConfig }) => {
|
||||
return (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-sm font-medium text-gray-700 mb-4">{config.title}</p>
|
||||
<div className="h-[150px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={config.data} margin={{ top: 5, right: 5, left: -20, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#E5E7EB" />
|
||||
<XAxis
|
||||
dataKey={config.xAxisKey}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: '#6B7280' }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 10, fill: '#6B7280' }}
|
||||
tickFormatter={(value) => value >= 10000 ? `${value / 10000}만` : value}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number) => [formatCurrency(value) + '원', '']}
|
||||
contentStyle={{ fontSize: 12 }}
|
||||
/>
|
||||
<Bar
|
||||
dataKey={config.dataKey}
|
||||
fill={config.color || '#60A5FA'}
|
||||
radius={[4, 4, 0, 0]}
|
||||
maxBarSize={40}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 도넛 차트 컴포넌트
|
||||
*/
|
||||
const PieChartSection = ({ config }: { config: PieChartConfig }) => {
|
||||
return (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-sm font-medium text-gray-700 mb-4">{config.title}</p>
|
||||
{/* 도넛 차트 - 중앙 정렬 */}
|
||||
<div className="flex justify-center mb-4">
|
||||
<PieChart width={120} height={120}>
|
||||
<Pie
|
||||
data={config.data}
|
||||
cx={60}
|
||||
cy={60}
|
||||
innerRadius={35}
|
||||
outerRadius={55}
|
||||
paddingAngle={2}
|
||||
dataKey="value"
|
||||
>
|
||||
{config.data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</div>
|
||||
{/* 범례 - 차트 아래 배치 */}
|
||||
<div className="space-y-2">
|
||||
{config.data.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: item.color }}
|
||||
/>
|
||||
<span className="text-gray-600">{item.name}</span>
|
||||
<span className="text-gray-400">{item.percentage}%</span>
|
||||
</div>
|
||||
<span className="font-medium text-gray-900">
|
||||
{formatCurrency(item.value)}원
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 테이블 컴포넌트
|
||||
*/
|
||||
const TableSection = ({ config }: { config: TableConfig }) => {
|
||||
const [filters, setFilters] = useState<Record<string, string>>(() => {
|
||||
const initial: Record<string, string> = {};
|
||||
config.filters?.forEach((filter) => {
|
||||
initial[filter.key] = filter.defaultValue;
|
||||
});
|
||||
return initial;
|
||||
});
|
||||
|
||||
const handleFilterChange = useCallback((key: string, value: string) => {
|
||||
setFilters((prev) => ({ ...prev, [key]: value }));
|
||||
}, []);
|
||||
|
||||
// 필터링된 데이터
|
||||
const filteredData = useMemo(() => {
|
||||
// 데이터가 없는 경우 빈 배열 반환
|
||||
if (!config.data || !Array.isArray(config.data)) {
|
||||
return [];
|
||||
}
|
||||
let result = [...config.data];
|
||||
|
||||
// 각 필터 적용 (sortOrder는 정렬용이므로 제외)
|
||||
config.filters?.forEach((filter) => {
|
||||
if (filter.key === 'sortOrder') return; // 정렬 필터는 값 필터링에서 제외
|
||||
const filterValue = filters[filter.key];
|
||||
if (filterValue && filterValue !== 'all') {
|
||||
result = result.filter((row) => row[filter.key] === filterValue);
|
||||
}
|
||||
});
|
||||
|
||||
// 정렬 필터 적용 (sortOrder가 있는 경우)
|
||||
if (filters['sortOrder']) {
|
||||
const sortOrder = filters['sortOrder'];
|
||||
result.sort((a, b) => {
|
||||
const dateA = new Date(a['date'] as string).getTime();
|
||||
const dateB = new Date(b['date'] as string).getTime();
|
||||
return sortOrder === 'latest' ? dateB - dateA : dateA - dateB;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [config.data, config.filters, filters]);
|
||||
|
||||
// 셀 값 포맷팅
|
||||
const formatCellValue = (value: unknown, format?: string): string => {
|
||||
if (value === null || value === undefined) return '-';
|
||||
|
||||
switch (format) {
|
||||
case 'currency':
|
||||
return typeof value === 'number' ? formatCurrency(value) : String(value);
|
||||
case 'number':
|
||||
return typeof value === 'number' ? formatCurrency(value) : String(value);
|
||||
case 'date':
|
||||
return String(value);
|
||||
default:
|
||||
return String(value);
|
||||
}
|
||||
};
|
||||
|
||||
// 셀 정렬 클래스
|
||||
const getAlignClass = (align?: string): string => {
|
||||
switch (align) {
|
||||
case 'center':
|
||||
return 'text-center';
|
||||
case 'right':
|
||||
return 'text-right';
|
||||
default:
|
||||
return 'text-left';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-6">
|
||||
{/* 테이블 헤더 */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-medium text-gray-800">{config.title}</h4>
|
||||
<span className="text-sm text-gray-500">총 {filteredData.length}건</span>
|
||||
</div>
|
||||
|
||||
{/* 필터 영역 */}
|
||||
{config.filters && config.filters.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
{config.filters.map((filter) => (
|
||||
<Select
|
||||
key={filter.key}
|
||||
value={filters[filter.key]}
|
||||
onValueChange={(value) => handleFilterChange(filter.key, value)}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-auto min-w-[80px] text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{filter.options.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 테이블 */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
{config.columns.map((column) => (
|
||||
<th
|
||||
key={column.key}
|
||||
className={cn(
|
||||
"px-4 py-3 text-xs font-medium text-gray-600",
|
||||
getAlignClass(column.align),
|
||||
column.width && `w-[${column.width}]`
|
||||
)}
|
||||
>
|
||||
{column.label}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredData.map((row, rowIndex) => (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
className="border-t border-gray-100 hover:bg-gray-50"
|
||||
>
|
||||
{config.columns.map((column) => (
|
||||
<td
|
||||
key={column.key}
|
||||
className={cn(
|
||||
"px-4 py-3 text-sm",
|
||||
getAlignClass(column.align)
|
||||
)}
|
||||
>
|
||||
{column.key === 'no'
|
||||
? rowIndex + 1
|
||||
: formatCellValue(row[column.key], column.format)
|
||||
}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
|
||||
{/* 합계 행 */}
|
||||
{config.showTotal && (
|
||||
<tr className="border-t-2 border-gray-200 bg-gray-50 font-medium">
|
||||
{config.columns.map((column, colIndex) => (
|
||||
<td
|
||||
key={column.key}
|
||||
className={cn(
|
||||
"px-4 py-3 text-sm",
|
||||
getAlignClass(column.align)
|
||||
)}
|
||||
>
|
||||
{column.key === config.totalColumnKey
|
||||
? (typeof config.totalValue === 'number'
|
||||
? formatCurrency(config.totalValue)
|
||||
: config.totalValue)
|
||||
: (colIndex === 0 ? config.totalLabel || '합계' : '')}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 상세 모달 공통 컴포넌트
|
||||
*/
|
||||
export function DetailModal({ isOpen, onClose, config }: DetailModalProps) {
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent className="!w-[85vw] !max-w-[1600px] max-h-[90vh] overflow-y-auto p-0">
|
||||
{/* 헤더 */}
|
||||
<DialogHeader className="sticky top-0 bg-white z-10 px-6 py-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<DialogTitle className="text-lg font-bold">{config.title}</DialogTitle>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="p-1 hover:bg-gray-100 rounded-full transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 요약 카드 영역 */}
|
||||
{config.summaryCards.length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{config.summaryCards.map((card, index) => (
|
||||
<SummaryCard key={index} data={card} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 차트 영역 */}
|
||||
{(config.barChart || config.pieChart) && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{config.barChart && <BarChartSection config={config.barChart} />}
|
||||
{config.pieChart && <PieChartSection config={config.pieChart} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 테이블 영역 */}
|
||||
{config.table && <TableSection key={config.title} config={config.table} />}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { ScheduleDetailModal } from './ScheduleDetailModal';
|
||||
export { DetailModal } from './DetailModal';
|
||||
|
||||
@@ -6,10 +6,10 @@ import type { MonthlyExpenseData } from '../types';
|
||||
|
||||
interface MonthlyExpenseSectionProps {
|
||||
data: MonthlyExpenseData;
|
||||
onClick?: () => void;
|
||||
onCardClick?: (cardId: string) => void;
|
||||
}
|
||||
|
||||
export function MonthlyExpenseSection({ data, onClick }: MonthlyExpenseSectionProps) {
|
||||
export function MonthlyExpenseSection({ data, onCardClick }: MonthlyExpenseSectionProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
@@ -20,7 +20,7 @@ export function MonthlyExpenseSection({ data, onClick }: MonthlyExpenseSectionPr
|
||||
<AmountCardItem
|
||||
key={card.id}
|
||||
card={card}
|
||||
onClick={onClick}
|
||||
onClick={onCardClick ? () => onCardClick(card.id) : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -214,6 +214,90 @@ export interface DashboardSettings {
|
||||
calendar: boolean;
|
||||
}
|
||||
|
||||
// ===== 상세 모달 공통 타입 =====
|
||||
|
||||
// 요약 카드 타입
|
||||
export interface SummaryCardData {
|
||||
label: string;
|
||||
value: string | number;
|
||||
isComparison?: boolean; // 전월 대비 등 비교 값인 경우
|
||||
isPositive?: boolean; // 비교 값일 때 증가/감소 여부
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
// 막대 차트 데이터 타입
|
||||
export interface BarChartDataItem {
|
||||
name: string;
|
||||
value: number;
|
||||
[key: string]: string | number;
|
||||
}
|
||||
|
||||
// 막대 차트 설정 타입
|
||||
export interface BarChartConfig {
|
||||
title: string;
|
||||
data: BarChartDataItem[];
|
||||
dataKey: string;
|
||||
xAxisKey: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 도넛 차트 데이터 타입
|
||||
export interface PieChartDataItem {
|
||||
name: string;
|
||||
value: number;
|
||||
percentage: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
// 도넛 차트 설정 타입
|
||||
export interface PieChartConfig {
|
||||
title: string;
|
||||
data: PieChartDataItem[];
|
||||
}
|
||||
|
||||
// 테이블 컬럼 타입
|
||||
export interface TableColumnConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
format?: 'number' | 'currency' | 'date' | 'text';
|
||||
width?: string;
|
||||
}
|
||||
|
||||
// 테이블 필터 옵션 타입
|
||||
export interface TableFilterOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
// 테이블 필터 설정 타입
|
||||
export interface TableFilterConfig {
|
||||
key: string;
|
||||
options: TableFilterOption[];
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
// 테이블 설정 타입
|
||||
export interface TableConfig {
|
||||
title: string;
|
||||
columns: TableColumnConfig[];
|
||||
data: Record<string, unknown>[];
|
||||
filters?: TableFilterConfig[];
|
||||
showTotal?: boolean;
|
||||
totalLabel?: string;
|
||||
totalValue?: string | number;
|
||||
totalColumnKey?: string; // 합계가 들어갈 컬럼 키
|
||||
}
|
||||
|
||||
// 상세 모달 전체 설정 타입
|
||||
export interface DetailModalConfig {
|
||||
title: string;
|
||||
summaryCards: SummaryCardData[];
|
||||
barChart?: BarChartConfig;
|
||||
pieChart?: PieChartConfig;
|
||||
table?: TableConfig;
|
||||
}
|
||||
|
||||
// 기본 설정값
|
||||
export const DEFAULT_DASHBOARD_SETTINGS: DashboardSettings = {
|
||||
todayIssue: {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
PostApiData,
|
||||
@@ -63,7 +63,7 @@ export async function getPosts(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] getPosts error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export async function getPost(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] getPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -142,7 +142,7 @@ export async function createPost(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] createPost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export async function updatePost(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] updatePost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -221,7 +221,7 @@ export async function deletePost(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] deletePost error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -260,7 +260,7 @@ export async function getComments(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] getComments error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -298,7 +298,7 @@ export async function createComment(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] createComment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -337,7 +337,7 @@ export async function updateComment(
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] updateComment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -374,7 +374,7 @@ export async function deleteComment(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[CustomerCenterActions] deleteComment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
AttendanceRecord,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Card, CardFormData, CardStatus } from './types';
|
||||
|
||||
@@ -274,7 +274,7 @@ export async function deleteCards(ids: string[]): Promise<{ success: boolean; er
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteCards] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
// ============================================
|
||||
@@ -316,7 +316,7 @@ export async function deleteDepartmentsMany(
|
||||
results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteDepartmentsMany] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch, getServerApiHeaders } from '@/lib/api/fetch-wrapper';
|
||||
import type { Employee, EmployeeFormData, EmployeeStats } from './types';
|
||||
@@ -100,7 +100,7 @@ export async function getEmployees(params?: {
|
||||
lastPage: result.data.last_page,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] getEmployees error:', error);
|
||||
return { data: [], total: 0, lastPage: 1 };
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export async function getEmployeeById(id: string): Promise<Employee | null | { _
|
||||
|
||||
return transformApiToFrontend(result.data);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] getEmployeeById error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export async function createEmployee(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] createEmployee error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -230,7 +230,7 @@ export async function updateEmployee(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] updateEmployee error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -268,7 +268,7 @@ export async function deleteEmployee(id: string): Promise<{ success: boolean; er
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] deleteEmployee error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -309,7 +309,7 @@ export async function deleteEmployees(ids: string[]): Promise<{ success: boolean
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] deleteEmployees error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -349,7 +349,7 @@ export async function getEmployeeStats(): Promise<EmployeeStats | null | { __aut
|
||||
averageTenure: result.data.average_tenure ?? 0,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] getEmployeeStats error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -401,7 +401,7 @@ export async function getPositions(type?: 'rank' | 'title'): Promise<PositionIte
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] getPositions error:', error);
|
||||
return [];
|
||||
}
|
||||
@@ -450,7 +450,7 @@ export async function getDepartments(): Promise<DepartmentItem[]> {
|
||||
const departments = Array.isArray(result.data) ? result.data : result.data.data || [];
|
||||
return departments;
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] getDepartments error:', error);
|
||||
return [];
|
||||
}
|
||||
@@ -513,7 +513,7 @@ export async function uploadProfileImage(inputFormData: FormData): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeActions] uploadProfileImage error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { SalaryRecord, SalaryDetail, PaymentStatus } from './types';
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
// ============================================
|
||||
@@ -293,7 +293,7 @@ export async function getLeaves(params?: GetLeavesParams): Promise<{
|
||||
error: result.message || '휴가 목록 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getLeaves] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -332,7 +332,7 @@ export async function getLeaveById(id: number): Promise<{
|
||||
error: result.message || '휴가 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getLeaveById] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -379,7 +379,7 @@ export async function createLeave(
|
||||
error: result.message || '휴가 신청에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createLeave] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -420,7 +420,7 @@ export async function approveLeave(
|
||||
error: result.message || '휴가 승인에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[approveLeave] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -461,7 +461,7 @@ export async function rejectLeave(
|
||||
error: result.message || '휴가 반려에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[rejectLeave] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -502,7 +502,7 @@ export async function cancelLeave(
|
||||
error: result.message || '휴가 취소에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[cancelLeave] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -545,7 +545,7 @@ export async function getMyLeaveBalance(year?: number): Promise<{
|
||||
error: result.message || '잔여 휴가 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getMyLeaveBalance] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -591,7 +591,7 @@ export async function getUserLeaveBalance(
|
||||
error: result.message || '잔여 휴가 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getUserLeaveBalance] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -635,7 +635,7 @@ export async function setLeaveBalance(
|
||||
error: result.message || '잔여 휴가 설정에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[setLeaveBalance] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -669,7 +669,7 @@ export async function deleteLeave(id: number): Promise<{ success: boolean; error
|
||||
error: result.message || '휴가 삭제에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteLeave] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -701,7 +701,7 @@ export async function approveLeavesMany(ids: number[]): Promise<{
|
||||
error: allSuccess ? undefined : '일부 휴가 승인에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[approveLeavesMany] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -736,7 +736,7 @@ export async function rejectLeavesMany(
|
||||
error: allSuccess ? undefined : '일부 휴가 반려에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[rejectLeavesMany] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -795,7 +795,7 @@ export async function getLeaveBalances(params?: GetLeaveBalancesParams): Promise
|
||||
error: result.message || '휴가 사용현황 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getLeaveBalances] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -942,7 +942,7 @@ export async function getLeaveGrants(params?: GetLeaveGrantsParams): Promise<{
|
||||
error: result.message || '휴가 부여 이력 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getLeaveGrants] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -988,7 +988,7 @@ export async function createLeaveGrant(
|
||||
error: result.message || '휴가 부여에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createLeaveGrant] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -1022,7 +1022,7 @@ export async function deleteLeaveGrant(id: number): Promise<{ success: boolean;
|
||||
error: result.message || '휴가 부여 삭제에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteLeaveGrant] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -1116,7 +1116,7 @@ export async function getActiveEmployees(): Promise<{
|
||||
error: result.message || '직원 목록 조회에 실패했습니다.',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getActiveEmployees] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
ReceivingItem,
|
||||
@@ -242,7 +242,7 @@ export async function getReceivings(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] getReceivings error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -282,7 +282,7 @@ export async function getReceivingStats(): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToStats(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] getReceivingStats error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -317,7 +317,7 @@ export async function getReceivingById(id: string): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] getReceivingById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -351,7 +351,7 @@ export async function createReceiving(
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] createReceiving error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -386,7 +386,7 @@ export async function updateReceiving(
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] updateReceiving error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -418,7 +418,7 @@ export async function deleteReceiving(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] deleteReceiving error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -453,7 +453,7 @@ export async function processReceiving(
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] processReceiving error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
StockItem,
|
||||
@@ -259,7 +259,7 @@ export async function getStocks(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[StockActions] getStocks error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -299,7 +299,7 @@ export async function getStockStats(): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToStats(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[StockActions] getStockStats error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -334,7 +334,7 @@ export async function getStockStatsByType(): Promise<{
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[StockActions] getStockStatsByType error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -369,7 +369,7 @@ export async function getStockById(id: string): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[StockActions] getStockById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -311,11 +311,16 @@ export function ShipmentList() {
|
||||
count: shipmentStats?.totalCount || 0,
|
||||
};
|
||||
|
||||
const statusTabs: TabOption[] = Object.entries(statusStats).map(([key, stat]) => ({
|
||||
value: key,
|
||||
label: stat.label,
|
||||
count: stat.count,
|
||||
}));
|
||||
const statusTabs: TabOption[] = Object.entries(statusStats).map(([key, stat]) => {
|
||||
// stat이 객체가 아니거나 label이 없는 경우 방어
|
||||
const label = typeof stat?.label === 'string' ? stat.label : key;
|
||||
const count = typeof stat?.count === 'number' ? stat.count : 0;
|
||||
return {
|
||||
value: key,
|
||||
label,
|
||||
count,
|
||||
};
|
||||
});
|
||||
|
||||
return [allTab, ...statusTabs];
|
||||
}, [statusStats, shipmentStats?.totalCount]);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
ShipmentItem,
|
||||
@@ -375,7 +375,7 @@ export async function getShipments(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipments error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -419,7 +419,7 @@ export async function getShipmentStats(): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToStats(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipmentStats error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -458,7 +458,7 @@ export async function getShipmentStatsByStatus(): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToStatsByStatus(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipmentStatsByStatus error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -497,7 +497,7 @@ export async function getShipmentById(id: string): Promise<{
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipmentById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -536,7 +536,7 @@ export async function createShipment(
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] createShipment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -576,7 +576,7 @@ export async function updateShipment(
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] updateShipment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -631,7 +631,7 @@ export async function updateShipmentStatus(
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] updateShipmentStatus error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -666,7 +666,7 @@ export async function deleteShipment(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] deleteShipment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -705,7 +705,7 @@ export async function getLotOptions(): Promise<{
|
||||
|
||||
return { success: true, data: result.data || [] };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getLotOptions error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -744,7 +744,7 @@ export async function getLogisticsOptions(): Promise<{
|
||||
|
||||
return { success: true, data: result.data || [] };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getLogisticsOptions error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -783,7 +783,7 @@ export async function getVehicleTonnageOptions(): Promise<{
|
||||
|
||||
return { success: true, data: result.data || [] };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getVehicleTonnageOptions error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { PricingData, ItemInfo } from './types';
|
||||
|
||||
@@ -197,7 +197,7 @@ export async function getPricingById(id: string): Promise<PricingData | null> {
|
||||
|
||||
return transformApiToFrontend(result.data);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] getPricingById error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -248,7 +248,7 @@ export async function getItemInfo(itemId: string): Promise<ItemInfo | null> {
|
||||
unit: item.unit || 'EA',
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] getItemInfo error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -304,7 +304,7 @@ export async function createPricing(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] createPricing error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -367,7 +367,7 @@ export async function updatePricing(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] updatePricing error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -415,7 +415,7 @@ export async function deletePricing(id: string): Promise<{ success: boolean; err
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] deletePricing error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -466,7 +466,7 @@ export async function finalizePricing(id: string): Promise<{ success: boolean; d
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] finalizePricing error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -547,7 +547,7 @@ export async function getPricingRevisions(priceId: string): Promise<{
|
||||
data: revisions,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PricingActions] getPricingRevisions error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Process, ProcessFormData, ClassificationRule } from '@/types/process';
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function getProcessList(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getProcessList] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -201,7 +201,7 @@ export async function getProcessById(id: string): Promise<{ success: boolean; da
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getProcessById] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -235,7 +235,7 @@ export async function createProcess(data: ProcessFormData): Promise<{ success: b
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createProcess] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -269,7 +269,7 @@ export async function updateProcess(id: string, data: ProcessFormData): Promise<
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateProcess] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -300,7 +300,7 @@ export async function deleteProcess(id: string): Promise<{ success: boolean; err
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteProcess] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -332,7 +332,7 @@ export async function deleteProcesses(ids: string[]): Promise<{ success: boolean
|
||||
|
||||
return { success: true, deletedCount: result.data.deleted_count };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteProcesses] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -363,7 +363,7 @@ export async function toggleProcessActive(id: string): Promise<{ success: boolea
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[toggleProcessActive] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -410,7 +410,7 @@ export async function getProcessOptions(): Promise<{
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getProcessOptions] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -455,7 +455,7 @@ export async function getProcessStats(): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getProcessStats] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -503,7 +503,7 @@ export async function getDepartmentOptions(): Promise<DepartmentOption[]> {
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getDepartmentOptions] Error:', error);
|
||||
return [];
|
||||
}
|
||||
@@ -564,7 +564,7 @@ export async function getItemList(params?: GetItemListParams): Promise<ItemOptio
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getItemList] Error:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { WorkOrder, WorkerStatus, ProcessType, DashboardStats } from './types';
|
||||
|
||||
@@ -184,7 +184,7 @@ export async function getDashboardData(processType?: ProcessType): Promise<{
|
||||
stats,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ProductionDashboardActions] getDashboardData error:', error);
|
||||
return {
|
||||
...emptyResult,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
WorkOrder,
|
||||
@@ -124,7 +124,7 @@ export async function getWorkOrders(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] getWorkOrders error:', error);
|
||||
return { ...emptyResponse, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export async function getWorkOrderStats(): Promise<{
|
||||
data: transformStatsApiToFrontend(statsApi),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] getWorkOrderStats error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -210,7 +210,7 @@ export async function getWorkOrderById(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] getWorkOrderById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -261,7 +261,7 @@ export async function createWorkOrder(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] createWorkOrder error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -304,7 +304,7 @@ export async function updateWorkOrder(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] updateWorkOrder error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -334,7 +334,7 @@ export async function deleteWorkOrder(id: string): Promise<{ success: boolean; e
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] deleteWorkOrder error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -375,7 +375,7 @@ export async function updateWorkOrderStatus(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] updateWorkOrderStatus error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -420,7 +420,7 @@ export async function assignWorkOrder(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] assignWorkOrder error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -461,7 +461,7 @@ export async function toggleBendingField(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] toggleBendingField error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -506,7 +506,7 @@ export async function addWorkOrderIssue(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] addWorkOrderIssue error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -544,7 +544,7 @@ export async function resolveWorkOrderIssue(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] resolveWorkOrderIssue error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -632,7 +632,7 @@ export async function getSalesOrdersForWorkOrder(params?: {
|
||||
data: salesOrders,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] getSalesOrdersForWorkOrder error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -712,7 +712,7 @@ export async function getDepartmentsWithUsers(): Promise<{
|
||||
data: departments,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkOrderActions] getDepartmentsWithUsers error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
/**
|
||||
* 작업실적 관리 Server Actions
|
||||
*
|
||||
@@ -135,7 +135,7 @@ export async function getWorkResults(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] getWorkResults error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -206,7 +206,7 @@ export async function getWorkResultStats(params?: {
|
||||
data: transformStatsApiToFrontend(statsApi),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] getWorkResultStats error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -260,7 +260,7 @@ export async function getWorkResultById(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] getWorkResultById error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -340,7 +340,7 @@ export async function createWorkResult(data: {
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] createWorkResult error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -394,7 +394,7 @@ export async function updateWorkResult(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] updateWorkResult error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -436,7 +436,7 @@ export async function deleteWorkResult(id: string): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] deleteWorkResult error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -484,7 +484,7 @@ export async function toggleInspection(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] toggleInspection error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -532,7 +532,7 @@ export async function togglePackaging(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkResultActions] togglePackaging error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { WorkOrder, WorkOrderStatus } from '../ProductionDashboard/types';
|
||||
|
||||
@@ -133,7 +133,7 @@ export async function getMyWorkOrders(): Promise<{
|
||||
data: workOrders,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] getMyWorkOrders error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -186,7 +186,7 @@ export async function completeWorkOrder(
|
||||
lotNo,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] completeWorkOrder error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -270,7 +270,7 @@ export async function getMaterialsForWorkOrder(
|
||||
data: materials,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] getMaterialsForWorkOrder error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -313,7 +313,7 @@ export async function registerMaterialInput(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] registerMaterialInput error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -359,7 +359,7 @@ export async function reportIssue(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] reportIssue error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -471,7 +471,7 @@ export async function getProcessSteps(
|
||||
data: steps,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] getProcessSteps error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -514,7 +514,7 @@ export async function requestInspection(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[WorkerScreenActions] requestInspection error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
/**
|
||||
* 검사 관리 Server Actions
|
||||
* API 연동 완료 (2025-12-26)
|
||||
@@ -265,7 +265,7 @@ export async function getInspections(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspections error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -349,7 +349,7 @@ export async function getInspectionStats(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspectionStats error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -411,7 +411,7 @@ export async function getInspectionById(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] getInspectionById error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -498,7 +498,7 @@ export async function createInspection(data: {
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] createInspection error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -590,7 +590,7 @@ export async function updateInspection(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] updateInspection error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -640,7 +640,7 @@ export async function deleteInspection(id: string): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] deleteInspection error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -708,7 +708,7 @@ export async function completeInspection(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[InspectionActions] completeInspection error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type {
|
||||
Quote,
|
||||
@@ -133,7 +133,7 @@ export async function getQuotes(params?: QuoteListParams): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] getQuotes error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -210,7 +210,7 @@ export async function getQuoteById(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] getQuoteById error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -267,7 +267,7 @@ export async function createQuote(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] createQuote error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -325,7 +325,7 @@ export async function updateQuote(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] updateQuote error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -370,7 +370,7 @@ export async function deleteQuote(id: string): Promise<{ success: boolean; error
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] deleteQuote error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -416,7 +416,7 @@ export async function bulkDeleteQuotes(ids: string[]): Promise<{ success: boolea
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] bulkDeleteQuotes error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -469,7 +469,7 @@ export async function finalizeQuote(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] finalizeQuote error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -522,7 +522,7 @@ export async function cancelFinalizeQuote(id: string): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] cancelFinalizeQuote error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -577,7 +577,7 @@ export async function convertQuoteToOrder(id: string): Promise<{
|
||||
orderId: result.data?.order?.id ? String(result.data.order.id) : undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] convertQuoteToOrder error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -629,7 +629,7 @@ export async function getQuoteNumberPreview(): Promise<{
|
||||
data: result.data?.quote_number || result.data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] getQuoteNumberPreview error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -681,7 +681,7 @@ export async function generateQuotePdf(id: string): Promise<{
|
||||
data: blob,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] generateQuotePdf error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -730,7 +730,7 @@ export async function sendQuoteEmail(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] sendQuoteEmail error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -779,7 +779,7 @@ export async function sendQuoteKakao(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] sendQuoteKakao error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -871,7 +871,7 @@ export async function getFinishedGoods(category?: string): Promise<{
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] getFinishedGoods error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -964,7 +964,7 @@ export async function calculateBomBulk(items: BomCalculateItem[]): Promise<{
|
||||
data: result.data || [],
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] calculateBomBulk error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -1040,7 +1040,7 @@ export async function getQuotesSummary(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] getQuotesSummary error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -1085,7 +1085,7 @@ export async function getSiteNames(): Promise<{
|
||||
data: siteNames,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[QuoteActions] getSiteNames error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { ComprehensiveAnalysisData } from './types';
|
||||
|
||||
@@ -167,7 +167,7 @@ export async function getComprehensiveAnalysis(params?: {
|
||||
data: transformedData,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ComprehensiveAnalysisActions] getComprehensiveAnalysis error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -208,7 +208,7 @@ export async function approveIssue(issueId: string): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ComprehensiveAnalysisActions] approveIssue error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -250,7 +250,7 @@ export async function rejectIssue(issueId: string, reason?: string): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ComprehensiveAnalysisActions] rejectIssue error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { AccountInfo, TermsAgreement, MarketingConsent } from './types';
|
||||
|
||||
@@ -112,7 +112,7 @@ export async function getAccountInfo(): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[AccountInfoActions] getAccountInfo error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -162,7 +162,7 @@ export async function withdrawAccount(password: string): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[AccountInfoActions] withdrawAccount error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -212,7 +212,7 @@ export async function suspendTenant(): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[AccountInfoActions] suspendTenant error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -264,7 +264,7 @@ export async function updateAgreements(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[AccountInfoActions] updateAgreements error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -382,7 +382,7 @@ export async function uploadProfileImage(formData: FormData): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[AccountInfoActions] uploadProfileImage error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Account, AccountFormData, AccountStatus } from './types';
|
||||
import { BANK_LABELS } from './types';
|
||||
@@ -130,7 +130,7 @@ export async function getBankAccounts(params?: {
|
||||
};
|
||||
return { success: true, data: accounts, meta };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getBankAccounts] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -173,7 +173,7 @@ export async function getBankAccount(id: number): Promise<{
|
||||
const account = transformApiToFrontend(result.data);
|
||||
return { success: true, data: account };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getBankAccount] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -218,7 +218,7 @@ export async function createBankAccount(data: AccountFormData): Promise<{
|
||||
const account = transformApiToFrontend(result.data);
|
||||
return { success: true, data: account };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createBankAccount] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -266,7 +266,7 @@ export async function updateBankAccount(
|
||||
const account = transformApiToFrontend(result.data);
|
||||
return { success: true, data: account };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateBankAccount] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -306,7 +306,7 @@ export async function deleteBankAccount(id: number): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteBankAccount] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -348,7 +348,7 @@ export async function toggleBankAccountStatus(id: number): Promise<{
|
||||
const account = transformApiToFrontend(result.data);
|
||||
return { success: true, data: account };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[toggleBankAccountStatus] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -390,7 +390,7 @@ export async function setPrimaryBankAccount(id: number): Promise<{
|
||||
const account = transformApiToFrontend(result.data);
|
||||
return { success: true, data: account };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[setPrimaryBankAccount] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -421,7 +421,7 @@ export async function deleteBankAccounts(ids: number[]): Promise<{
|
||||
|
||||
return { success: true, deletedCount: successCount };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteBankAccounts] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -112,7 +112,7 @@ export async function getAttendanceSetting(): Promise<ApiResponse<AttendanceSett
|
||||
data: transformFromApi(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('getAttendanceSetting error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -152,7 +152,7 @@ export async function updateAttendanceSetting(
|
||||
data: transformFromApi(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('updateAttendanceSetting error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -211,7 +211,7 @@ export async function getDepartments(): Promise<ApiResponse<Department[]>> {
|
||||
|
||||
return { success: true, data: departments };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('getDepartments error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { CompanyFormData } from './types';
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function getCompanyInfo(): Promise<{
|
||||
|
||||
return { success: true, data: formData };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getCompanyInfo] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export async function updateCompanyInfo(
|
||||
const updatedData = transformApiToFrontend(result.data);
|
||||
return { success: true, data: updatedData };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateCompanyInfo] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -275,7 +275,7 @@ export async function uploadCompanyLogo(formData: FormData): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[uploadCompanyLogo] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { LeavePolicySettings } from './types';
|
||||
|
||||
@@ -101,7 +101,7 @@ export async function getLeavePolicy(): Promise<{
|
||||
data: transformedData,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[LeavePolicyActions] getLeavePolicy error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -158,7 +158,7 @@ export async function updateLeavePolicy(data: Partial<LeavePolicySettings>): Pro
|
||||
data: transformedData,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[LeavePolicyActions] updateLeavePolicy error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
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';
|
||||
@@ -54,7 +54,7 @@ export async function getNotificationSettings(): Promise<{
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[NotificationActions] getNotificationSettings error:', error);
|
||||
return {
|
||||
success: true,
|
||||
@@ -104,7 +104,7 @@ export async function saveNotificationSettings(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[NotificationActions] saveNotificationSettings error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { PaymentApiData, PaymentHistory } from './types';
|
||||
import { transformApiToFrontend } from './utils';
|
||||
@@ -87,7 +87,7 @@ export async function getPayments(params?: {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PaymentActions] getPayments error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -236,7 +236,7 @@ export async function getPaymentStatement(id: string): Promise<{
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PaymentActions] getPaymentStatement error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Role, RoleStats, PermissionMatrix, MenuTreeItem, ApiResponse, PaginatedResponse } from './types';
|
||||
@@ -45,7 +45,7 @@ export async function fetchRoles(params?: {
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to fetch roles:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '역할 목록 조회 실패' };
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export async function fetchRole(id: number): Promise<ApiResponse<Role>> {
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to fetch role:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '역할 조회 실패' };
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export async function createRole(data: {
|
||||
revalidatePath('/settings/permissions');
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to create role:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '역할 생성 실패' };
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export async function updateRole(
|
||||
revalidatePath(`/settings/permissions/${id}`);
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to update role:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '역할 수정 실패' };
|
||||
}
|
||||
@@ -184,7 +184,7 @@ export async function deleteRole(id: number): Promise<ApiResponse<void>> {
|
||||
revalidatePath('/settings/permissions');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to delete role:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '역할 삭제 실패' };
|
||||
}
|
||||
@@ -213,7 +213,7 @@ export async function fetchRoleStats(): Promise<ApiResponse<RoleStats>> {
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to fetch role stats:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '역할 통계 조회 실패' };
|
||||
}
|
||||
@@ -242,7 +242,7 @@ export async function fetchActiveRoles(): Promise<ApiResponse<Role[]>> {
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to fetch active roles:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '활성 역할 목록 조회 실패' };
|
||||
}
|
||||
@@ -276,7 +276,7 @@ export async function fetchPermissionMenus(): Promise<ApiResponse<{
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to fetch permission menus:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '메뉴 트리 조회 실패' };
|
||||
}
|
||||
@@ -305,7 +305,7 @@ export async function fetchPermissionMatrix(roleId: number): Promise<ApiResponse
|
||||
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to fetch permission matrix:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '권한 매트릭스 조회 실패' };
|
||||
}
|
||||
@@ -348,7 +348,7 @@ export async function togglePermission(
|
||||
revalidatePath(`/settings/permissions/${roleId}`);
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to toggle permission:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '권한 토글 실패' };
|
||||
}
|
||||
@@ -380,7 +380,7 @@ export async function allowAllPermissions(roleId: number): Promise<ApiResponse<{
|
||||
revalidatePath(`/settings/permissions/${roleId}`);
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to allow all permissions:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '전체 허용 실패' };
|
||||
}
|
||||
@@ -412,7 +412,7 @@ export async function denyAllPermissions(roleId: number): Promise<ApiResponse<{
|
||||
revalidatePath(`/settings/permissions/${roleId}`);
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to deny all permissions:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '전체 거부 실패' };
|
||||
}
|
||||
@@ -444,7 +444,7 @@ export async function resetPermissions(roleId: number): Promise<ApiResponse<{ co
|
||||
revalidatePath(`/settings/permissions/${roleId}`);
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Failed to reset permissions:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '권한 초기화 실패' };
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Popup, PopupFormData } from './types';
|
||||
import { transformApiToFrontend, transformFrontendToApi, type PopupApiData } from './utils';
|
||||
@@ -82,7 +82,7 @@ export async function getPopups(params?: {
|
||||
|
||||
return result.data.data.map(transformApiToFrontend);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PopupActions] getPopups error:', error);
|
||||
return [];
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export async function getPopupById(id: string): Promise<Popup | null> {
|
||||
|
||||
return transformApiToFrontend(result.data);
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PopupActions] getPopupById error:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ export async function createPopup(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PopupActions] createPopup error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -233,7 +233,7 @@ export async function updatePopup(
|
||||
data: transformApiToFrontend(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PopupActions] updatePopup error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -281,7 +281,7 @@ export async function deletePopup(id: string): Promise<{ success: boolean; error
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PopupActions] deletePopup error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -307,7 +307,7 @@ export async function deletePopups(ids: string[]): Promise<{ success: boolean; e
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[PopupActions] deletePopups error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Rank } from './types';
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function getRanks(params?: {
|
||||
const ranks = result.data.map(transformApiToFrontend);
|
||||
return { success: true, data: ranks };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getRanks] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -136,7 +136,7 @@ export async function createRank(data: {
|
||||
const rank = transformApiToFrontend(result.data);
|
||||
return { success: true, data: rank };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createRank] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export async function updateRank(
|
||||
const rank = transformApiToFrontend(result.data);
|
||||
return { success: true, data: rank };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateRank] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -226,7 +226,7 @@ export async function deleteRank(id: number): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteRank] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -269,7 +269,7 @@ export async function reorderRanks(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[reorderRanks] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { SubscriptionApiData, UsageApiData, SubscriptionInfo } from './types';
|
||||
import { transformApiToFrontend } from './utils';
|
||||
@@ -54,7 +54,7 @@ export async function getCurrentSubscription(): Promise<{
|
||||
data: result.data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[SubscriptionActions] getCurrentSubscription error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -112,7 +112,7 @@ export async function getUsage(): Promise<{
|
||||
data: result.data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[SubscriptionActions] getUsage error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -166,7 +166,7 @@ export async function cancelSubscription(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[SubscriptionActions] cancelSubscription error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -225,7 +225,7 @@ export async function requestDataExport(
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[SubscriptionActions] requestDataExport error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -264,7 +264,7 @@ export async function getSubscriptionData(): Promise<{
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[SubscriptionActions] getSubscriptionData error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Title } from './types';
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function getTitles(params?: {
|
||||
const titles = result.data.map(transformApiToFrontend);
|
||||
return { success: true, data: titles };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[getTitles] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -136,7 +136,7 @@ export async function createTitle(data: {
|
||||
const title = transformApiToFrontend(result.data);
|
||||
return { success: true, data: title };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[createTitle] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export async function updateTitle(
|
||||
const title = transformApiToFrontend(result.data);
|
||||
return { success: true, data: title };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[updateTitle] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -226,7 +226,7 @@ export async function deleteTitle(id: number): Promise<{
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[deleteTitle] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
@@ -269,7 +269,7 @@ export async function reorderTitles(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[reorderTitles] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://sam.kr:8080';
|
||||
@@ -120,7 +120,7 @@ export async function getWorkSetting(): Promise<{
|
||||
data: transformFromApi(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('getWorkSetting error:', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -169,7 +169,7 @@ export async function updateWorkSetting(
|
||||
data: transformFromApi(result.data),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('updateWorkSetting error:', error);
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -229,16 +229,15 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro
|
||||
setActiveMenu(result.menuId);
|
||||
|
||||
// 부모 메뉴가 있으면 자동으로 확장
|
||||
if (result.parentId && !expandedMenus.includes(result.parentId)) {
|
||||
setExpandedMenus(prev => [...prev, result.parentId!]);
|
||||
}
|
||||
} else {
|
||||
// 대시보드 등 어떤 메뉴에도 속하지 않는 페이지일 경우 모든 메뉴 닫기
|
||||
if (expandedMenus.length > 0) {
|
||||
setExpandedMenus([]);
|
||||
if (result.parentId) {
|
||||
setExpandedMenus(prev =>
|
||||
prev.includes(result.parentId!) ? prev : [...prev, result.parentId!]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [pathname, menuItems, setActiveMenu, expandedMenus]);
|
||||
// 대시보드 등 메뉴에 매칭되지 않아도 expandedMenus 유지
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname, menuItems, setActiveMenu]);
|
||||
|
||||
const handleMenuClick = (menuId: string, path: string) => {
|
||||
setActiveMenu(menuId);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { isRedirectError } from 'next/dist/client/components/redirect';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { createErrorResponse, type ApiErrorResponse } from './errors';
|
||||
import { refreshAccessToken } from './refresh-token';
|
||||
|
||||
@@ -177,7 +177,7 @@ export async function serverFetch(
|
||||
return { response, error: null };
|
||||
} catch (error) {
|
||||
// Next.js 15: redirect()는 특수한 에러를 throw하므로 다시 throw해서 Next.js가 처리하도록 함
|
||||
if (isRedirectError(error)) throw error;
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error(`[serverFetch] Network error: ${url}`, error);
|
||||
return {
|
||||
response: null,
|
||||
|
||||
38
src/lib/utils/redirect-error.ts
Normal file
38
src/lib/utils/redirect-error.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Next.js Redirect Error 유틸리티
|
||||
*
|
||||
* Next.js의 redirect() 함수가 throw하는 NEXT_REDIRECT 에러를 감지합니다.
|
||||
*
|
||||
* 왜 자체 구현인가?
|
||||
* - Next.js 내부 경로(next/dist/...)는 버전 업데이트 시 변경될 수 있음
|
||||
* - 공개 API로 제공되지 않는 내부 함수에 의존하지 않기 위함
|
||||
* - 한 곳에서 관리하여 유지보수 용이
|
||||
*
|
||||
* @see https://nextjs.org/docs/app/api-reference/functions/redirect
|
||||
*/
|
||||
|
||||
/**
|
||||
* Next.js redirect() 에러인지 확인
|
||||
*
|
||||
* redirect() 호출 시 Next.js는 특수한 에러를 throw합니다.
|
||||
* 이 에러는 catch 블록에서 잡히면 안 되고, 다시 throw해야 합니다.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* try {
|
||||
* // ... some code that might call redirect()
|
||||
* } catch (error) {
|
||||
* if (isNextRedirectError(error)) throw error;
|
||||
* // handle other errors
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function isNextRedirectError(error: unknown): boolean {
|
||||
return (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'digest' in error &&
|
||||
typeof (error as { digest: string }).digest === 'string' &&
|
||||
(error as { digest: string }).digest.startsWith('NEXT_REDIRECT')
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user