diff options
| -rw-r--r-- | lib/vendor-basic-info/sales-info-table.tsx | 179 | ||||
| -rw-r--r-- | lib/vendor-basic-info/use-credit-integration.ts | 3 |
2 files changed, 181 insertions, 1 deletions
diff --git a/lib/vendor-basic-info/sales-info-table.tsx b/lib/vendor-basic-info/sales-info-table.tsx new file mode 100644 index 00000000..4381e878 --- /dev/null +++ b/lib/vendor-basic-info/sales-info-table.tsx @@ -0,0 +1,179 @@ +"use client"; + +import React from "react"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; + +interface SalesInfoTableProps { + creditLoading: boolean; + creditError: string | null; + creditResults: any[]; + selectedCreditService: string; + bestResult: any; + getCurrentResult: () => any | null; + handleCreditServiceChange: (code: string) => void; + creditServices: { code: string; name: string }[]; +} + +export function SalesInfoTable({ + creditLoading, + creditError, + creditResults, + selectedCreditService, + bestResult, + getCurrentResult, + handleCreditServiceChange, + creditServices, +}: SalesInfoTableProps) { + // 'auto' 선택 시 bestResult 우선 사용, 수동 선택 시 해당 코드의 결과 사용 + const currentResult = React.useMemo(() => { + // 'auto' 선택 시에만 bestResult 사용 + if (selectedCreditService === "auto") { + return bestResult || getCurrentResult?.() || null; + } + // 수동 선택 시 해당 평가사 결과 + return creditResults.find((r) => r.code === selectedCreditService) || null; + }, [selectedCreditService, bestResult, creditResults, getCurrentResult]); + + const data = currentResult?.data || null; + + // 가용한 지표 인덱스 탐색: bs_dt{n} 키를 모두 수집해 인덱스 배열 생성 + const availableIndexes: number[] = React.useMemo(() => { + if (!data) return []; + // 0,1,2(3개년)만 표시. 해당 인덱스의 기준일 키가 존재하는 경우만 포함 + return [0, 1, 2].filter((n) => (data as any)[`BS_DT${n}`] || (data as any)[`bs_dt${n}`]); + }, [data]); + + // TR 계열(비율) 키는 연도 인덱스에 따라 0→3, 1→2, 2→1 매핑됨 + const mapIdxToSuffix = (idx: number): string | null => { + if (idx === 0) return "3"; + if (idx === 1) return "2"; + if (idx === 2) return "1"; + return null; // 3년 초과 시 해당 비율 데이터가 없을 수 있음 + }; + + const getField = (prefix: string, idx: number): string => { + // prefix는 소문자 기준(e.g., bs59_, pl27_), 대문자 키(e.g., BS59_)도 함께 조회 + const lowerKey = `${prefix}${idx}`; + const upperKey = `${prefix.toUpperCase()}${idx}`; + const v = (data && (data[lowerKey] ?? data[upperKey])) as unknown as string | number | undefined | null; + return v !== undefined && v !== null && v !== "" ? String(v) : "-"; + }; + + const getRatio = (baseKey: string, idx: number): string => { + const suffix = mapIdxToSuffix(idx); + if (!suffix) return "-"; + const key = `${baseKey}${suffix}`; + const v = data?.[key]; + return v && v !== "" ? String(v) : "-"; + }; + + return ( + <div className="space-y-4"> + <div className="p-4 border-b bg-muted/30"> + <div className="flex items-center justify-between mb-2"> + <span className="text-sm font-medium">신용평가사 데이터</span> + {creditLoading && ( + <div className="flex items-center gap-2 text-sm text-muted-foreground"> + <div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div> + 로딩 중... + </div> + )} + </div> + <div className="flex items-center gap-4"> + <Select value={selectedCreditService} onValueChange={handleCreditServiceChange}> + <SelectTrigger className="w-48"> + <SelectValue placeholder="신용평가사 선택" /> + </SelectTrigger> + <SelectContent> + {creditServices.map((service) => { + const result = creditResults.find((r) => r.code === service.code); + return ( + <SelectItem key={service.code} value={service.code}> + {service.name} + {service.code !== "auto" && result && ( + <span className="ml-2 text-xs text-muted-foreground"> + {result.success ? `(${result.dataCount}개 항목)` : "(조회 실패)"} + </span> + )} + </SelectItem> + ); + })} + </SelectContent> + </Select> + + {currentResult && ( + <div className="text-sm text-muted-foreground"> + 선택됨: <span className="font-medium">{currentResult?.name}</span> + {selectedCreditService === "auto" && bestResult && ( + <span className="ml-1">({bestResult.dataCount}개 항목으로 자동선택)</span> + )} + </div> + )} + + {creditError && <div className="text-sm text-destructive">{creditError}</div>} + + {!creditLoading && !creditError && creditResults.length > 0 && !currentResult?.data && ( + <div className="text-sm text-muted-foreground">신용평가 데이터가 없습니다</div> + )} + </div> + </div> + + <Table> + <TableHeader> + <TableRow> + <TableHead rowSpan={2} className="text-center border-r align-middle">기준일</TableHead> + <TableHead colSpan={3} className="text-center border-r">자산 구성</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">매출액<br />(백만원)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">영업이익<br />(백만원)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">당기순이익<br />(백만원)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">부채비율<br />(%)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">차입금의존도<br />(%)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">영업이익률<br />(%)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">순이익률<br />(%)</TableHead> + <TableHead rowSpan={2} className="text-center border-r align-middle">매출액증감<br />(%)</TableHead> + <TableHead rowSpan={2} className="text-center align-middle">유동비율<br />(%)</TableHead> + </TableRow> + <TableRow> + <TableHead className="text-center border-r">총자산</TableHead> + <TableHead className="text-center border-r">부채총계</TableHead> + <TableHead className="text-center border-r">자본총계</TableHead> + </TableRow> + </TableHeader> + <TableBody> + {availableIndexes.length === 0 && ( + <TableRow> + <TableCell colSpan={13} className="text-center text-sm text-muted-foreground">표시할 데이터가 없습니다</TableCell> + </TableRow> + )} + {availableIndexes.map((idx) => ( + <TableRow key={`row-${idx}`}> + <TableCell className="text-center font-medium border-r bg-yellow-50">{(data?.[`BS_DT${idx}`] || data?.[`bs_dt${idx}`] || "-") as any}</TableCell> + <TableCell className="text-right border-r">{getField("bs59_", idx)}</TableCell> + <TableCell className="text-right border-r">{getField("bs91_", idx)}</TableCell> + <TableCell className="text-right border-r">{getField("bs113_", idx)}</TableCell> + <TableCell className="text-right border-r">{getField("pl01_", idx)}</TableCell> + <TableCell className="text-right border-r">{getField("pl27_", idx)}</TableCell> + <TableCell className="text-right border-r">{getField("pl71_", idx)}</TableCell> + <TableCell className="text-right border-r">{getRatio("TR005", idx)}</TableCell> + <TableCell className="text-right border-r">{getRatio("TR051", idx)}</TableCell> + <TableCell className="text-right border-r">{getRatio("TR052", idx)}</TableCell> + <TableCell className="text-right border-r">{getRatio("TR010", idx)}</TableCell> + <TableCell className="text-right border-r">{getRatio("TR022", idx)}</TableCell> + <TableCell className="text-right">{getRatio("TR001", idx)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </div> + ); +} + + diff --git a/lib/vendor-basic-info/use-credit-integration.ts b/lib/vendor-basic-info/use-credit-integration.ts index 5cdfd04a..a39bddeb 100644 --- a/lib/vendor-basic-info/use-credit-integration.ts +++ b/lib/vendor-basic-info/use-credit-integration.ts @@ -150,7 +150,8 @@ export function useCreditIntegration(vendorId: string) { fieldsToCheck.forEach(field => { const value = data[field as keyof CreditData]; - if (value && value.toString().trim() !== '' && value.toString().trim() !== '0') { + // null/undefined 또는 공백이 아닌 값은 모두 유효로 간주 ("0" 포함) + if (value !== null && value !== undefined && value.toString().trim() !== '') { count++; } }); |
