diff --git a/.gitignore b/.gitignore
index c5082bba3..7175c78d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,4 @@ settings.json
.npmrc
.cursor
-apps/**/tsconfig.tsbuildinfo
+
diff --git a/.husky/pre-push b/.husky/pre-push
index 82c73e102..0ddfdb46e 100755
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -1,3 +1,6 @@
#!/bin/sh
BRANCH=$(git rev-parse --abbrev-ref HEAD)
+if [ "$BRANCH" != "main" ]; then
+ exit 0
+fi
pnpm turbo run build --filter="...[origin/$BRANCH]"
diff --git a/apps/fo/app/pages/(with-layout)/layout.tsx b/apps/fo/app/pages/(with-layout)/layout.tsx
index f77bc244b..e1f6f664e 100644
--- a/apps/fo/app/pages/(with-layout)/layout.tsx
+++ b/apps/fo/app/pages/(with-layout)/layout.tsx
@@ -21,7 +21,6 @@ export default function RootLayout({
setCurrentMenuInfo(id);
getMenuAuth(id.menuCd);
router.push(id.urlPath);
- console.log(id);
};
return (
diff --git a/apps/sn/AGENTS.md b/apps/sn/AGENTS.md
new file mode 100644
index 000000000..18d6c1e38
--- /dev/null
+++ b/apps/sn/AGENTS.md
@@ -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
+
diff --git a/apps/sn/app/(map-layout)/map/components/common/index.ts b/apps/sn/app/(map-layout)/map/components/common/index.ts
index 2581ac0fa..d2aed5956 100644
--- a/apps/sn/app/(map-layout)/map/components/common/index.ts
+++ b/apps/sn/app/(map-layout)/map/components/common/index.ts
@@ -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";
diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx
index 031c69d8a..11e61b006 100644
--- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx
+++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx
@@ -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; 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(''); // 로딩 상태
const [chmbrYdnts, setChmbrYdnts] = useState(''); // 로딩 상태
+ const linkedFacilityFeatureRef = useRef(null);
+ const linkedFacilityOriginalStyleRef = useRef(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,
)
}
-export default FeatureClickEventPopup;
\ No newline at end of file
+export default FeatureClickEventPopup;
diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx
index a90284dde..ad57c0a5f 100644
--- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx
+++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx
@@ -249,7 +249,7 @@ const ReportTab: React.FC = ({ open, isSearch = true, ctpvNm, sg
{
- return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건>);
+ return (<>조회 결과>);
})()
}
tableData={tableData}
diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx
index d179652f3..3ce334518 100644
--- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx
+++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx
@@ -130,7 +130,7 @@ const SearchModal: React.FC = () => {
{
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}
diff --git a/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx
index f5104154f..f5fcf7d91 100644
--- a/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx
+++ b/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx
@@ -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();
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() {
}
>
-
+ {/* */}
>
);
}
diff --git a/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx
index 8a6744833..eee7d9f73 100644
--- a/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx
+++ b/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx
@@ -248,7 +248,7 @@ export default function LdrmntCoSttn() {
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;
};
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('비밀번호 초기화에 실패하였습니다.')
-
}
}
}
-}
\ No newline at end of file
+}
diff --git a/apps/wr/src/shared/ui/alert-dialog.tsx b/apps/wr/src/shared/ui/alert-dialog.tsx
index 6160d5301..51e82798c 100644
--- a/apps/wr/src/shared/ui/alert-dialog.tsx
+++ b/apps/wr/src/shared/ui/alert-dialog.tsx
@@ -21,10 +21,12 @@ function AlertDialog() {
{message}
-
-
-
-
+
+
+
+
+
+
diff --git a/apps/wr/src/shared/ui/confirm-dialog.tsx b/apps/wr/src/shared/ui/confirm-dialog.tsx
index 6a4996bfb..7a8065a98 100644
--- a/apps/wr/src/shared/ui/confirm-dialog.tsx
+++ b/apps/wr/src/shared/ui/confirm-dialog.tsx
@@ -28,17 +28,19 @@ function ConfirmDialog({
{message}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/apps/wr/tsconfig.json b/apps/wr/tsconfig.json
index 493584782..221de4580 100644
--- a/apps/wr/tsconfig.json
+++ b/apps/wr/tsconfig.json
@@ -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": [
diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx
index d2fd81de6..e5fc933ae 100644
--- a/packages/common/business_components/src/ui/table/index.tsx
+++ b/packages/common/business_components/src/ui/table/index.tsx
@@ -16,6 +16,7 @@ export type ChkClass = {
chk: boolean;
readonly: boolean;
};
+
type UiTableProps = {
headName: React.ReactNode | undefined;
tableData: T[];
@@ -123,17 +124,20 @@ export function UiTable>({
};
const leafCols = getLeafCols(headlist);
+ const totalLabel = typeof total === "number" ? total.toLocaleString("ko-KR") : " ";
+ const hasTotal = typeof total === "number";
+
return (
<>
{headName ? (
headButton ? (
-
{headName}
+ {headName} 총 {totalLabel}건
<>{headButton}>
) : (
-
{headName}
+ {headName} 총 {totalLabel}건
)
) : (
@@ -286,7 +290,7 @@ export function UiTable>({
)}
- {total <= 0 ? (
+ {!hasTotal || total <= 0 ? (
<>>
) : (