refactor(WEB): SearchableSelectionModal 공통화 및 actions lookup 통합
- SearchableSelectionModal<T> 제네릭 컴포넌트 추출 (organisms) - 검색 모달 5개 리팩토링: SupplierSearch, QuotationSelect, SalesOrderSelect, OrderSelect, ItemSearch - shared-lookups API 유틸 추가 (거래처/품목/수주 등 공통 조회) - create-crud-service 확장 (lookup, search 메서드) - actions.ts 20+개 파일 lookup 패턴 통일 - 공통 페이지 패턴 가이드 문서 추가 - CLAUDE.md Common Component Usage Rules 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,20 +16,13 @@
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import type { BadDebtRecord, BadDebtItem, CollectionStatus } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
// ===== API 응답 타입 =====
|
||||
|
||||
interface PaginatedResponse<T> {
|
||||
current_page: number;
|
||||
data: T[];
|
||||
total: number;
|
||||
per_page: number;
|
||||
last_page: number;
|
||||
}
|
||||
|
||||
interface BadDebtItemApiData {
|
||||
id: number;
|
||||
debt_amount: number;
|
||||
@@ -176,7 +169,7 @@ export async function getBadDebts(params?: {
|
||||
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/bad-debts?${searchParams.toString()}`,
|
||||
transform: (data: PaginatedResponse<BadDebtApiData>) => data.data.map(transformApiToFrontend),
|
||||
transform: (data: PaginatedApiResponse<BadDebtApiData>) => data.data.map(transformApiToFrontend),
|
||||
errorMessage: '악성채권 목록 조회에 실패했습니다.',
|
||||
});
|
||||
return result.data || [];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import type { BankTransaction, TransactionKind } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -34,13 +35,7 @@ interface BankTransactionApiSummary {
|
||||
withdrawal_unset_count: number;
|
||||
}
|
||||
|
||||
interface BankTransactionPaginatedResponse {
|
||||
data: BankTransactionApiItem[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type BankTransactionPaginatedResponse = PaginatedApiResponse<BankTransactionApiItem>;
|
||||
|
||||
interface FrontendPagination { currentPage: number; lastPage: number; perPage: number; total: number }
|
||||
const DEFAULT_PAGINATION: FrontendPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
|
||||
@@ -2,19 +2,14 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import type { BillRecord, BillApiData, BillStatus } from './types';
|
||||
import { transformApiToFrontend, transformFrontendToApi } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
// ===== API 응답 타입 =====
|
||||
interface BillPaginatedResponse {
|
||||
data: BillApiData[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type BillPaginatedResponse = PaginatedApiResponse<BillApiData>;
|
||||
|
||||
interface BillSummaryApiData {
|
||||
total_amount: number;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import type { CardTransaction } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -35,13 +36,7 @@ interface CardTransactionApiSummary {
|
||||
total_amount: number;
|
||||
}
|
||||
|
||||
interface CardPaginatedResponse {
|
||||
data: CardTransactionApiItem[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type CardPaginatedResponse = PaginatedApiResponse<CardTransactionApiItem>;
|
||||
|
||||
interface FrontendPagination { currentPage: number; lastPage: number; perPage: number; total: number }
|
||||
const DEFAULT_PAGINATION: FrontendPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import { fetchVendorOptions, fetchBankAccountOptions } from '@/lib/api/shared-lookups';
|
||||
import type { DepositRecord, DepositType, DepositStatus } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -26,13 +28,7 @@ interface DepositApiData {
|
||||
bank_account?: { id: number; bank_name: string; account_name: string } | null;
|
||||
}
|
||||
|
||||
interface DepositPaginatedResponse {
|
||||
data: DepositApiData[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type DepositPaginatedResponse = PaginatedApiResponse<DepositApiData>;
|
||||
|
||||
interface FrontendPagination { currentPage: number; lastPage: number; perPage: number; total: number }
|
||||
const DEFAULT_PAGINATION: FrontendPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
@@ -162,15 +158,7 @@ export async function updateDeposit(id: string, data: Partial<DepositRecord>): P
|
||||
export async function getVendors(): Promise<{
|
||||
success: boolean; data: { id: string; name: string }[]; error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/clients?per_page=100`,
|
||||
transform: (data: { data?: { id: number; name: string }[] } | { id: number; name: string }[]) => {
|
||||
type ClientApi = { id: number; name: string };
|
||||
const clients: ClientApi[] = Array.isArray(data) ? data : (data as { data?: ClientApi[] })?.data || [];
|
||||
return clients.map(c => ({ id: String(c.id), name: c.name }));
|
||||
},
|
||||
errorMessage: '거래처 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchVendorOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
@@ -178,14 +166,6 @@ export async function getVendors(): Promise<{
|
||||
export async function getBankAccounts(): Promise<{
|
||||
success: boolean; data: { id: string; name: string }[]; error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/bank-accounts?per_page=100`,
|
||||
transform: (data: { data?: { id: number; account_name: string; bank_name: string }[] } | { id: number; account_name: string; bank_name: string }[]) => {
|
||||
type AccountApi = { id: number; account_name: string; bank_name: string };
|
||||
const accounts: AccountApi[] = Array.isArray(data) ? data : (data as { data?: AccountApi[] })?.data || [];
|
||||
return accounts.map(a => ({ id: String(a.id), name: `${a.bank_name} ${a.account_name}` }));
|
||||
},
|
||||
errorMessage: '계좌 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchBankAccountOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import { fetchBankAccountDetailOptions } from '@/lib/api/shared-lookups';
|
||||
import type { ExpectedExpenseRecord, TransactionType, PaymentStatus, ApprovalStatus } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -27,13 +29,7 @@ interface ExpectedExpenseApiData {
|
||||
bank_account?: { id: number; bank_name: string; account_name: string } | null;
|
||||
}
|
||||
|
||||
interface ExpensePaginatedResponse {
|
||||
data: ExpectedExpenseApiData[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type ExpensePaginatedResponse = PaginatedApiResponse<ExpectedExpenseApiData>;
|
||||
|
||||
interface SummaryData {
|
||||
total_amount: number;
|
||||
@@ -217,14 +213,6 @@ export async function getClients(): Promise<{
|
||||
export async function getBankAccounts(): Promise<{
|
||||
success: boolean; data: { id: string; bankName: string; accountName: string; accountNumber: string }[]; error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/bank-accounts?per_page=100`,
|
||||
transform: (data: { data?: { id: number; bank_name: string; account_name: string; account_number: string }[] } | { id: number; bank_name: string; account_name: string; account_number: string }[]) => {
|
||||
type AccountApi = { id: number; bank_name: string; account_name: string; account_number: string };
|
||||
const accounts: AccountApi[] = Array.isArray(data) ? data : (data as { data?: AccountApi[] })?.data || [];
|
||||
return accounts.map(a => ({ id: String(a.id), bankName: a.bank_name, accountName: a.account_name, accountNumber: a.account_number }));
|
||||
},
|
||||
errorMessage: '은행 계좌 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchBankAccountDetailOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import { fetchVendorOptions, fetchBankAccountDetailOptions } from '@/lib/api/shared-lookups';
|
||||
import type { PurchaseRecord, PurchaseType } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -46,13 +48,7 @@ interface PurchaseApiData {
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
interface PurchaseApiPaginatedResponse {
|
||||
data: PurchaseApiData[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type PurchaseApiPaginatedResponse = PaginatedApiResponse<PurchaseApiData>;
|
||||
|
||||
// ===== 변환 함수 =====
|
||||
|
||||
@@ -199,17 +195,7 @@ export async function getBankAccounts(): Promise<{
|
||||
data: { id: string; bankName: string; accountName: string; accountNumber: string }[];
|
||||
error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/bank-accounts?per_page=100`,
|
||||
transform: (data: { data?: { id: number; bank_name: string; account_name: string; account_number: string }[] } | { id: number; bank_name: string; account_name: string; account_number: string }[]) => {
|
||||
type BankAccountApi = { id: number; bank_name: string; account_name: string; account_number: string };
|
||||
const accounts: BankAccountApi[] = Array.isArray(data) ? data : (data as { data?: BankAccountApi[] })?.data || [];
|
||||
return accounts.map(a => ({
|
||||
id: String(a.id), bankName: a.bank_name, accountName: a.account_name, accountNumber: a.account_number,
|
||||
}));
|
||||
},
|
||||
errorMessage: '은행 계좌 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchBankAccountDetailOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
@@ -219,14 +205,6 @@ export async function getVendors(): Promise<{
|
||||
data: { id: string; name: string }[];
|
||||
error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/clients?per_page=100`,
|
||||
transform: (data: { data?: { id: number; name: string }[] } | { id: number; name: string }[]) => {
|
||||
type ClientApi = { id: number; name: string };
|
||||
const clients: ClientApi[] = Array.isArray(data) ? data : (data as { data?: ClientApi[] })?.data || [];
|
||||
return clients.map(c => ({ id: String(c.id), name: c.name }));
|
||||
},
|
||||
errorMessage: '거래처 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchVendorOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { cookies } from 'next/headers';
|
||||
import type { VendorLedgerItem, VendorLedgerDetail, VendorLedgerSummary, TransactionEntry } from './types';
|
||||
@@ -64,13 +65,7 @@ interface VendorLedgerApiDetail {
|
||||
transactions: VendorLedgerApiTransaction[];
|
||||
}
|
||||
|
||||
interface VendorLedgerPaginatedResponse {
|
||||
data: VendorLedgerApiItem[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type VendorLedgerPaginatedResponse = PaginatedApiResponse<VendorLedgerApiItem>;
|
||||
|
||||
interface FrontendPagination { currentPage: number; lastPage: number; perPage: number; total: number }
|
||||
const DEFAULT_PAGINATION: FrontendPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import { fetchVendorOptions, fetchBankAccountOptions } from '@/lib/api/shared-lookups';
|
||||
import type { WithdrawalRecord, WithdrawalType } from './types';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
@@ -30,13 +32,7 @@ interface WithdrawalApiData {
|
||||
card?: { id: number; card_name: string } | null;
|
||||
}
|
||||
|
||||
interface WithdrawalPaginatedResponse {
|
||||
data: WithdrawalApiData[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
type WithdrawalPaginatedResponse = PaginatedApiResponse<WithdrawalApiData>;
|
||||
|
||||
interface FrontendPagination { currentPage: number; lastPage: number; perPage: number; total: number }
|
||||
const DEFAULT_PAGINATION: FrontendPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
@@ -165,15 +161,7 @@ export async function updateWithdrawal(id: string, data: Partial<WithdrawalRecor
|
||||
export async function getVendors(): Promise<{
|
||||
success: boolean; data: { id: string; name: string }[]; error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/clients?per_page=100`,
|
||||
transform: (data: { data?: { id: number; name: string }[] } | { id: number; name: string }[]) => {
|
||||
type ClientApi = { id: number; name: string };
|
||||
const clients: ClientApi[] = Array.isArray(data) ? data : (data as { data?: ClientApi[] })?.data || [];
|
||||
return clients.map(c => ({ id: String(c.id), name: c.name }));
|
||||
},
|
||||
errorMessage: '거래처 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchVendorOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
@@ -181,14 +169,6 @@ export async function getVendors(): Promise<{
|
||||
export async function getBankAccounts(): Promise<{
|
||||
success: boolean; data: { id: string; name: string }[]; error?: string;
|
||||
}> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/bank-accounts?per_page=100`,
|
||||
transform: (data: { data?: { id: number; account_name: string; bank_name: string }[] } | { id: number; account_name: string; bank_name: string }[]) => {
|
||||
type AccountApi = { id: number; account_name: string; bank_name: string };
|
||||
const accounts: AccountApi[] = Array.isArray(data) ? data : (data as { data?: AccountApi[] })?.data || [];
|
||||
return accounts.map(a => ({ id: String(a.id), name: `${a.bank_name} ${a.account_name}` }));
|
||||
},
|
||||
errorMessage: '계좌 조회에 실패했습니다.',
|
||||
});
|
||||
const result = await fetchBankAccountOptions();
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user