diff --git a/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts b/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts index 61a11c8a8..d1e777242 100644 --- a/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts +++ b/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts @@ -43,7 +43,7 @@ export const codeMapper = ({ layerName, data }: CodeMapperProps): any => { // usg_cd 값 변환 (용도코드) if (processedData.usg_cd) { const usageCodeItem = MS_SWAGE_MHL_USE_CODES.find( - code => code.val === processedData.usg_cd.trim() + code => code.val.trim() === processedData.usg_cd.trim() ); if (usageCodeItem) { processedData.usg_cd = usageCodeItem.text; diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index 31f9d6dfc..92b0e3c4a 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -399,11 +399,11 @@ export const MsSwageMhlCol2 = [ // { id: 'mngInstCd', title: '관리기관코드' }, { id: 'instlYmd', title: '설치일자' }, // { id: 'lastDrdgnYmd', title: '최종준설일자' }, - // { id: 'usgCd', title: '용도코드' }, + { id: 'usgCd', title: '용도' }, // { id: 'fcltyShpCd', title: '시설물형태코드' }, // { id: 'mhlKndCd', title: '맨홀종류코드' }, { id: 'swageMhlDiamVl', title: '하수맨홀구경(mm)' }, - { id: 'mqltCd', title: '재질코드' }, + // { id: 'mqltCd', title: '재질코드' }, { id: 'wdthLen', title: '가로길이(m)' }, { id: 'vrtcLen', title: '세로길이(m)' }, // { id: 'angDir', title: '표고' }, @@ -448,31 +448,31 @@ export const MS_SWAGE_MHL_CODES = [ ]; // 하수맨홀 용도 코드표 export const MS_SWAGE_MHL_USE_CODES = [ - { val: "SMU000", text: "미분류" }, - { val: "SMU001", text: "우수" }, - { val: "SMU002", text: "오수" }, - { val: "SMU003", text: "집수" }, - { val: "SMU004", text: "합류" }, - { val: "SMU005", text: "차집" }, - { val: "SMU999", text: "기타" }, - { val: "SMU900", text: "1호" }, - { val: "SMU901", text: "2호" }, - { val: "SMU902", text: "3호" }, - { val: "SMU903", text: "4호" }, - { val: "SMU904", text: "5호" }, - { val: "SMU905", text: "6호" }, - { val: "SMU906", text: "7호" }, - { val: "SMU907", text: "8호" }, - { val: "SMU908", text: "구거용맨홀" }, - { val: "SMU909", text: "부관부착맨홀" }, - { val: "SMU910", text: "조립식맨홀" }, - { val: "SMU911", text: "특1호" }, - { val: "SMU912", text: "특2호" }, - { val: "SMU913", text: "특3호" }, - { val: "SMU914", text: "특4호" }, - { val: "SMU915", text: "특5호" }, - { val: "SMU916", text: "하수맨홀용도" }, - { val: "SMU917", text: "현장타설관거용" } + { val: "SMU000 ", text: "미분류" }, + { val: "SMU001 ", text: "우수" }, + { val: "SMU002 ", text: "오수" }, + { val: "SMU003 ", text: "집수" }, + { val: "SMU004 ", text: "합류" }, + { val: "SMU005 ", text: "차집" }, + { val: "SMU999 ", text: "기타" }, + { val: "SMU900 ", text: "1호" }, + { val: "SMU901 ", text: "2호" }, + { val: "SMU902 ", text: "3호" }, + { val: "SMU903 ", text: "4호" }, + { val: "SMU904 ", text: "5호" }, + { val: "SMU905 ", text: "6호" }, + { val: "SMU906 ", text: "7호" }, + { val: "SMU907 ", text: "8호" }, + { val: "SMU908 ", text: "구거용맨홀" }, + { val: "SMU909 ", text: "부관부착맨홀" }, + { val: "SMU910 ", text: "조립식맨홀" }, + { val: "SMU911 ", text: "특1호" }, + { val: "SMU912 ", text: "특2호" }, + { val: "SMU913 ", text: "특3호" }, + { val: "SMU914 ", text: "특4호" }, + { val: "SMU915 ", text: "특5호" }, + { val: "SMU916 ", text: "하수맨홀용도" }, + { val: "SMU917 ", text: "현장타설관거용" } ]; // 우수펌프장 diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx index 651ee05b6..5dcde5f5e 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react' const exclnInputColumn = [ { id: "swtrmtZoneNm", title: "배수구역", type: "input" }, { id: "mltvDtdvNm", title: "배수분구", type: "input" }, - { id: "designFrqYr", title: "설계빈도(년)", type: "input" }, + { id: "designFrqYr", title: "설계빈도(년)", type: "input", placeholder: "예: 10", isNumber: true, length: 4 }, ] // 오수펌프장 자체 입력 정보 const drtwInputColumn = [ diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index 446632965..1068bb2e5 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -139,7 +139,7 @@ const OperationTab: React.FC = ({ className="w-60 mr-8" selectClass="rounded-lg bg-white text-xl" label="" - selectArray={BIZ_TY_CD_SELECT_ARRAY} + selectArray={[{ val: "0", text: "전체" }, ...BIZ_TY_CD_SELECT_ARRAY]} value={var2} onSelectValue={(val) => { setVar2(String(val)); diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index 99d1741dd..ce495aa92 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -3,7 +3,7 @@ import { UiModal, UITab, UiTable } from '@common/business_components/ui'; import axios from 'axios'; import React, { useEffect, useMemo, useState } from 'react'; import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table'; -import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES } from '../components/common/table'; +import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES } from '../components/common/table'; import { getData } from "@/components/lib/api"; import BasicTab, { BASIC_TYPES } from './basicTab'; import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab'; @@ -214,6 +214,7 @@ const SearchModal: React.FC = ({ } const matchedMhlCode = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim()); + const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd); const ctpvNm = ctpv.find(code => code.val.trim() === ctpvCd)?.text || ctpvCd; const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || '-'; const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); @@ -222,15 +223,16 @@ const SearchModal: React.FC = ({ return { ...formattedItem, rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 - scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn, // scsYn이 null이면 'N'으로 변경 - mqltCd: matchedMhlCode ? matchedMhlCode.text : item.mqltCd, - usgCd: usgCdObj ? usgCdObj.text : item.usgCd, + scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item?.scsYn, // scsYn이 null이면 'N'으로 변경 + mqltCd: matchedMhlCode ? matchedMhlCode.text : item?.mqltCd, + usgCd: matchedMhlUsgCode ? matchedMhlUsgCode.text : (usgCdObj ? usgCdObj.text : item?.usgCd), ctpvNm, sggNm, swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-', }; }); + console.log("dataWithRowNumber: ", dataWithRowNumber) setTableData(dataWithRowNumber); setTableCount(res.data.count || []); @@ -286,7 +288,7 @@ const SearchModal: React.FC = ({ setTableData([]); } finally { setIsLoading(false); // 로딩 종료 - // setIsResetSelection(false); + setIsResetSelection(false); // setIsResetDetailSelection(false); } }; @@ -295,7 +297,7 @@ const SearchModal: React.FC = ({ const pplCqlFilters = () => { const cqlFilters: string[] = []; // 추후 작성 예정 - if (var1) cqlFilters.push(`usg_cd='${var1}'`); + if (var1 !== '0') cqlFilters.push(`usg_cd='${var1}'`); if (var2) cqlFilters.push(`mqlt_cd='${var2}'`); if (ctpvCd !== '0' && sggCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${sggCd}%'`); else if (ctpvCd !== '0') cqlFilters.push(`pbadms_emd_cd LIKE '${ctpvCd.trim()}%'`); @@ -394,7 +396,7 @@ const SearchModal: React.FC = ({ cqlFilters.push( `instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`, ); - if (var1) cqlFilters.push(`usg_cd='${var1}'`); + if (var1 !== '0') cqlFilters.push(`usg_cd='${var1}'`); // 구경(mm) 범위 if (start1 && end1) cqlFilters.push(`swage_mhL_diam_vl BETWEEN ${start1} AND ${end1}`); else if (start1) cqlFilters.push(`swage_mhL_diam_vl >= ${start1}`); @@ -482,7 +484,7 @@ const SearchModal: React.FC = ({ const layerNm = row.lyrTypeNm; const fid = row.fid; const core = GisApp.Module.layer as any; - const r = core.showSearchResultsFromGeoJSON(layerNm, fid); + core.showSearchResultsFromGeoJSON(layerNm, fid); onOpenChange(false); // SearchModal 닫기 } }; diff --git a/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx b/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx index 6face5b38..721bbb40b 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swageMhl.tsx @@ -72,7 +72,7 @@ const SwageMhl: React.FC = ({ className="w-60 mr-8" selectClass="rounded-lg bg-white text-xl" label="" - selectArray={MS_SWAGE_MHL_USE_CODES} + selectArray={[{ val: "0", text: "전체" }, ...MS_SWAGE_MHL_USE_CODES]} value={var1} onSelectValue={(val) => { setVar1(String(val)); diff --git a/apps/sn/app/(map-layout)/map/searchModal/swagePpl.tsx b/apps/sn/app/(map-layout)/map/searchModal/swagePpl.tsx index ee599887a..e501a9153 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/swagePpl.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/swagePpl.tsx @@ -83,7 +83,7 @@ const SwagePpl: React.FC = ({ className="w-32 mr-8" selectClass="rounded-lg bg-white text-xl" label="" - selectArray={MS_SWAGE_PPL_USE_CODES} + selectArray={[{val: "0", text: "전체"}, ...MS_SWAGE_PPL_USE_CODES]} value={var1} onSelectValue={(val) => { setVar1(String(val)); diff --git a/apps/sn/app/components/ui/YearRangeSelectBox.tsx b/apps/sn/app/components/ui/YearRangeSelectBox.tsx index 83b21cba5..8450a2b60 100644 --- a/apps/sn/app/components/ui/YearRangeSelectBox.tsx +++ b/apps/sn/app/components/ui/YearRangeSelectBox.tsx @@ -72,7 +72,7 @@ export function YearRangeSelectBox({ (maxYear - i).toString() ).filter(y => !end || parseInt(y) <= parseInt(end)); return [ - { val: "0", text: "년" }, + { val: "0", text: "전체" }, ...years.map(y => ({ val: y, text: y })), ]; }; @@ -82,7 +82,7 @@ export function YearRangeSelectBox({ (maxYear - i).toString() ).filter(y => !start || parseInt(y) >= parseInt(start)); return [ - { val: "9999", text: "년" }, + { val: "9999", text: "전체" }, ...years.map(y => ({ val: y, text: y })), ]; }; diff --git a/apps/sn/app/components/ui/verticalTable.tsx b/apps/sn/app/components/ui/verticalTable.tsx index ee13405fb..14c61bec8 100644 --- a/apps/sn/app/components/ui/verticalTable.tsx +++ b/apps/sn/app/components/ui/verticalTable.tsx @@ -1,6 +1,8 @@ import UiPageNations from "@common/business_components/ui/table/uiPageNations"; import { Table, TableBody, TableCell, TableRow, Button } from "@common/components"; import { useEffect, useState } from "react"; +import { confirmToast, showConfirmToast } from '@/components/ui/toast' +import { toast, Toaster } from "react-hot-toast"; type UiTableProps = { headName: React.ReactNode | undefined; @@ -38,6 +40,9 @@ type UiHeadProps = { align?: string; type?: string; options?: string[]; + placeholder?: string; + isNumber?: boolean; + length?: number; }; export function VerticalTable({ @@ -62,6 +67,8 @@ export function VerticalTable({ }: UiTableProps) { const [cur, setCur] = useState(1); const [formData, setFormData] = useState<{ [key: string]: string | number | boolean }>({}); + const [errors, setErrors] = useState<{ [key: string]: string }>({}); + useEffect(() => { if (resetPageNo) { setCur(1); @@ -71,32 +78,44 @@ export function VerticalTable({ useEffect(() => { if (tableData.length > 0 && tableData[0]) { - // 깊은 복사를 통해 새로운 객체 생성 const newData = JSON.parse(JSON.stringify(tableData[0])); setFormData(newData); - console.log("Form data initialized:", newData); // 디버깅용 + setErrors({}); } }, [tableData]); const handleInputChange = (id: string, value: string | number | boolean) => { - console.log(`Input change: ${id} = ${value}`); // 디버깅용 const newFormData = { ...formData, [id]: value, }; - console.log("New form data:", newFormData); // 디버깅용 setFormData(newFormData); - // 폼 데이터가 변경될 때마다 부모에게 알림 + if (errors[id]) { + setErrors(prev => { + const newErrors = { ...prev }; + delete newErrors[id]; + return newErrors; + }); + } + if (onFormDataChange) { onFormDataChange(newFormData); } }; + const handlePageChange = (page: number) => { setCur(page); onPageChange(page); }; + const showError = (fieldId: string, message: string) => { + setErrors(prev => ({ + ...prev, + [fieldId]: message + })); + }; + return ( <> {headName ? ( @@ -141,13 +160,55 @@ export function VerticalTable({ ))} ) : head.type === "input" ? ( - handleInputChange(head.id, e.target.value)} - className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1" - /> +
+ { + if (head.isNumber) { + const newValue = e.target.value; + + if (newValue === "") { + handleInputChange(head.id, newValue); + return; + } + + const isValidNumber = /^[0-9]+$/.test(newValue); + if (!isValidNumber) { + showError(head.id, `${head.title}는 숫자만 입력 가능합니다.`); + return; + } + + if (head.length && newValue.length > head.length) { + showError(head.id, `${head.title}는 최대 ${head.length}자까지 입력 가능합니다.`); + return; + } + + const numericValue = parseInt(newValue); + if (isNaN(numericValue) || numericValue < 0) return; + + handleInputChange(head.id, numericValue); + } else { + const newValue = e.target.value; + + if (head.length && newValue.length > head.length) { + showError(head.id, `${head.title}는 최대 ${head.length}자까지 입력 가능합니다.`); + return; + } + + handleInputChange(head.id, e.target.value); + } + }} + className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1" + placeholder={head.placeholder} + /> + {errors[head.id] && ( +
+ {errors[head.id]} +
+ )} +
) : ( {formData[head.id] ?? "-"} ) @@ -188,13 +249,55 @@ export function VerticalTable({ ))} ) : head.type === "input" ? ( - handleInputChange(head.id, e.target.value)} - className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1" - /> +
+ { + if (head.isNumber) { + const newValue = e.target.value; + + if (newValue === "") { + handleInputChange(head.id, newValue); + return; + } + + const isValidNumber = /^[0-9]+$/.test(newValue); + if (!isValidNumber) { + showError(head.id, `${head.title}는 숫자만 입력 가능합니다.`); + return; + } + + if (head.length && newValue.length > head.length) { + showError(head.id, `${head.title}는 최대 ${head.length}자까지 입력 가능합니다.`); + return; + } + + const numericValue = parseInt(newValue); + if (isNaN(numericValue) || numericValue < 0) return; + + handleInputChange(head.id, numericValue); + } else { + const newValue = e.target.value; + + if (head.length && newValue.length > head.length) { + showError(head.id, `${head.title}는 최대 ${head.length}자까지 입력 가능합니다.`); + return; + } + + handleInputChange(head.id, e.target.value); + } + }} + className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1" + placeholder={head.placeholder} + /> + {errors[head.id] && ( +
+ {errors[head.id]} +
+ )} +
) : ( {formData[head.id] ?? "-"} ) @@ -216,6 +319,7 @@ export function VerticalTable({ /> )} + ); } \ No newline at end of file diff --git a/apps/sn/app/store/index.ts b/apps/sn/app/store/index.ts index de827c9a1..e27d2922f 100644 --- a/apps/sn/app/store/index.ts +++ b/apps/sn/app/store/index.ts @@ -21,7 +21,7 @@ export const fetchCtpv = async (set: (state: any) => void) => { .sort((a, b) => a.text.localeCompare(b.text, 'ko')); console.log('시도 데이터', list); - set({ ctpv: list}); + set({ ctpv: list }); }; export const fetchSgg = async (set: (state: any) => void) => { @@ -52,7 +52,10 @@ export const fetchBIZ_SE_CD = async (set: (state: any) => void) => { export const fetchBIZ_TY_CD = async (set: (state: any) => void) => { try { const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`); - set({ BIZ_TY_CD: res }); + const filtered: BIZ_CD_TYPE[] = Array.isArray(res) + ? (res as BIZ_CD_TYPE[]).filter(item => item?.lwpoCd !== "AQAAA") + : []; + set({ BIZ_TY_CD: filtered }); } catch (e: any) { // alert("BIZ_TY_CD 데이터 로드 실패: " + e.message); console.error("BIZ_TY_CD 데이터 로드 실패:", e);