diff --git a/packages/common/business_components/src/ui/chart/BarChart.tsx b/packages/common/business_components/src/ui/chart/BarChart.tsx index 0802a72e4..02d3a7c60 100644 --- a/packages/common/business_components/src/ui/chart/BarChart.tsx +++ b/packages/common/business_components/src/ui/chart/BarChart.tsx @@ -75,7 +75,7 @@ const CustomLegend = (props: any) => { }; export default function Chart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) { - const colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEEAD", "#D4A5A5"]; + const colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#FED16A", "#FFEEAD", "#D4A5A5"]; const getMaxValue = (data: any[], keyPrefix: string) => { return Math.max(...data.map(item => Object.keys(item) diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index cde836627..36304a02f 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -47,7 +47,7 @@ function Gis({ const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); const mapInstanceRef = useRef(null); const checkedLayerIds = useRef([]); - const [viewMode, setViewMode] = useState('list'); // 상태로 관리 + const layerNmRef = useRef('') // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); @@ -85,12 +85,32 @@ function Gis({ foundFeature = data; } else { // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 - const featureId = data.fid || data.ftrIdn || data.id; + let featureId + const layerName = layerNmRef.current + if (layerName === '하수재이용시설') { + featureId = data.searchPk; + } else if (layerName === '빗물이용시설') { + featureId = data.bdstInfoPk; + } else if (layerName === '하수처리시설') { + featureId = data.bscFctCd; + } else if (layerName === '중수도시설' || layerName == '온배수시설') { + featureId = data.bdstRuseFcltPk; + } else { + console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm); + featureId = data.fid || data.id || data.ftrIdn; + } gisMap.getLayers().getArray().forEach(layer => { const source = layer.getSource?.(); - if (source?.getFeatureById) { - const feature = source.getFeatureById(String(featureId).trim()); - if (feature) foundFeature = feature; + if (source?.getFeatures) { + const allFeatures = source.getFeatures(); + for (const f of allFeatures) { + const id = String(f.getId()); + // console.log('id', id, featureId) + if (id && id.split('.')[1] === String(featureId)) { + foundFeature = f; + break; + } + } } }); } @@ -361,6 +381,7 @@ function Gis({ setVisible={setTabVisible} onCheckedChange={handleCheckedLayersChange} onLayerIconClick={(nodeNm) => { + layerNmRef.current = nodeNm setLayerNm(nodeNm); setIsOpen(true); const dummyFeature = { getProperties: () => ({}), // 최소 구조 }; @@ -376,6 +397,7 @@ function Gis({ featureData={featureData || { getProperties: () => ({}) }} seletectedLayerNm={layerNm} onClose={closePopup} // 닫기 콜백 전달 + onFeatureSelect={handleFeatureSelect} /> )} {/* SearchView가 존재할 때만 렌더링 */} diff --git a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx index 2d7d2bf90..e58592837 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx @@ -1,4 +1,5 @@ -import { FormField } from "../form"; +import React from 'react'; +import { FormField } from "../form"; import { SelectBox } from "../select"; import { Input } from "@common/components"; import { FormUi } from "../form_ui"; @@ -55,6 +56,7 @@ interface FormSchema { bznmTotNo?: string; rgtr?: string; dmdAplyPswd?: string; + dmdAplyPswdConfirm?: string; } interface SdmRegFormProps { @@ -85,12 +87,27 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun const formatter = new AsYouType("KR"); const formatted = formatter.input(input); - // 저장할 값 처리: 숫자만 추출 - const digitsOnly = input.replace(/\D/g, ""); - return formatted }; + // 사업자등록번호 포맷팅 함수 (3-2-5 형식) + const formatBusinessNumber = (value: string) => { + // 숫자만 추출 + const digitsOnly = value.replace(/\D/g, ""); + + // 10자리까지만 처리 + const limitedDigits = digitsOnly.slice(0, 10); + + // 3-2-5 형식으로 포맷팅 + if (limitedDigits.length <= 3) { + return limitedDigits; + } else if (limitedDigits.length <= 5) { + return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3)}`; + } else { + return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`; + } + }; + const initializeSigunData = useCallback((sidoCd: string) => { if (sidoCd === '0') { @@ -179,12 +196,15 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun const value = e.target.value; if(noType === 'dmdInfoPk') { - if(orgBznmNo.substring(0,10) === value) { + // 숫자만 추출하여 저장 (포맷팅 제거) + const digitsOnly = value.replace(/\D/g, ""); + + if(orgBznmNo.substring(0,10) === digitsOnly) { setIsDuplicate(true); } else { setIsDuplicate(false); } - field?.onChange(value); + field?.onChange(digitsOnly); } else { if(orgBznmNo.substring(10,14) === value) { setIsDuplicate(true); @@ -214,6 +234,7 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun }, [updateList,form]); useEffect(() => { + console.log( 'form.formState.errors ', form.formState.errors) },[form.formState.errors]) const formFields: Array<{ @@ -296,7 +317,7 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun type="text" className="!text-xl text-gray-600" onChange={(e) => bznNoChange(e, 'dmdInfoPk', field)} - value={field.value} + value={formatBusinessNumber(field.value || '')} readOnly={!!updateList?.dmdInfoPk} /> ) @@ -368,32 +389,60 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun {...field} /> ) + }, + { + name: "dmdAplyPswdConfirm", + label: "패스워드확인", + component: (field) => ( + + ) } ] return ( <> - {formFields.filter((item)=> - {if(updateList?.dmdInfoPk){ - return item.name !== 'dmdAplyPswd' - }else{ - return item - } - }) - .map(({ name, label, component }) => ( - ( - name === 'bznmTotNo' ? component(field) : ( - - {component(field)} - - ) - )} - /> - ))} + {formFields + .filter(({ name }) => !(updateList?.dmdInfoPk && (name === "dmdAplyPswd" || name === "dmdAplyPswdConfirm"))) + .map(({ name, label, component }) => { + const error = form.formState.errors[name]; + const inputId = `${name}-input`; + const errorId = `${name}-error`; + const isRequired = ["ctpvCd", "sggCd", "dmdCoNm", "dmdInfoPk", "daddr", "telno", "dmdAplyPswd", "dmdAplyPswdConfirm"].includes(name); + + return ( + ( +
+ + + {React.cloneElement(component(field), { + id: inputId, + "aria-required": isRequired, + "aria-invalid": !!error, + "aria-describedby": error ? errorId : undefined + })} + + {error?.message && ( + + )} +
+ )} + /> + ); + })} ); } \ No newline at end of file diff --git a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx index 4219602e6..35c609f9e 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx @@ -42,12 +42,24 @@ const createFormSchema = (isBznmTotNoDisabled: boolean, isDuplicate: boolean ,up .min(8, "전화번호는 8자 이상 입력해주세요") .max(11, "전화번호는 11자 이내로 입력해주세요") .regex(/^[0-9]+$/, "전화번호는 숫자만 입력 가능합니다"), - rgtr: z.string().optional(), + rgtr: z.string().nullish(), dmdAplyPswd : updateList?.dmdInfoPk // 업데이트일경우 ? z.string().optional() // 패스워드는 선택 사항이며 길이 제한 없음 : z.string().optional() .refine((val) => !val || (val.length >= 8 && val.length <= 16), "패스워드는 8자 이상 16자 이내로 입력해주세요"), - + dmdAplyPswdConfirm: updateList?.dmdInfoPk // 업데이트일경우 + ? z.string().optional() // 패스워드는 선택 사항이며 길이 제한 없음 + :z.string() + .min(1, "패스워드확인을 입력해주세요"), +}).refine((data) => { + // 패스워드 확인 검증 + if (data.dmdAplyPswd && data.dmdAplyPswdConfirm) { + return data.dmdAplyPswd === data.dmdAplyPswdConfirm; + } + return true; +}, { + message: "패스워드가 일치하지 않습니다", + path: ["dmdAplyPswdConfirm"], // 에러를 패스워드 확인 필드에 표시 }); type FormSchema = z.infer>; @@ -62,6 +74,7 @@ const defaultValues: FormSchema = { telno: '', rgtr: '', dmdAplyPswd: '', + dmdAplyPswdConfirm: '', }; /** @@ -149,7 +162,7 @@ export default function SdmInsertForm({selectSidoData, selectSigunData, dataList console.log(' formData ', formData) /* await insertSdmData(formData); */ - rtnInputData(formData as DemandCompType); + rtnInputData(formData as unknown as DemandCompType); //router.push(`/pages/sdmMng/sdmInfoInsert?${queryParams.toString()}`); diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index fe720abc0..01bb18ba2 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -49,6 +49,12 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr } }, [resetSelection]); + // 테이블 데이터가 변경될 때마다 선택된 행 초기화하는 useEffect 추가 + useEffect(() => { + // 새로운 데이터가 로드되면 선택 상태 초기화 + setIdx(-1); + }, [tableData]); + const handlePageChange = (page: number) => { setCur(page); onPageChange(page); @@ -83,7 +89,7 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr ? handleCellClick(index, head.id)} key={`cell-${head.id}-${index}`} @@ -97,7 +103,7 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr : handleCellClick(index, head.id)} key={`cell-${head.id}-${index}`} diff --git a/packages/common/components/src/ui/date-picker/index.tsx b/packages/common/components/src/ui/date-picker/index.tsx index ee5d37872..85c584e5f 100644 --- a/packages/common/components/src/ui/date-picker/index.tsx +++ b/packages/common/components/src/ui/date-picker/index.tsx @@ -2,7 +2,7 @@ import "./date-picker.css"; import { format } from "date-fns"; import { CalendarIcon } from "lucide-react"; -import type { DayPickerSingleProps } from "react-day-picker"; +import type { DayPicker } from "react-day-picker"; import { cn } from "../../lib/utils"; import { Button } from "../button"; @@ -12,12 +12,15 @@ import { Popover, PopoverContent, PopoverTrigger } from "../popover"; type DatePickerProps = { placeholder?: string; disabled?: boolean; -} & DayPickerSingleProps; + selected?: Date; + zIndex?: number; // z-index prop 추가 +} & React.ComponentProps; export function DatePicker({ className, placeholder = "날짜 선택", disabled, + zIndex, // zIndex prop 받기 ...props }: DatePickerProps) { return ( @@ -36,8 +39,11 @@ export function DatePicker({ )} - - + + );