import { wingPool } from '../db/wingDb.js'; // ============================================================ // 인터페이스 // ============================================================ interface ZoneItem { cstSrvyZoneSn: number; zoneCd: string; zoneNm: string; jrsdNm: string; sectCnt: number; latCenter: number; lngCenter: number; latRange: number; lngRange: number; } interface SectionListItem { cstSectSn: number; sectCd: string; sectNm: string; cstTpCd: string; esiCd: string; esiNum: number; lenM: number; snstvtCd: string; srvySttsCd: string; lat: number; lng: number; tags: string[]; zoneCd: string; zoneNm: string; jrsdNm: string; } interface SectionDetail { cstSectSn: number; sectCd: string; sectNm: string; cstTpCd: string; esiCd: string; esiNum: number; lenM: number; snstvtCd: string; srvySttsCd: string; lat: number; lng: number; tags: string[]; geomJson: Record | null; zoneCd: string; zoneNm: string; jrsdNm: string; shoreTp: string | null; accessDc: string | null; accessPt: string | null; sensitiveInfo: { t: string; v: string }[]; cleanupMethods: string[]; endCriteria: string[]; notes: string[]; } // ============================================================ // 관할청 목록 조회 // ============================================================ export async function listOffices(): Promise { const sql = ` SELECT DISTINCT OFFICE_CD FROM wing.CST_SRVY_ZONE WHERE USE_YN = 'Y' AND OFFICE_CD IS NOT NULL ORDER BY OFFICE_CD `; const { rows } = await wingPool.query(sql); return rows.map((r: Record) => r.office_cd as string); } // ============================================================ // 관할서 목록 조회 // ============================================================ export async function listJurisdictions(officeCd?: string): Promise { const conditions: string[] = ["USE_YN = 'Y'", 'JRSD_NM IS NOT NULL']; const params: unknown[] = []; let idx = 1; if (officeCd) { conditions.push(`OFFICE_CD = $${idx++}`); params.push(officeCd); } const sql = ` SELECT DISTINCT JRSD_NM FROM wing.CST_SRVY_ZONE WHERE ${conditions.join(' AND ')} ORDER BY JRSD_NM `; const { rows } = await wingPool.query(sql, params); return rows.map((r: Record) => r.jrsd_nm as string); } // ============================================================ // 조사구역 목록 조회 // ============================================================ export async function listZones(filters?: { jurisdiction?: string; officeCd?: string; }): Promise { const conditions: string[] = ["USE_YN = 'Y'"]; const params: unknown[] = []; let idx = 1; if (filters?.jurisdiction) { conditions.push(`JRSD_NM ILIKE '%' || $${idx++} || '%'`); params.push(filters.jurisdiction); } if (filters?.officeCd) { conditions.push(`OFFICE_CD = $${idx++}`); params.push(filters.officeCd); } const where = 'WHERE ' + conditions.join(' AND '); const sql = ` SELECT CST_SRVY_ZONE_SN, ZONE_CD, ZONE_NM, JRSD_NM, SECT_CNT, LAT_CENTER, LNG_CENTER, LAT_RANGE, LNG_RANGE FROM wing.CST_SRVY_ZONE ${where} ORDER BY CST_SRVY_ZONE_SN `; const { rows } = await wingPool.query(sql, params); return rows.map((r: Record) => ({ cstSrvyZoneSn: r.cst_srvy_zone_sn as number, zoneCd: r.zone_cd as string, zoneNm: r.zone_nm as string, jrsdNm: r.jrsd_nm as string, sectCnt: r.sect_cnt as number, latCenter: parseFloat(r.lat_center as string), lngCenter: parseFloat(r.lng_center as string), latRange: parseFloat(r.lat_range as string), lngRange: parseFloat(r.lng_range as string), })); } // ============================================================ // 해안구간 목록 조회 (필터링) // ============================================================ export async function listSections(filters: { zone?: string; status?: string; sensitivity?: string; jurisdiction?: string; search?: string; officeCd?: string; }): Promise { const conditions: string[] = []; const params: unknown[] = []; let idx = 1; if (filters.zone) { conditions.push(`z.ZONE_CD = $${idx++}`); params.push(filters.zone); } if (filters.status) { conditions.push(`s.SRVY_STTS_CD = $${idx++}`); params.push(filters.status); } if (filters.sensitivity) { conditions.push(`s.SNSTVT_CD = $${idx++}`); params.push(filters.sensitivity); } if (filters.jurisdiction) { conditions.push(`z.JRSD_NM ILIKE '%' || $${idx++} || '%'`); params.push(filters.jurisdiction); } if (filters.search) { conditions.push(`s.SECT_NM ILIKE '%' || $${idx++} || '%'`); params.push(filters.search); } if (filters.officeCd) { conditions.push(`z.OFFICE_CD = $${idx++}`); params.push(filters.officeCd); } conditions.push("s.USE_YN = 'Y'"); conditions.push("z.USE_YN = 'Y'"); const where = 'WHERE ' + conditions.join(' AND '); const sql = ` SELECT s.CST_SECT_SN, s.SECT_CD, s.SECT_NM, s.CST_TP_CD, s.ESI_CD, s.ESI_NUM, s.LEN_M, s.SNSTVT_CD, s.SRVY_STTS_CD, s.LAT, s.LNG, s.TAGS, z.ZONE_CD, z.ZONE_NM, z.JRSD_NM FROM wing.CST_SECT s JOIN wing.CST_SRVY_ZONE z ON z.CST_SRVY_ZONE_SN = s.CST_SRVY_ZONE_SN ${where} ORDER BY s.CST_SECT_SN `; const { rows } = await wingPool.query(sql, params); return rows.map((r: Record) => ({ cstSectSn: r.cst_sect_sn as number, sectCd: r.sect_cd as string, sectNm: r.sect_nm as string, cstTpCd: r.cst_tp_cd as string, esiCd: r.esi_cd as string, esiNum: r.esi_num as number, lenM: r.len_m as number, snstvtCd: r.snstvt_cd as string, srvySttsCd: r.srvy_stts_cd as string, lat: parseFloat(r.lat as string), lng: parseFloat(r.lng as string), tags: (r.tags as string[]) ?? [], zoneCd: r.zone_cd as string, zoneNm: r.zone_nm as string, jrsdNm: r.jrsd_nm as string, })); } // ============================================================ // 해안구간 단건 상세 조회 (JSONB 포함) // ============================================================ export async function getSection(sn: number): Promise { const sql = ` SELECT s.CST_SECT_SN, s.SECT_CD, s.SECT_NM, s.CST_TP_CD, s.ESI_CD, s.ESI_NUM, s.LEN_M, s.SNSTVT_CD, s.SRVY_STTS_CD, s.LAT, s.LNG, s.TAGS, ST_AsGeoJSON(s.GEOM)::jsonb AS geom_json, s.SHORE_TP, s.ACCESS_DC, s.ACCESS_PT, s.SENSITIVE_INFO, s.CLEANUP_METHODS, s.END_CRITERIA, s.NOTES, z.ZONE_CD, z.ZONE_NM, z.JRSD_NM FROM wing.CST_SECT s JOIN wing.CST_SRVY_ZONE z ON z.CST_SRVY_ZONE_SN = s.CST_SRVY_ZONE_SN WHERE s.CST_SECT_SN = $1 AND s.USE_YN = 'Y' `; const { rows } = await wingPool.query(sql, [sn]); if (rows.length === 0) return null; const r = rows[0] as Record; return { cstSectSn: r.cst_sect_sn as number, sectCd: r.sect_cd as string, sectNm: r.sect_nm as string, cstTpCd: r.cst_tp_cd as string, esiCd: r.esi_cd as string, esiNum: r.esi_num as number, lenM: r.len_m as number, snstvtCd: r.snstvt_cd as string, srvySttsCd: r.srvy_stts_cd as string, lat: parseFloat(r.lat as string), lng: parseFloat(r.lng as string), tags: (r.tags as string[]) ?? [], geomJson: (r.geom_json as Record) ?? null, zoneCd: r.zone_cd as string, zoneNm: r.zone_nm as string, jrsdNm: r.jrsd_nm as string, shoreTp: (r.shore_tp as string) ?? null, accessDc: (r.access_dc as string) ?? null, accessPt: (r.access_pt as string) ?? null, sensitiveInfo: (r.sensitive_info as { t: string; v: string }[]) ?? [], cleanupMethods: (r.cleanup_methods as string[]) ?? [], endCriteria: (r.end_criteria as string[]) ?? [], notes: (r.notes as string[]) ?? [], }; }