diff --git a/deployment.yaml b/deployment.yaml new file mode 100644 index 0000000..0fde9de --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend-service +spec: + replicas: 1 + selector: + matchLabels: + app: frontend-service + template: + metadata: + labels: + app: frontend-service + spec: + tolerations: + - key: "eks.amazonaws.com/compute-type" + operator: "Equal" + value: "fargate" + effect: "NoSchedule" + containers: + - name: frontend-service + image: kdonghwan/frontend-service:latest + ports: + - containerPort: 3000 + env: + - name: PORT + value: "3000" + - name: HOSTNAME + value: "0.0.0.0" + # 게이트웨이 API URL - 클러스터 내부용 + - name: NEXT_PUBLIC_SPRING_API_URL + value: "http://gateway-service" # 빈 값으로 유지 - axios.ts의 로직으로 동적 URL 처리 + # auth 경로 설정 + - name: NEXT_PUBLIC_AUTH_PATH + value: "/auth" + # API 베이스 URL + - name: NEXT_PUBLIC_API_BASE_URL + value: "http://gateway-service" + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" 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 && (
-