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:
유병철
2026-02-10 16:01:23 +09:00
parent 0643d56194
commit 437d5f6834
42 changed files with 1683 additions and 1144 deletions

View File

@@ -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 };
}