import { useState, useEffect, useMemo } from 'react'; interface BypassParam { paramName: string; paramType: string; paramIn: string; required: boolean; description: string; example: string; } interface BypassConfig { id: number; domainName: string; endpointName: string; displayName: string; httpMethod: string; externalPath: string; description: string; generated: boolean; createdAt: string; params: BypassParam[]; } interface ApiResponse { success: boolean; data: T; } type ViewMode = 'card' | 'table'; const METHOD_COLORS: Record = { GET: 'bg-emerald-100 text-emerald-700', POST: 'bg-blue-100 text-blue-700', PUT: 'bg-amber-100 text-amber-700', DELETE: 'bg-red-100 text-red-700', }; const SWAGGER_BASE = '/snp-api/swagger-ui/index.html?urls.primaryName=3.%20Bypass%20API'; function buildSwaggerDeepLink(config: BypassConfig): string { // Swagger UI deep link: #/{Tag}/{operationId} // Tag = domainName 첫글자 대문자 (예: compliance → Compliance) // operationId = get{EndpointName}Data (SpringDoc 기본 패턴) const tag = config.domainName.charAt(0).toUpperCase() + config.domainName.slice(1); const operationId = `get${config.endpointName}Data`; return `${SWAGGER_BASE}#/${tag}/${operationId}`; } export default function BypassCatalog() { const [configs, setConfigs] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [selectedDomain, setSelectedDomain] = useState(''); const [viewMode, setViewMode] = useState('table'); useEffect(() => { fetch('/snp-api/api/bypass-config') .then(res => res.json()) .then((res: ApiResponse) => setConfigs((res.data ?? []).filter(c => c.generated))) .catch(() => setConfigs([])) .finally(() => setLoading(false)); }, []); const domainNames = useMemo(() => { const names = [...new Set(configs.map((c) => c.domainName))]; return names.sort(); }, [configs]); const filtered = useMemo(() => { return configs.filter((c) => { const matchesSearch = !searchTerm.trim() || c.domainName.toLowerCase().includes(searchTerm.toLowerCase()) || c.displayName.toLowerCase().includes(searchTerm.toLowerCase()) || (c.description || '').toLowerCase().includes(searchTerm.toLowerCase()); const matchesDomain = !selectedDomain || c.domainName === selectedDomain; return matchesSearch && matchesDomain; }); }, [configs, searchTerm, selectedDomain]); if (loading) { return (
API 목록을 불러오는 중...
); } return (
{/* 헤더 */}

S&P Global API 카탈로그

S&P Global Maritime API 목록입니다. Swagger UI에서 직접 테스트할 수 있습니다.

Swagger UI
{/* 검색 + 필터 + 뷰 전환 */}
{/* 검색 */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-wing-border rounded-lg text-sm focus:ring-2 focus:ring-wing-accent focus:border-wing-accent outline-none bg-wing-surface text-wing-text" /> {searchTerm && ( )}
{/* 도메인 드롭다운 필터 */} {/* 뷰 전환 토글 */}
{(searchTerm || selectedDomain) && (

{filtered.length}개 API 검색됨

)}
{/* 빈 상태 */} {configs.length === 0 ? (

등록된 API가 없습니다.

관리자에게 문의해주세요.

) : filtered.length === 0 ? (

검색 결과가 없습니다.

다른 검색어를 사용해 보세요.

) : viewMode === 'card' ? ( /* 카드 뷰 */
{filtered.map((config) => (

{config.displayName}

{config.domainName}

{config.httpMethod}

{config.externalPath}

{config.description && (

{config.description}

)}
{config.params.length > 0 && (
Parameters
{config.params.map((p) => ( {p.paramName} {p.required && *} ))}
)}
))}
) : ( /* 테이블 뷰 */
{filtered.map((config) => ( ))}
도메인명 표시명 HTTP 외부 경로 파라미터 Swagger
{config.domainName} {config.displayName} {config.httpMethod} {config.externalPath}
{config.params.map((p) => ( {p.paramName} {p.required && *} ))}
테스트 →
)}
); }