- HNS_SUBSTANCE 테이블 마이그레이션 SQL 추가 (002_hns_substance.sql) - HNS 검색/상세 API 구현 (hnsRouter, hnsService) - HNS 시드 스크립트 추가 (seedHns.ts, 20종 물질 데이터) - 프론트엔드 HNSSubstanceView: 정적 HNS_SEARCH_DB → API 호출 전환 - HNSSearchSubstance 타입 common/types/hns.ts로 분리 - Mock 데이터 이동: data/ → common/mock/ (vesselMockData, backtrackMockData) - layerDatabase.ts → common/services/layerService.ts 이동 - layerData.ts → common/data/layerData.ts 이동 - scat/index.ts 누락 수정 + .gitignore scat 규칙 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
import { wingPool } from '../db/wingDb.js'
|
|
|
|
interface HnsSearchParams {
|
|
q?: string
|
|
type?: 'abbreviation' | 'nameKr' | 'nameEn' | 'casNumber' | 'unNumber' | 'cargoCode'
|
|
sebc?: string
|
|
page?: number
|
|
limit?: number
|
|
}
|
|
|
|
export async function searchSubstances(params: HnsSearchParams) {
|
|
const { q, type = 'nameKr', sebc, page = 1, limit = 50 } = params
|
|
const conditions: string[] = ["USE_YN = 'Y'"]
|
|
const values: (string | number)[] = []
|
|
let paramIdx = 1
|
|
|
|
if (q && q.trim()) {
|
|
const keyword = q.trim()
|
|
switch (type) {
|
|
case 'abbreviation':
|
|
conditions.push(`ABBREVIATION ILIKE $${paramIdx}`)
|
|
values.push(`%${keyword}%`)
|
|
break
|
|
case 'nameKr':
|
|
conditions.push(`NM_KR ILIKE $${paramIdx}`)
|
|
values.push(`%${keyword}%`)
|
|
break
|
|
case 'nameEn':
|
|
conditions.push(`NM_EN ILIKE $${paramIdx}`)
|
|
values.push(`%${keyword}%`)
|
|
break
|
|
case 'casNumber':
|
|
conditions.push(`CAS_NO ILIKE $${paramIdx}`)
|
|
values.push(`%${keyword}%`)
|
|
break
|
|
case 'unNumber':
|
|
conditions.push(`UN_NO = $${paramIdx}`)
|
|
values.push(keyword)
|
|
break
|
|
case 'cargoCode':
|
|
conditions.push(`DATA->'cargoCodes' @> $${paramIdx}::jsonb`)
|
|
values.push(JSON.stringify([{ code: keyword }]))
|
|
break
|
|
default:
|
|
conditions.push(`(NM_KR ILIKE $${paramIdx} OR NM_EN ILIKE $${paramIdx} OR ABBREVIATION ILIKE $${paramIdx})`)
|
|
values.push(`%${keyword}%`)
|
|
}
|
|
paramIdx++
|
|
}
|
|
|
|
if (sebc && sebc.trim()) {
|
|
conditions.push(`SEBC ILIKE $${paramIdx}`)
|
|
values.push(`%${sebc.trim()}%`)
|
|
paramIdx++
|
|
}
|
|
|
|
const where = conditions.join(' AND ')
|
|
const offset = (page - 1) * limit
|
|
|
|
const countQuery = `SELECT COUNT(*) as total FROM HNS_SUBSTANCE WHERE ${where}`
|
|
const dataQuery = `
|
|
SELECT SBST_SN, ABBREVIATION, NM_KR, NM_EN, UN_NO, CAS_NO, SEBC, DATA
|
|
FROM HNS_SUBSTANCE
|
|
WHERE ${where}
|
|
ORDER BY SBST_SN
|
|
LIMIT $${paramIdx} OFFSET $${paramIdx + 1}
|
|
`
|
|
|
|
const [countResult, dataResult] = await Promise.all([
|
|
wingPool.query(countQuery, values),
|
|
wingPool.query(dataQuery, [...values, limit, offset]),
|
|
])
|
|
|
|
return {
|
|
total: parseInt(countResult.rows[0].total, 10),
|
|
page,
|
|
limit,
|
|
items: dataResult.rows.map(row => ({
|
|
id: row.sbst_sn,
|
|
abbreviation: row.abbreviation,
|
|
nameKr: row.nm_kr,
|
|
nameEn: row.nm_en,
|
|
unNumber: row.un_no,
|
|
casNumber: row.cas_no,
|
|
sebc: row.sebc,
|
|
...row.data,
|
|
})),
|
|
}
|
|
}
|
|
|
|
export async function getSubstanceById(id: number) {
|
|
const { rows } = await wingPool.query(
|
|
`SELECT SBST_SN, ABBREVIATION, NM_KR, NM_EN, UN_NO, CAS_NO, SEBC, DATA
|
|
FROM HNS_SUBSTANCE WHERE SBST_SN = $1`,
|
|
[id]
|
|
)
|
|
if (rows.length === 0) return null
|
|
|
|
const row = rows[0]
|
|
return {
|
|
id: row.sbst_sn,
|
|
abbreviation: row.abbreviation,
|
|
nameKr: row.nm_kr,
|
|
nameEn: row.nm_en,
|
|
unNumber: row.un_no,
|
|
casNumber: row.cas_no,
|
|
sebc: row.sebc,
|
|
...row.data,
|
|
}
|
|
}
|