From 54d6d922bc0a189f8f9b37565e363f96ff0f3f58 Mon Sep 17 00:00:00 2001 From: donghwan Date: Fri, 30 May 2025 16:15:49 +0900 Subject: [PATCH 1/6] backup --- src/services/partnerCompany.ts | 702 +++++++++++++++---------------- src/types/IFRS/partnerCompany.ts | 60 +++ 2 files changed, 391 insertions(+), 371 deletions(-) create mode 100644 src/types/IFRS/partnerCompany.ts diff --git a/src/services/partnerCompany.ts b/src/services/partnerCompany.ts index b05ac82..bcf8f40 100644 --- a/src/services/partnerCompany.ts +++ b/src/services/partnerCompany.ts @@ -1,72 +1,17 @@ -/** - * 파트너사(협력사) 정보 타입 - */ -export interface PartnerCompany { - id?: string; // 파트너사 고유 ID - status?: "ACTIVE" | "INACTIVE" | "PENDING"; // 파트너사 상태 - industry?: string; // 산업군 - country?: string; // 국가 - address?: string; // 주소 - corp_code: string; // DART corp_code - corp_name: string; // 회사명 (API 응답 필드) - stock_code?: string; // 주식 코드 - contract_start_date?: string; // 계약 시작일 (API 응답 필드 - YYYY-MM-DD 문자열) - modify_date?: string; // 수정일 - // 프론트엔드에서 사용할 필드 (API 응답을 변환하여 채움) - companyName: string; - contractStartDate: Date; -} - -/** - * 파트너사 목록 API 응답 타입 (페이지네이션 포함) - */ -export interface PartnerCompanyResponse { - data: PartnerCompany[]; - total: number; - page: number; - pageSize: number; -} - -/** - * DART API 기업 정보 타입 - */ -export interface DartCorpInfo { - corp_code: string; // 기업 고유 코드 - corp_name: string; // 기업명 - stock_code?: string; // 주식 코드 (상장사만 존재) - modify_date: string; // 최종 수정일 -} +const API_BASE_URL = process.env.NEXT_DART_API_URL || '/api' // 환경 변수 또는 기본값 사용 -/** - * DART API 페이지네이션 응답 타입 - */ -export interface DartApiResponse { - data: DartCorpInfo[]; // 기업 정보 목록 - total: number; // 전체 항목 수 - page: number; // 현재 페이지 번호 - pageSize: number; // 페이지 당 항목 수 - totalPages: number; // 전체 페이지 수 - hasNextPage: boolean; // 다음 페이지 존재 여부 -} - -/** - * 기업 검색 파라미터 타입 - */ -export interface SearchCorpParams { - page?: number; - pageSize?: number; - listedOnly?: boolean; - corpNameFilter?: string; -} - -const API_BASE_URL = process.env.NEXT_DART_API_URL || "/api"; // 환경 변수 또는 기본값 사용 - -import { useAuthStore } from '@/stores/authStore'; +import {useAuthStore} from '@/stores/authStore' +import { + DartApiResponse, + PartnerCompany, + PartnerCompanyResponse, + SearchCorpParams +} from '@/types/IFRS/partnerCompany' // 파트너사 API 엔드포인트 -const PARTNER_COMPANIES_BASE_PATH = '/api/v1/partners/partner-companies'; -const UNIQUE_PARTNER_COMPANY_NAMES_ENDPOINT = `${API_BASE_URL}/api/v1/partners/unique-partner-companies`; -const DART_CORP_CODES_ENDPOINT = `${API_BASE_URL}/api/v1/dart/corp-codes`; +const PARTNER_COMPANIES_BASE_PATH = '/api/v1/partners/partner-companies' +const UNIQUE_PARTNER_COMPANY_NAMES_ENDPOINT = `${API_BASE_URL}/api/v1/partners/unique-partner-companies` +const DART_CORP_CODES_ENDPOINT = `${API_BASE_URL}/api/v1/dart/corp-codes` /** * 파트너사 목록을 조회합니다. (페이지네이션 지원) @@ -76,41 +21,43 @@ const DART_CORP_CODES_ENDPOINT = `${API_BASE_URL}/api/v1/dart/corp-codes`; * @returns 파트너사 목록 응답 */ export async function fetchPartnerCompanies( - page = 1, - pageSize = 10, - companyNameFilter?: string, + page = 1, + pageSize = 10, + companyNameFilter?: string ): Promise { - try { - const url = new URL(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}`); - url.searchParams.append('page', page.toString()); - url.searchParams.append('pageSize', pageSize.toString()); - - if (companyNameFilter) { - url.searchParams.append('companyName', companyNameFilter); - } - - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - const response = await fetch(url.toString(), { - method: 'GET', - headers - }); - - if (!response.ok) { - throw new Error(`파트너사 목록을 가져오는 중 오류가 발생했습니다: ${response.status}`); - } - - return await response.json(); - } catch (error) { - console.error('파트너사 목록을 가져오는 중 오류:', error); - throw error; - } + try { + const url = new URL(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}`) + url.searchParams.append('page', page.toString()) + url.searchParams.append('pageSize', pageSize.toString()) + + if (companyNameFilter) { + url.searchParams.append('companyName', companyNameFilter) + } + + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + if (token) { + headers.Authorization = `Bearer ${token}` + } + + const response = await fetch(url.toString(), { + method: 'GET', + headers + }) + + if (!response.ok) { + throw new Error( + `파트너사 목록을 가져오는 중 오류가 발생했습니다: ${response.status}` + ) + } + + return await response.json() + } catch (error) { + console.error('파트너사 목록을 가져오는 중 오류:', error) + throw error + } } /** @@ -119,36 +66,36 @@ export async function fetchPartnerCompanies( * @returns 파트너사 정보 */ export async function fetchPartnerCompanyById( - id: string, + id: string ): Promise { - try { - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${id}`, { - method: 'GET', - headers, - }); - - if (response.status === 404) { - return null; - } - - if (!response.ok) { - throw new Error(`파트너사 정보를 가져오는데 실패했습니다: ${response.status}`); - } - - return await response.json(); - } catch (error) { - console.error('파트너사 정보 조회 오류:', error); - throw error; - } + try { + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + + if (token) { + headers.Authorization = `Bearer ${token}` + } + + const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${id}`, { + method: 'GET', + headers + }) + + if (response.status === 404) { + return null + } + + if (!response.ok) { + throw new Error(`파트너사 정보를 가져오는데 실패했습니다: ${response.status}`) + } + + return await response.json() + } catch (error) { + console.error('파트너사 정보 조회 오류:', error) + throw error + } } /** @@ -156,38 +103,40 @@ export async function fetchPartnerCompanyById( * @param partnerInput 등록할 파트너사 정보 * @returns 등록된 파트너사 정보 */ -export async function createPartnerCompany( - partnerInput: { companyName: string; corpCode: string; contractStartDate: string }, -): Promise { - try { - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers.Authorization = `Bearer ${token}`; - headers['X-Member-Id'] = token; // 또는 토큰에서 추출한 사용자 ID - } - - const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}`, { - method: 'POST', - headers, - body: JSON.stringify(partnerInput), - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error( - `파트너사 등록에 실패했습니다: ${response.status} ${errorData?.message || ''}` - ); - } - - return await response.json(); - } catch (error) { - console.error('파트너사 등록 오류:', error); - throw error; - } +export async function createPartnerCompany(partnerInput: { + companyName: string + corpCode: string + contractStartDate: string +}): Promise { + try { + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + + if (token) { + headers.Authorization = `Bearer ${token}` + headers['X-Member-Id'] = token // 또는 토큰에서 추출한 사용자 ID + } + + const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}`, { + method: 'POST', + headers, + body: JSON.stringify(partnerInput) + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => null) + throw new Error( + `파트너사 등록에 실패했습니다: ${response.status} ${errorData?.message || ''}` + ) + } + + return await response.json() + } catch (error) { + console.error('파트너사 등록 오류:', error) + throw error + } } /** @@ -197,51 +146,54 @@ export async function createPartnerCompany( * @returns 수정된 파트너사 정보 */ export async function updatePartnerCompany( - id: string, - partnerData: Partial>, + id: string, + partnerData: Partial> ): Promise { - try { - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - // API 문서에 맞게 요청 데이터 변환 - const requestData = { - companyName: partnerData.companyName, - corpCode: partnerData.corp_code, - contractStartDate: partnerData.contractStartDate instanceof Date - ? partnerData.contractStartDate.toISOString().split('T')[0] - : partnerData.contractStartDate, - status: partnerData.status - }; - - const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${id}`, { - method: 'PATCH', - headers, - body: JSON.stringify(requestData), - }); - - if (response.status === 404) { - return null; - } - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error( - `파트너사 정보 수정에 실패했습니다: ${response.status} ${errorData?.message || ''}` - ); - } - - return await response.json(); - } catch (error) { - console.error('파트너사 수정 오류:', error); - throw error; - } + try { + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + + if (token) { + headers.Authorization = `Bearer ${token}` + } + + // API 문서에 맞게 요청 데이터 변환 + const requestData = { + companyName: partnerData.companyName, + corpCode: partnerData.corp_code, + contractStartDate: + partnerData.contractStartDate instanceof Date + ? partnerData.contractStartDate.toISOString().split('T')[0] + : partnerData.contractStartDate, + status: partnerData.status + } + + const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${id}`, { + method: 'PATCH', + headers, + body: JSON.stringify(requestData) + }) + + if (response.status === 404) { + return null + } + + if (!response.ok) { + const errorData = await response.json().catch(() => null) + throw new Error( + `파트너사 정보 수정에 실패했습니다: ${response.status} ${ + errorData?.message || '' + }` + ) + } + + return await response.json() + } catch (error) { + console.error('파트너사 수정 오류:', error) + throw error + } } /** @@ -249,31 +201,31 @@ export async function updatePartnerCompany( * @param id 파트너사 ID (UUID) */ export async function deletePartnerCompany(id: string): Promise { - try { - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${id}`, { - method: 'DELETE', - headers, - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error( - `파트너사 삭제에 실패했습니다: ${response.status} ${errorData?.message || ''}` - ); - } - } catch (error) { - console.error('파트너사 삭제 오류:', error); - throw error; - } + try { + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + + if (token) { + headers.Authorization = `Bearer ${token}` + } + + const response = await fetch(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${id}`, { + method: 'DELETE', + headers + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => null) + throw new Error( + `파트너사 삭제에 실패했습니다: ${response.status} ${errorData?.message || ''}` + ) + } + } catch (error) { + console.error('파트너사 삭제 오류:', error) + throw error + } } /** @@ -282,69 +234,69 @@ export async function deletePartnerCompany(id: string): Promise { * @returns DART API 응답 */ export async function searchCompaniesFromDart( - params: SearchCorpParams, + params: SearchCorpParams ): Promise { - try { - const url = new URL(DART_CORP_CODES_ENDPOINT); - - if (params.page !== undefined) { - url.searchParams.append('page', params.page.toString()); - } - - if (params.pageSize !== undefined) { - url.searchParams.append('pageSize', params.pageSize.toString()); - } - - if (params.listedOnly !== undefined) { - url.searchParams.append('listedOnly', params.listedOnly.toString()); - } - - if (params.corpNameFilter) { - url.searchParams.append('corpNameFilter', params.corpNameFilter); - } - - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers.Authorization = `Bearer ${token}`; - // API 키 헤더 추가 - headers['X-API-KEY'] = process.env.NEXT_PUBLIC_DART_API_KEY || ''; - } - - const response = await fetch(url.toString(), { - method: 'GET', - headers, - }); - - if (!response.ok) { - throw new Error(`DART 기업 검색에 실패했습니다: ${response.status}`); - } - - return await response.json(); - } catch (error) { - console.error('DART 기업 검색 오류:', error); - throw error; - } + try { + const url = new URL(DART_CORP_CODES_ENDPOINT) + + if (params.page !== undefined) { + url.searchParams.append('page', params.page.toString()) + } + + if (params.pageSize !== undefined) { + url.searchParams.append('pageSize', params.pageSize.toString()) + } + + if (params.listedOnly !== undefined) { + url.searchParams.append('listedOnly', params.listedOnly.toString()) + } + + if (params.corpNameFilter) { + url.searchParams.append('corpNameFilter', params.corpNameFilter) + } + + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + + if (token) { + headers.Authorization = `Bearer ${token}` + // API 키 헤더 추가 + headers['X-API-KEY'] = process.env.NEXT_PUBLIC_DART_API_KEY || '' + } + + const response = await fetch(url.toString(), { + method: 'GET', + headers + }) + + if (!response.ok) { + throw new Error(`DART 기업 검색에 실패했습니다: ${response.status}`) + } + + return await response.json() + } catch (error) { + console.error('DART 기업 검색 오류:', error) + throw error + } } export interface FinancialRiskItem { - description: string; - actualValue: string; - threshold: string; - notes: string | null; - itemNumber: number; - atRisk: boolean; + description: string + actualValue: string + threshold: string + notes: string | null + itemNumber: number + atRisk: boolean } export interface FinancialRiskAssessment { - partnerCompanyId: string; - partnerCompanyName: string; - assessmentYear: string; - reportCode: string; - riskItems: FinancialRiskItem[]; + partnerCompanyId: string + partnerCompanyName: string + assessmentYear: string + reportCode: string + riskItems: FinancialRiskItem[] } /** @@ -354,39 +306,41 @@ export interface FinancialRiskAssessment { * @returns 재무 위험 분석 결과 */ export async function fetchFinancialRiskAssessment( - corpCode: string, - partnerName?: string + corpCode: string, + partnerName?: string ): Promise { - try { - const url = new URL(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${corpCode}/financial-risk`); - - if (partnerName) { - url.searchParams.append('partnerName', partnerName); - } - - // 인증 토큰 및 기타 필요한 헤더 설정 (필요시) - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - if (token) { - // headers['Authorization'] = `Bearer ${token}`; // 이 API는 인증이 필요 없는 것으로 보임 - } - - const response = await fetch(url.toString(), { - method: 'GET', - headers: headers, - }); - - if (!response.ok) { - throw new Error(`재무 위험 정보를 가져오는데 실패했습니다: ${response.status}`); - } - - return await response.json(); - } catch (error) { - console.error('재무 위험 정보 조회 오류:', error); - throw error; - } + try { + const url = new URL( + `${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${corpCode}/financial-risk` + ) + + if (partnerName) { + url.searchParams.append('partnerName', partnerName) + } + + // 인증 토큰 및 기타 필요한 헤더 설정 (필요시) + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + if (token) { + // headers['Authorization'] = `Bearer ${token}`; // 이 API는 인증이 필요 없는 것으로 보임 + } + + const response = await fetch(url.toString(), { + method: 'GET', + headers: headers + }) + + if (!response.ok) { + throw new Error(`재무 위험 정보를 가져오는데 실패했습니다: ${response.status}`) + } + + return await response.json() + } catch (error) { + console.error('재무 위험 정보 조회 오류:', error) + throw error + } } /** @@ -394,32 +348,34 @@ export async function fetchFinancialRiskAssessment( * @returns 파트너사 이름 목록 */ export async function fetchUniquePartnerCompanyNames(): Promise { - try { - const url = new URL(UNIQUE_PARTNER_COMPANY_NAMES_ENDPOINT); - - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - const response = await fetch(url.toString(), { - method: 'GET', - headers - }); - - if (!response.ok) { - throw new Error(`파트너사 이름 목록을 가져오는 중 오류가 발생했습니다: ${response.status}`); - } - - const data = await response.json(); - return data.companyNames || []; - } catch (error) { - console.error('파트너사 이름 목록을 가져오는 중 오류:', error); - throw error; - } + try { + const url = new URL(UNIQUE_PARTNER_COMPANY_NAMES_ENDPOINT) + + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + if (token) { + headers.Authorization = `Bearer ${token}` + } + + const response = await fetch(url.toString(), { + method: 'GET', + headers + }) + + if (!response.ok) { + throw new Error( + `파트너사 이름 목록을 가져오는 중 오류가 발생했습니다: ${response.status}` + ) + } + + const data = await response.json() + return data.companyNames || [] + } catch (error) { + console.error('파트너사 이름 목록을 가져오는 중 오류:', error) + throw error + } } /** @@ -427,31 +383,35 @@ export async function fetchUniquePartnerCompanyNames(): Promise { * @param partnerId 파트너사 ID * @returns 파트너사 상세 정보 */ -export async function fetchPartnerCompanyDetail(partnerId: string): Promise { - try { - const url = new URL(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${partnerId}`); - - const token = useAuthStore.getState().accessToken; - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - const response = await fetch(url.toString(), { - method: 'GET', - headers - }); - - if (!response.ok) { - throw new Error(`파트너사 상세 정보를 가져오는 중 오류가 발생했습니다: ${response.status}`); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error('파트너사 상세 정보를 가져오는 중 오류:', error); - throw error; - } +export async function fetchPartnerCompanyDetail( + partnerId: string +): Promise { + try { + const url = new URL(`${API_BASE_URL}${PARTNER_COMPANIES_BASE_PATH}/${partnerId}`) + + const token = useAuthStore.getState().accessToken + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + if (token) { + headers.Authorization = `Bearer ${token}` + } + + const response = await fetch(url.toString(), { + method: 'GET', + headers + }) + + if (!response.ok) { + throw new Error( + `파트너사 상세 정보를 가져오는 중 오류가 발생했습니다: ${response.status}` + ) + } + + const data = await response.json() + return data + } catch (error) { + console.error('파트너사 상세 정보를 가져오는 중 오류:', error) + throw error + } } diff --git a/src/types/IFRS/partnerCompany.ts b/src/types/IFRS/partnerCompany.ts new file mode 100644 index 0000000..6d96140 --- /dev/null +++ b/src/types/IFRS/partnerCompany.ts @@ -0,0 +1,60 @@ +/** + * 파트너사(협력사) 정보 타입 + */ +export interface PartnerCompany { + id?: string // 파트너사 고유 ID + status?: 'ACTIVE' | 'INACTIVE' | 'PENDING' // 파트너사 상태 + industry?: string // 산업군 + country?: string // 국가 + address?: string // 주소 + corp_code: string // DART corp_code + corp_name: string // 회사명 (API 응답 필드) + stock_code?: string // 주식 코드 + contract_start_date?: string // 계약 시작일 (API 응답 필드 - YYYY-MM-DD 문자열) + modify_date?: string // 수정일 + // 프론트엔드에서 사용할 필드 (API 응답을 변환하여 채움) + companyName: string + contractStartDate: Date +} + +/** + * 파트너사 목록 API 응답 타입 (페이지네이션 포함) + */ +export interface PartnerCompanyResponse { + data: PartnerCompany[] + total: number + page: number + pageSize: number +} + +/** + * DART API 기업 정보 타입 + */ +export interface DartCorpInfo { + corp_code: string // 기업 고유 코드 + corp_name: string // 기업명 + stock_code?: string // 주식 코드 (상장사만 존재) + modify_date: string // 최종 수정일 +} + +/** + * DART API 페이지네이션 응답 타입 + */ +export interface DartApiResponse { + data: DartCorpInfo[] // 기업 정보 목록 + total: number // 전체 항목 수 + page: number // 현재 페이지 번호 + pageSize: number // 페이지 당 항목 수 + totalPages: number // 전체 페이지 수 + hasNextPage: boolean // 다음 페이지 존재 여부 +} + +/** + * 기업 검색 파라미터 타입 + */ +export interface SearchCorpParams { + page?: number + pageSize?: number + listedOnly?: boolean + corpNameFilter?: string +} From 7c239a6cb324d2c16419c18e02165882c12873c0 Mon Sep 17 00:00:00 2001 From: donghwan Date: Sun, 1 Jun 2025 18:55:45 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../financialRisk/financialRiskForm.tsx | 6 +- .../(partnerCompany)/managePartner/page.tsx | 587 +++++++++++------- src/services/partnerCompany.ts | 300 +++------ 3 files changed, 439 insertions(+), 454 deletions(-) diff --git a/src/app/(dashboard)/(partnerCompany)/financialRisk/financialRiskForm.tsx b/src/app/(dashboard)/(partnerCompany)/financialRisk/financialRiskForm.tsx index 3f1870f..b77b7e8 100644 --- a/src/app/(dashboard)/(partnerCompany)/financialRisk/financialRiskForm.tsx +++ b/src/app/(dashboard)/(partnerCompany)/financialRisk/financialRiskForm.tsx @@ -272,7 +272,7 @@ export default function FinancialRiskForm() { } + icon={} title="협력사 재무 위험 분석" description="사의 재무 건전성과 위험을 분석합니다." module="CSDD" @@ -302,8 +302,8 @@ export default function FinancialRiskForm() { {riskData && ( -
-
+
+
파트너사 diff --git a/src/app/(dashboard)/(partnerCompany)/managePartner/page.tsx b/src/app/(dashboard)/(partnerCompany)/managePartner/page.tsx index 1f472fd..498c362 100644 --- a/src/app/(dashboard)/(partnerCompany)/managePartner/page.tsx +++ b/src/app/(dashboard)/(partnerCompany)/managePartner/page.tsx @@ -1,71 +1,67 @@ 'use client' -import { useState, useEffect, useCallback } from 'react' -import { PageHeader } from '@/components/layout/PageHeader' -import { - Building2, - Plus, - Search, - X, - Loader2, - AlertTriangle, - Check, - MoreHorizontal, - Trash, +import {useState, useEffect, useCallback} from 'react' +import {PageHeader} from '@/components/layout/PageHeader' +import { + Building2, + Plus, + Search, + X, + Loader2, + AlertTriangle, + Check, + MoreHorizontal, + Trash, Edit3, Home, ChevronRight, Users } from 'lucide-react' -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, DialogTitle, DialogTrigger, DialogClose } from '@/components/ui/dialog' -import { Badge } from '@/components/ui/badge' -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow +import {Badge} from '@/components/ui/badge' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow } from '@/components/ui/table' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger +import {Button} from '@/components/ui/button' +import {Input} from '@/components/ui/input' +import {Label} from '@/components/ui/label' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger } from '@/components/ui/dropdown-menu' -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' -import { useToast } from '@/hooks/use-toast' -import type { - PartnerCompany, - DartCorpInfo, - PartnerCompanyResponse -} from '@/services/partnerCompany' -import { - fetchPartnerCompanies, - createPartnerCompany, - deletePartnerCompany, - updatePartnerCompany, - searchCompaniesFromDart +import {useToast} from '@/hooks/use-toast' +import {DartCorpInfo, PartnerCompany} from '@/types/IFRS/partnerCompany' +import { + searchCompaniesFromDart, + createPartnerCompany, + deletePartnerCompany, + fetchPartnerCompanies, + updatePartnerCompany } from '@/services/partnerCompany' // 디바운스 훅 @@ -86,8 +82,8 @@ function useDebounce(value: T, delay: number): T { } export default function ManagePartnerPage() { - const { toast } = useToast() - + const {toast} = useToast() + // 상태 관리 (HEAD 기반, origin/develop 요소 일부 통합) const [partners, setPartners] = useState([]) const [isLoading, setIsLoading] = useState(false) // 전반적인 로딩 (목록, DART 검색 등) @@ -98,18 +94,20 @@ export default function ManagePartnerPage() { const [totalPages, setTotalPages] = useState(1) const [totalItems, setTotalItems] = useState(0) const [pageSize] = useState(10) - + // 다이얼로그 상태 (HEAD 기반) const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) // 수정 다이얼로그 (HEAD) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [selectedPartner, setSelectedPartner] = useState(null) - + // 추가/수정 다이얼로그 내 상태 (HEAD 기반, origin/develop 요소 통합) const [companySearchQuery, setCompanySearchQuery] = useState('') // DART 검색어 (HEAD) const [dartSearchResults, setDartSearchResults] = useState([]) // DART 검색 결과 (HEAD, 이름 변경: searchResults -> dartSearchResults) - const [selectedDartCompany, setSelectedDartCompany] = useState(null) // DART에서 선택된 회사 (origin/develop: selectedCompany) - + const [selectedDartCompany, setSelectedDartCompany] = useState( + null + ) // DART에서 선택된 회사 (origin/develop: selectedCompany) + // 입력 폼 데이터 (HEAD: newPartnerData, origin/develop: newPartnerData 와 유사) const [formData, setFormData] = useState({ id: '', // 수정 시 사용 @@ -124,44 +122,48 @@ export default function ManagePartnerPage() { // 디바운스된 검색어 const debouncedMainSearchQuery = useDebounce(searchQuery, 500) // 파트너 목록 검색 (HEAD) const debouncedDartSearchQuery = useDebounce(companySearchQuery, 500) // DART 검색 (HEAD) - + // 파트너사 목록 로드 (페이징 및 검색 지원) - HEAD 기준 - const loadPartners = useCallback(async (page: number, companyNameFilter?: string) => { - setIsLoading(true) - setIsPageLoading(false) // 실제 로딩 시작 시 페이지 로딩 상태 false - try { - const response = await fetchPartnerCompanies(page, pageSize, companyNameFilter) - // API 응답의 contract_start_date (string)를 프론트엔드 PartnerCompany 타입의 contractStartDate (Date)로 변환 - const transformedData = response.data.map(p => ({ - ...p, - companyName: p.corp_name, // API 응답 corp_name을 companyName으로 매핑 - // contract_start_date가 있고 유효한 문자열일 때만 Date 객체로 변환 - contractStartDate: p.contract_start_date && typeof p.contract_start_date === 'string' - ? new Date(p.contract_start_date) - : new Date() // 혹은 적절한 기본값 또는 null 처리 - })); - setPartners(transformedData) - setCurrentPage(response.page) - setTotalPages(Math.ceil(response.total / response.pageSize)) - setTotalItems(response.total) - } catch (error) { - console.error('Failed to load partner companies:', error) - toast({ - variant: 'destructive', - title: '오류', - description: '파트너사 목록을 불러오는데 실패했습니다.', - }) - } finally { - setIsLoading(false) - } - }, [pageSize, toast]) - + const loadPartners = useCallback( + async (page: number, companyNameFilter?: string) => { + setIsLoading(true) + setIsPageLoading(false) // 실제 로딩 시작 시 페이지 로딩 상태 false + try { + const response = await fetchPartnerCompanies(page, pageSize, companyNameFilter) + // API 응답의 contract_start_date (string)를 프론트엔드 PartnerCompany 타입의 contractStartDate (Date)로 변환 + const transformedData = response.data.map(p => ({ + ...p, + companyName: p.corp_name, // API 응답 corp_name을 companyName으로 매핑 + // contract_start_date가 있고 유효한 문자열일 때만 Date 객체로 변환 + contractStartDate: + p.contract_start_date && typeof p.contract_start_date === 'string' + ? new Date(p.contract_start_date) + : new Date() // 혹은 적절한 기본값 또는 null 처리 + })) + setPartners(transformedData) + setCurrentPage(response.page) + setTotalPages(Math.ceil(response.total / response.pageSize)) + setTotalItems(response.total) + } catch (error) { + console.error('Failed to load partner companies:', error) + toast({ + variant: 'destructive', + title: '오류', + description: '파트너사 목록을 불러오는데 실패했습니다.' + }) + } finally { + setIsLoading(false) + } + }, + [pageSize, toast] + ) + // 페이지 첫 로드 시 파트너사 목록 조회 useEffect(() => { setIsPageLoading(true) loadPartners(1).finally(() => setIsPageLoading(false)) }, [loadPartners]) - + // 메인 검색어 변경 시 파트너사 목록 재조회 useEffect(() => { // isPageLoading이 true일때는 초기 로딩이므로, 검색어 변경으로 인한 재조회를 막습니다. @@ -169,7 +171,7 @@ export default function ManagePartnerPage() { loadPartners(1, debouncedMainSearchQuery || undefined) } }, [debouncedMainSearchQuery, loadPartners, isPageLoading]) - + // DART 기업 검색 (HEAD + origin/develop 통합) const searchDartCompanies = useCallback(async () => { if (!debouncedDartSearchQuery) { @@ -177,43 +179,56 @@ export default function ManagePartnerPage() { setDialogError(null) return } - + setIsLoading(true) // DART 검색 중 로딩 상태 setDialogError(null) setDartSearchResults([]) try { + console.log('DART 검색 요청 파라미터 (페이지):', { + page: 1, + pageSize: 5, + listedOnly: true, + corpNameFilter: debouncedDartSearchQuery + }) + const response = await searchCompaniesFromDart({ page: 1, // origin/develop 에서는 페이지 없었으나, API 스펙에 따라 추가/조정 가능 pageSize: 5, // origin/develop 에서는 10, HEAD 에서는 5. 일단 5로 통일 listedOnly: true, // HEAD 기준 corpNameFilter: debouncedDartSearchQuery }) - + + console.log('DART 검색 응답 전체:', response) + setDartSearchResults(response.data || []) // API 응답이 data 배열을 포함한다고 가정 + console.log('DART 검색 결과 설정:', response.data || []) + if ((response.data || []).length === 0) { + console.log('DART 검색 결과 없음') setDialogError(`'${debouncedDartSearchQuery}'에 대한 검색 결과가 없습니다.`) } } catch (error) { console.error('Failed to search companies from DART:', error) - const errorMessage = error instanceof Error ? error.message : 'DART 기업 검색 중 오류가 발생했습니다.' + const errorMessage = + error instanceof Error ? error.message : 'DART 기업 검색 중 오류가 발생했습니다.' setDialogError(errorMessage) toast({ variant: 'destructive', title: 'DART 검색 오류', - description: errorMessage, + description: errorMessage }) } finally { setIsLoading(false) } }, [debouncedDartSearchQuery, toast]) - + // DART 검색어 변경 시 또는 다이얼로그 열릴 때 DART 검색 (isAddDialogOpen || isEditDialogOpen) useEffect(() => { if (debouncedDartSearchQuery && (isAddDialogOpen || isEditDialogOpen)) { searchDartCompanies() } }, [debouncedDartSearchQuery, isAddDialogOpen, isEditDialogOpen, searchDartCompanies]) - + // DART 검색 결과에서 회사 선택 시 (HEAD + origin/develop) const handleSelectDartCompany = useCallback((company: DartCorpInfo) => { setSelectedDartCompany(company) // 선택된 DART 회사 정보 저장 @@ -228,7 +243,7 @@ export default function ManagePartnerPage() { setCompanySearchQuery('') // DART 검색창 비움 setDialogError(null) }, []) - + // 모달 상태 초기화 (HEAD + origin/develop) const resetModalStateAndClose = useCallback(() => { setFormData({ @@ -251,23 +266,29 @@ export default function ManagePartnerPage() { const handleSubmitPartner = useCallback(async () => { if (!formData.companyName || !formData.corpCode || !formData.contractStartDate) { setDialogError('회사명, DART코드, 계약 시작일은 필수입니다.') - toast({variant: 'destructive', title: '입력 오류', description: '회사명, DART코드, 계약 시작일은 필수입니다.'}) + toast({ + variant: 'destructive', + title: '입력 오류', + description: '회사명, DART코드, 계약 시작일은 필수입니다.' + }) return } setIsSubmitting(true) setDialogError(null) try { - if (isEditDialogOpen && selectedPartner?.id) { // 수정 모드 + if (isEditDialogOpen && selectedPartner?.id) { + // 수정 모드 await updatePartnerCompany(selectedPartner.id, { companyName: formData.companyName, corp_name: formData.companyName, - corp_code: formData.corpCode, + corp_code: formData.corpCode, contract_start_date: formData.contractStartDate, status: formData.status }) toast({title: '성공', description: '파트너사가 수정되었습니다.'}) - } else { // 추가 모드 + } else { + // 추가 모드 await createPartnerCompany({ companyName: formData.companyName, corpCode: formData.corpCode, @@ -276,16 +297,33 @@ export default function ManagePartnerPage() { toast({title: '성공', description: '파트너사가 추가되었습니다.'}) } resetModalStateAndClose() - await loadPartners(isEditDialogOpen ? currentPage : 1, debouncedMainSearchQuery || undefined) + await loadPartners( + isEditDialogOpen ? currentPage : 1, + debouncedMainSearchQuery || undefined + ) } catch (error) { console.error('Failed to save partner company:', error) - const errorMessage = error instanceof Error ? error.message : (isEditDialogOpen ? '파트너사 수정에 실패했습니다.' : '파트너사 추가에 실패했습니다.') + const errorMessage = + error instanceof Error + ? error.message + : isEditDialogOpen + ? '파트너사 수정에 실패했습니다.' + : '파트너사 추가에 실패했습니다.' setDialogError(errorMessage) - toast({ variant: 'destructive', title: '저장 오류', description: errorMessage }) + toast({variant: 'destructive', title: '저장 오류', description: errorMessage}) } finally { setIsSubmitting(false) } - }, [formData, toast, currentPage, debouncedMainSearchQuery, loadPartners, resetModalStateAndClose, isEditDialogOpen, selectedPartner]) + }, [ + formData, + toast, + currentPage, + debouncedMainSearchQuery, + loadPartners, + resetModalStateAndClose, + isEditDialogOpen, + selectedPartner + ]) // 파트너사 삭제 핸들러 (HEAD 기준) const handleDeletePartner = useCallback(async () => { @@ -305,38 +343,59 @@ export default function ManagePartnerPage() { await loadPartners(pageToLoad, debouncedMainSearchQuery || undefined) } catch (error) { console.error('Failed to delete partner company:', error) - toast({ variant: 'destructive', title: '삭제 오류', description: '파트너사 삭제에 실패했습니다.' }) + toast({ + variant: 'destructive', + title: '삭제 오류', + description: '파트너사 삭제에 실패했습니다.' + }) } finally { setIsSubmitting(false) } - }, [selectedPartner, currentPage, partners.length, debouncedMainSearchQuery, loadPartners, toast]) - + }, [ + selectedPartner, + currentPage, + partners.length, + debouncedMainSearchQuery, + loadPartners, + toast + ]) + // 페이지 이동 핸들러 (HEAD 기준) - const handlePageChange = useCallback((page: number) => { - if (page < 1 || page > totalPages || page === currentPage) return - loadPartners(page, debouncedMainSearchQuery || undefined) - }, [totalPages, currentPage, debouncedMainSearchQuery, loadPartners]) + const handlePageChange = useCallback( + (page: number) => { + if (page < 1 || page > totalPages || page === currentPage) return + loadPartners(page, debouncedMainSearchQuery || undefined) + }, + [totalPages, currentPage, debouncedMainSearchQuery, loadPartners] + ) // 수정 다이얼로그 열기 핸들러 const openEditDialog = (partner: PartnerCompany) => { setSelectedPartner(partner) - + // status 타입 처리: PartnerCompany['status'] 타입에 'ACTIVE', 'INACTIVE', 'PENDING'이 포함된다고 가정 - let finalStatus: PartnerCompany['status'] = 'ACTIVE'; // 기본값 - const validStatuses: Array = ['ACTIVE', 'INACTIVE', 'PENDING']; + let finalStatus: PartnerCompany['status'] = 'ACTIVE' // 기본값 + const validStatuses: Array = [ + 'ACTIVE', + 'INACTIVE', + 'PENDING' + ] if (partner.status && validStatuses.includes(partner.status)) { - finalStatus = partner.status; + finalStatus = partner.status } setFormData({ id: partner.id || '', - companyName: partner.corp_name || partner.companyName, + companyName: partner.corp_name || partner.companyName, corpCode: partner.corp_code, - contractStartDate: partner.contract_start_date ? partner.contract_start_date.split('T')[0] : new Date().toISOString().split('T')[0], + contractStartDate: partner.contract_start_date + ? partner.contract_start_date.split('T')[0] + : new Date().toISOString().split('T')[0], status: finalStatus }) - setCompanySearchQuery(partner.corp_name || partner.companyName) - setSelectedDartCompany({ // 선택된 DART 회사 정보도 설정 (수정 시 DART 재검색 불필요하게 안 하도록) + setCompanySearchQuery(partner.corp_name || partner.companyName) + setSelectedDartCompany({ + // 선택된 DART 회사 정보도 설정 (수정 시 DART 재검색 불필요하게 안 하도록) corp_code: partner.corp_code, corp_name: partner.corp_name || partner.companyName, stock_code: partner.stock_code, @@ -344,7 +403,7 @@ export default function ManagePartnerPage() { }) setIsEditDialogOpen(true) } - + // 추가 다이얼로그 열기 핸들러 const openAddDialog = () => { resetModalStateAndClose() // 이전 상태 초기화 @@ -352,10 +411,11 @@ export default function ManagePartnerPage() { } // 로딩 UI - if (isPageLoading && partners.length === 0) { // 초기 전체 페이지 로딩 + if (isPageLoading && partners.length === 0) { + // 초기 전체 페이지 로딩 return ( -
- +
+
) } @@ -370,20 +430,20 @@ export default function ManagePartnerPage() {
} + icon={} title="파트너사 관리" description="파트너사를 등록, 조회, 수정 및 삭제합니다." module="CSDD" /> - +
-
+
- + setSearchQuery(e.target.value)} + onChange={e => setSearchQuery(e.target.value)} className="pl-10 pr-10" /> {searchQuery && ( @@ -391,29 +451,32 @@ export default function ManagePartnerPage() { variant="ghost" size="icon" onClick={() => setSearchQuery('')} - className="absolute right-2 top-1/2 transform -translate-y-1/2 h-7 w-7" - > - + className="absolute transform -translate-y-1/2 right-2 top-1/2 h-7 w-7"> + )}
- +
- - { - if (!open) resetModalStateAndClose() - else if (isAddDialogOpen) setIsAddDialogOpen(true) - else if (isEditDialogOpen) setIsEditDialogOpen(true) - }}> + + { + if (!open) resetModalStateAndClose() + else if (isAddDialogOpen) setIsAddDialogOpen(true) + else if (isEditDialogOpen) setIsEditDialogOpen(true) + }}> - {isEditDialogOpen ? '파트너사 수정' : '파트너사 추가'} + + {isEditDialogOpen ? '파트너사 수정' : '파트너사 추가'} + -
+
@@ -422,7 +485,7 @@ export default function ManagePartnerPage() { id="companySearch" placeholder="회사명 입력 후 검색" value={companySearchQuery} - onChange={(e) => setCompanySearchQuery(e.target.value)} + onChange={e => setCompanySearchQuery(e.target.value)} className="pl-9" disabled={isSubmitting} /> @@ -430,38 +493,47 @@ export default function ManagePartnerPage() { )}
- + {dartSearchResults.length > 0 && ( -
- {dartSearchResults.map((company) => ( +
+ {dartSearchResults.map(company => ( ))}
)} - {dialogError && dartSearchResults.length === 0 && companySearchQuery && !isLoading && ( -

- - {dialogError} -

- )} + {dialogError && + dartSearchResults.length === 0 && + companySearchQuery && + !isLoading && ( +

+ + {dialogError} +

+ )}
- + {/* 회사명, DART 코드, 계약 시작일 입력 필드는 추가 모드일 때는 숨기고, 수정 모드일 때만 보이도록 변경 */} {isEditDialogOpen && ( <> @@ -470,33 +542,37 @@ export default function ManagePartnerPage() { setFormData({...formData, companyName: e.target.value})} + onChange={e => + setFormData({...formData, companyName: e.target.value}) + } className="mt-1" placeholder="파트너사 회사명" disabled={isSubmitting} />
- +
setFormData({...formData, corpCode: e.target.value})} + onChange={e => setFormData({...formData, corpCode: e.target.value})} className="mt-1" placeholder="DART 고유번호 (8자리)" maxLength={8} disabled={isSubmitting} />
- +
setFormData({...formData, contractStartDate: e.target.value})} + onChange={e => + setFormData({...formData, contractStartDate: e.target.value}) + } className="mt-1" disabled={isSubmitting} /> @@ -507,20 +583,23 @@ export default function ManagePartnerPage() { {isEditDialogOpen && (
-