Merge branch 'iipbs/sn/dev' into iipbs/dm/dev

This commit is contained in:
2026-03-18 09:34:01 +09:00
15 changed files with 218 additions and 60 deletions
@@ -21,7 +21,6 @@ export default function RootLayout({
setCurrentMenuInfo(id);
getMenuAuth(id.menuCd);
router.push(id.urlPath);
console.log(id);
};
return (
+35
View File
@@ -0,0 +1,35 @@
## Project
This repository contains a Next.js web application.
Main technologies:
- Next.js
- TypeScript
- pnpm workspace
- turborepo
이 저장소는 pnpm workspace + Turborepo 기반 monorepo이다.
- 워크스페이스 범위는 `apps/*`, `apps/common/*`, `packages/*`, `packages/common/*`
- 루트 스크립트는 `turbo run build`, `turbo run dev` 를 사용한다
- `build` 작업은 의존 패키지의 `build` 를 먼저 수행한다 (`dependsOn: ["^build"]`)
- `dev` 는 캐시하지 않는 지속 실행 작업이다
- 변경 범위만 선택 실행할 수 있으므로, 전체 빌드보다 `--filter` 사용을 우선한다
## Encoding
- UTF-8
- 한글이 깨지지 않도록 주의할 것
## Monorepo structure
apps/
_template
as
cm
cs
dc
dm
dm_new
fo
sn
st
wr
@@ -1,5 +1,5 @@
import MsSwagePpl from "../popupComponent/MsSwagePpl";
import sewagePump from "../popupComponent/sewagePump";
import MsSwagePpl from "../popupComponent/MsSwagePpl"; // 하수관로
import sewagePump from "../popupComponent/sewagePump"; // 하수펌프
import smh from "../popupComponent/smh";
import Ssc from "../popupComponent/Ssc";
import StormWaterPump from "../popupComponent/stormWaterPump";
@@ -28,6 +28,10 @@ import { VerticalTable } from "@/components/ui/verticalTable";
import { useGisContext } from "@common/business_components/ui/map/Gis";
import { Button } from "@common/components/ui";
import { useEffect, useRef, useState, useMemo } from "react";
import Style from "ol/style/Style.js";
import Fill from "ol/style/Fill.js";
import Stroke from "ol/style/Stroke.js";
import CircleStyle from "ol/style/Circle.js";
import { getLayerContentComponent } from "../common";
import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table";
import InfilChart from './chart/infilChart';
@@ -38,6 +42,27 @@ import { codeMapper } from "../common/codeMapper";
import LoadingOverlay from '@/components/ui/LoadingBar';
import { searchUpperPpls } from "./smh";
const linkedFacilityHighlightStyle = new Style({
image: new CircleStyle({
radius: 18,
fill: new Fill({
color: 'rgba(255, 235, 59, 0.35)',
}),
stroke: new Stroke({
color: 'rgba(255, 193, 7, 0.45)',
width: 2,
}),
}),
fill: new Fill({
color: 'rgba(255, 235, 59, 0.2)',
}),
stroke: new Stroke({
color: 'rgba(255, 193, 7, 0.35)',
width: 2.5,
}),
zIndex: 9999,
});
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
@@ -59,6 +84,8 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
const [isLoading, setIsLoading] = useState(false); // 로딩 상태
const [chmbrXcnts, setChmbrXcnts] = useState<string>(''); // 로딩 상태
const [chmbrYdnts, setChmbrYdnts] = useState<string>(''); // 로딩 상태
const linkedFacilityFeatureRef = useRef<any>(null);
const linkedFacilityOriginalStyleRef = useRef<any>(null);
// 🎯 관로관리번호 추출
const pplMngNo = featureData?.values_?.fclt_mng_no || "";
@@ -77,6 +104,70 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
};
}, [featureData?.values_]);
const clearLinkedFacilityHighlight = () => {
if (linkedFacilityFeatureRef.current) {
linkedFacilityFeatureRef.current.setStyle(linkedFacilityOriginalStyleRef.current ?? undefined);
linkedFacilityFeatureRef.current.changed?.();
}
linkedFacilityFeatureRef.current = null;
linkedFacilityOriginalStyleRef.current = null;
};
// 하수처리구역에 연결된 하수처리시설 피처를 찾아 기존 아이콘 위에 노랑 하이라이트를 덧씌운다.
const highlightLinkedFacility = (facilityCode: string | null | undefined) => {
clearLinkedFacilityHighlight();
if (!gisMap || !facilityCode) return;
const normalizedFacilityCode = String(facilityCode).trim();
const layers = gisMap.getLayers().getArray();
for (const layer of layers) {
const source = layer?.getSource?.();
if (!source?.getFeatures) continue;
const features = source.getFeatures();
const targetFeature = features.find((feature: any) => {
const featureId = feature?.getId?.();
const isFacilityLayer = typeof featureId === 'string' && featureId.startsWith('tb_il_swage_prcs_fclt.');
const featureFacilityCode = feature?.get?.('swtrmt_fclt_cd');
return isFacilityLayer && String(featureFacilityCode ?? '').trim() === normalizedFacilityCode;
});
if (targetFeature) {
const originalFeatureStyle = targetFeature.getStyle?.();
const featureLayer = targetFeature.getLayer?.();
const layerStyleFn = featureLayer?.getStyleFunction?.() ?? null;
linkedFacilityOriginalStyleRef.current = originalFeatureStyle;
linkedFacilityFeatureRef.current = targetFeature;
targetFeature.setStyle((feature: any, resolution: number) => {
let styles: any[] = [];
if (typeof originalFeatureStyle === 'function') {
const originalStyles = originalFeatureStyle(feature, resolution);
if (Array.isArray(originalStyles)) styles.push(...originalStyles);
else if (originalStyles) styles.push(originalStyles);
} else if (originalFeatureStyle) {
if (Array.isArray(originalFeatureStyle)) styles.push(...originalFeatureStyle);
else styles.push(originalFeatureStyle);
} else if (layerStyleFn) {
const layerStyles = layerStyleFn(feature, resolution);
if (Array.isArray(layerStyles)) styles.push(...layerStyles);
else if (layerStyles) styles.push(layerStyles);
}
styles.push(linkedFacilityHighlightStyle);
return styles;
});
targetFeature.changed?.();
return;
}
}
};
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
const getSelectedLayers = () => {
let result = null;
@@ -348,6 +439,26 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
fetchData();
}, [featureId, processedData]);
useEffect(() => {
const layerTypeNm = featureData?.values_
? featureData?.id_?.split('.')?.[0]
: featureData?.lyrTypeNm;
if (layerTypeNm === 'tb_ms_swage_prcs_zone') {
const facilityCode = featureData?.values_?.swtrmt_fclt_cd ?? featureData?.swtrmtFcltCd;
highlightLinkedFacility(facilityCode);
return () => {
clearLinkedFacilityHighlight();
};
}
clearLinkedFacilityHighlight();
return () => {
clearLinkedFacilityHighlight();
};
}, [featureData, gisMap]);
// 저장 시 모달 띄우기
const handleSaveClick = () => {
setIsModalOpen(true); // 모달 열기
@@ -525,4 +636,4 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
</div>
)
}
export default FeatureClickEventPopup;
export default FeatureClickEventPopup;
@@ -249,7 +249,7 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
<LoadingOverlay isLoading={isLoading} />
<UiTable
headName={(() => {
return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>);
return (<>조회 결과</>);
})()
}
tableData={tableData}
@@ -130,7 +130,7 @@ const SearchModal: React.FC = () => {
<UiTable className='w-[120%]'
headName={(() => {
const label = BASIC_TYPES.find((type) => type.val === basicType)?.text
return (<>{label} 조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>);
return (<>{label} 조회 결과</>);
})()
}
tableData={tableData}
@@ -161,7 +161,7 @@ const SearchModal: React.FC = () => {
headName={
(() => {
const label = WORK_TYPES.find((type) => type.val === workType)?.text
return (<>{label} 사업 현황 ({tableCount.toLocaleString('ko-KR')})건</>);
return (<>{label} 사업 현황</>);
})()
}
tableData={tableData}
@@ -188,7 +188,7 @@ const SearchModal: React.FC = () => {
headName={
(() => {
const label = WORK_TYPES.find((type) => type.val === workType)?.text
return (<>{label} 결과표 ({detailTableCount.toLocaleString('ko-KR')})건</>);
return (<>{label} 결과표</>);
})()
}
tableData={detailTableData}
@@ -5,7 +5,7 @@ import { btnClass } from "@/app/store/common/code";
import { excelCoSttnParsing } from "@/app/store/matDmd/coSttn/excel";
import { fetchType, getFetch } from "@/components/lib/api";
import {
confirmToast,
//confirmToast,
excelDownload,
formatPhoneNumber,
getCity,
@@ -29,8 +29,10 @@ import type { DemandCompType } from "@common/business_components/ui/sdmInsertFor
import { Button } from "@common/components/ui";
import moment from "moment";
import { useCallback, useEffect, useState } from "react";
import toast, { Toaster } from "react-hot-toast";
// import toast, { Toaster } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useAlertStore } from "@/src/shared/lib";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
export default function CoSttn() {
const {
@@ -48,7 +50,8 @@ export default function CoSttn() {
getMyUUID
} = useWrStore((state) => state);
const { regAuthrtYn, mdfcnAuthrtYn } = auth;
const alert = useAlertStore((state) => state.setMessage);
const { confirm } = useConfirm();
const [bdstTable, setBdstTable] = useState<DemandCompType[]>();
const [pageNation, setPageNation] = useState(1);
const { coSttnRes, coSttnResCount } = coSttn;
@@ -98,7 +101,7 @@ export default function CoSttn() {
useEffect(() => {
if (!coSttn.isInit) {
coSttnInit("1", "13", "0");
coSttnInit("1", "13", "0"); // 수요처 목록 조회
}
}, [coSttn.isInit, coSttnInit]);
@@ -167,16 +170,21 @@ export default function CoSttn() {
}
};
const { t } = useTranslation('base');
const initPassword = () => {
const initPassword = async () => {
if (updateData) {
const dmdInfoId = updateData.dmdInfoId;
const dmdInfoSliceId = `${dmdInfoId.slice(0, 3)}-${dmdInfoId.slice(3, 5)}-${dmdInfoId.slice(5, 10)}-${dmdInfoId.slice(10)}`
const handleConfirm = () => {
coSttnPwInit(dmdInfoId);
const handleConfirm = async () => {
const message = await coSttnPwInit(dmdInfoId);
alert(message);
};
confirmToast( t('isInitPw', { dmdInfoId: dmdInfoSliceId }), handleConfirm, 'text-2xl');
//confirmToast( t('isInitPw', { dmdInfoId: dmdInfoSliceId }), handleConfirm, 'text-2xl');
if (await confirm(t('isInitPw', { dmdInfoId: dmdInfoSliceId }))) {
await handleConfirm();
}
} else {
toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.")
alert("비밀번호 초기화를 원하는 행을 선택해주세요.");
// toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.")
}
}
@@ -238,7 +246,7 @@ export default function CoSttn() {
onClick={() => {
/* 저장 로직 */
if (!updateData) {
toast.error("수요처 변경을 원하는 행을 선택해주세요");
alert("수요처 변경을 원하는 행을 선택해주세요");
} else {
setIsDmdModal({
...isDmdModal,
@@ -274,8 +282,8 @@ export default function CoSttn() {
headName={[]}
tableData={bdstTable || []}
headlist={coSttn.coSttHeadList}
pageSize={20}
total={coSttnResCount + 10 || 0}
pageSize={13}
total={coSttnResCount || 0}
cellClick={ async(index: number | undefined) => {
const uuid = await getMyUUID();
const updateData = coSttnRes[index as number];
@@ -332,9 +340,7 @@ export default function CoSttn() {
`/reuseFc/request/${data.dmdInfoId}`
);
if (dmd) {
toast.error(
`사업자번호 ${dmd.dmdInfoId}는 이미 등록되었습니다. 다시등록해주세요`
);
alert(`사업자번호 ${dmd.dmdInfoId}는 이미 등록되었습니다. 다시등록해주세요`);
return;
}
await regDemandCompany(data as DemandCompType);
@@ -352,7 +358,7 @@ export default function CoSttn() {
}
></UiModal>
</ContentsDiv>
<Toaster position="top-center" />
{/* <Toaster position="top-center" /> */}
</>
);
}
@@ -248,7 +248,7 @@ export default function LdrmntCoSttn() {
<div className="overflow-x-auto">
<ListDiv className="min-w-max">
<UiTable
headName={undefined}
headName={[]}
tableData={bdstTable || []}
headlist={ldrmntHeadListExport}
pageSize={20}
+9 -9
View File
@@ -2,7 +2,6 @@ import type { CommonTableProps } from "@/app/store/common/type/header";
import { fetchType, getFetchWithCount, putFectch } from "@/components/lib/api";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import { coSttHeadListExport, coSttNodeListExport } from "./headList";
import toast from "react-hot-toast";
// 테이블 헤드 리스트
const coSttNodeList = coSttNodeListExport;
@@ -25,7 +24,7 @@ type coSttnActions = {
coSttnInit: (pageNo: string, pageCnt: string, con: string) => void;
coSttnSearch: (pageNo: string, pageCnt: string, con: string) => void;
coSttnTxtSearch: (pageNo: string, pageCnt: string, con: string, txt: string) => void;
coSttnPwInit: (dmdInfoId: string) => void;
coSttnPwInit: (dmdInfoId: string) => Promise<string>;
};
export type coSttnType = coSttnState & coSttnActions;
@@ -46,6 +45,7 @@ export const coSttnActionsExport: (set: any) => coSttnActions = (
set: any
) => {
return {
// 수요처 목록 조회
coSttnInit: async (pageNo: string, pageCnt: string, con: string) => {
try {
@@ -126,10 +126,14 @@ export const coSttnActionsExport: (set: any) => coSttnActions = (
try {
const response = await putFectch(`/reuseFc/request/chgPassword/${dmdInfoId}`,[])
if (response.status === 200) {
toast.success('비밀번호가 초기화 되었습니다.')
return "비밀번호가 초기화되었습니다.";
} else {
toast.error('비밀번호 초기화에 실패하였습니다')
return "비밀번호 초기화에 실패하였습니다.";
}
} catch (err) {
console.log(err);
return "비밀번호 초기화에 실패하였습니다.";
} finally {
set((state: coSttnState) => {
return {
...state,
@@ -139,11 +143,7 @@ export const coSttnActionsExport: (set: any) => coSttnActions = (
}
}
});
} catch (err) {
console.log(err);
toast.error('비밀번호 초기화에 실패하였습니다.')
}
}
}
}
}
+6 -4
View File
@@ -21,10 +21,12 @@ function AlertDialog() {
{message}
</p>
</div>
<DialogFooter className="!justify-center">
<DialogClose asChild>
<Button>확인</Button>
</DialogClose>
<DialogFooter>
<div className="flex w-full justify-center">
<DialogClose asChild>
<Button>확인</Button>
</DialogClose>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
+13 -11
View File
@@ -28,17 +28,19 @@ function ConfirmDialog({
{message}
</p>
</div>
<DialogFooter className="!justify-center">
<DialogClose asChild>
<Button onClick={onConfirm} {...confirmButtonProps}>
{confirmButtonProps?.label ?? "확인"}
</Button>
</DialogClose>
<DialogClose asChild>
<Button onClick={onCancel} color="white" {...cancelButtonProps}>
{cancelButtonProps?.label ?? "취소"}
</Button>
</DialogClose>
<DialogFooter>
<div className="flex w-full justify-center gap-2">
<DialogClose asChild>
<Button onClick={onConfirm} {...confirmButtonProps}>
{confirmButtonProps?.label ?? "확인"}
</Button>
</DialogClose>
<DialogClose asChild>
<Button onClick={onCancel} color="white" {...cancelButtonProps}>
{cancelButtonProps?.label ?? "취소"}
</Button>
</DialogClose>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
+3 -7
View File
@@ -28,13 +28,9 @@
"@/*": ["./*"],
"@common/components/*": ["../../packages/common/components/src/*"],
"@common/components": ["../../packages/common/components/src/index"],
"@common/business_components/*": [
"../../packages/common/business_components/src/*"
],
"@common/business_components": [
"../../packages/common/business_components/src/index"
],
"@common/network": ["../../packages/common/network"]
"@common/business_components/*": ["../../packages/common/business_components/src/*"],
"@common/business_components": ["../../packages/common/business_components/src/index"],
"@common/network": ["../../packages/common/network"],
}
},
"include": [