SCAT 좌측패널 리팩토링, 해안조사 뷰 기능 보강, 기상 우측패널 중복 코드 제거 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
272 lines
7.9 KiB
TypeScript
272 lines
7.9 KiB
TypeScript
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<string, unknown> | 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<string[]> {
|
|
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<string, unknown>) => r.office_cd as string);
|
|
}
|
|
|
|
// ============================================================
|
|
// 관할서 목록 조회
|
|
// ============================================================
|
|
|
|
export async function listJurisdictions(officeCd?: string): Promise<string[]> {
|
|
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<string, unknown>) => r.jrsd_nm as string);
|
|
}
|
|
|
|
// ============================================================
|
|
// 조사구역 목록 조회
|
|
// ============================================================
|
|
|
|
export async function listZones(filters?: {
|
|
jurisdiction?: string;
|
|
officeCd?: string;
|
|
}): Promise<ZoneItem[]> {
|
|
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<string, unknown>) => ({
|
|
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<SectionListItem[]> {
|
|
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<string, unknown>) => ({
|
|
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<SectionDetail | null> {
|
|
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<string, unknown>;
|
|
|
|
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<string, unknown>) ?? 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[]) ?? [],
|
|
};
|
|
}
|