From 7a91fcbd8042d4b8518583a1687a5857a4ded163 Mon Sep 17 00:00:00 2001 From: dongsilove Date: Wed, 18 Feb 2026 22:11:12 +0900 Subject: [PATCH] dm_new menu --- apps/dm_new/.env.local | 2 +- .../dm_new/app/(with-layout)/apply/layout.tsx | 11 + .../apply/modal/WaterQualityRefModal.tsx | 99 ++ .../app/(with-layout)/apply/place/page.tsx | 350 +++++++ .../app/(with-layout)/apply/register/page.tsx | 476 +++++++++ .../app/(with-layout)/apply/status/page.tsx | 338 ++++++ .../app/(with-layout)/example/layout.tsx | 11 + .../(with-layout)/example/rain/suwon/page.tsx | 93 ++ .../example/sewage/bolyeong/page.tsx | 111 ++ .../example/sewage/cheongju/page.tsx | 94 ++ .../example/sewage/guri/page.tsx | 171 ++++ .../example/sewage/hwanggu/page.tsx | 171 ++++ .../example/sewage/paju/page.tsx | 112 ++ .../example/sewage/yeosu/page.tsx | 101 ++ .../(with-layout)/intro/greywater/page.tsx | 86 ++ .../app/(with-layout)/intro/law/page.tsx | 122 +++ .../dm_new/app/(with-layout)/intro/layout.tsx | 11 + .../app/(with-layout)/intro/rain/page.tsx | 76 ++ .../app/(with-layout)/intro/sewage/page.tsx | 76 ++ apps/dm_new/app/(with-layout)/layout.tsx | 13 + .../(with-layout)/status/greywater/page.tsx | 96 ++ .../app/(with-layout)/status/layout.tsx | 11 + .../app/(with-layout)/status/rain/page.tsx | 86 ++ .../app/(with-layout)/status/sewage/page.tsx | 96 ++ apps/dm_new/app/layout.tsx | 2 +- apps/dm_new/app/main/SideIndicator.tsx | 99 ++ apps/dm_new/app/main/_hooks/useScrollSpy.ts | 55 + apps/dm_new/app/main/_sections/Cases.tsx | 65 ++ apps/dm_new/app/main/_sections/Hero.tsx | 29 + apps/dm_new/app/main/_sections/IntroCards.tsx | 86 ++ .../dm_new/app/main/_sections/ProcessFlow.tsx | 22 + apps/dm_new/app/main/layout.tsx | 13 + apps/dm_new/app/main/page.tsx | 58 ++ apps/dm_new/app/reuse.module.css | 238 +++++ apps/dm_new/app/store/common/index.tsx | 495 +++++++++ apps/dm_new/app/store/index.tsx | 42 + apps/dm_new/app/store/map/index.tsx | 114 +++ apps/dm_new/app/store/map/type.tsx | 39 + apps/dm_new/app/store/readme.md | 121 +++ apps/dm_new/app/store/sdmMng/index.tsx | 797 +++++++++++++++ apps/dm_new/app/store/store.tsx | 48 + apps/dm_new/app/store/wrStatus/index.tsx | 129 +++ apps/dm_new/app/store/wrStatus/type.ts | 41 + apps/dm_new/components/CaseImageSlider.tsx | 167 +++ apps/dm_new/components/CaseTabsCarousel.tsx | 519 ++++++++++ apps/dm_new/components/ImageSlider.tsx | 167 +++ apps/dm_new/components/Lnb.tsx | 168 +++ apps/dm_new/components/LoginModal.tsx | 320 ++++++ apps/dm_new/components/SubPageHeader.tsx | 64 ++ apps/dm_new/components/TopNav.tsx | 187 ++++ apps/dm_new/components/ui/api/index.ts | 99 ++ .../ui/common/searhDemandInput/component.tsx | 103 ++ .../components/ui/common/utils/utils.ts | 54 + apps/dm_new/components/ui/config/index.ts | 6 + .../map/SearchInputText/SearchInputText.tsx | 139 +++ .../components/ui/map/searchBar/searchBar.tsx | 118 +++ .../sdmInfoInsertForm/AccessibleFormField.tsx | 35 + .../ui/sdmInfoInsertForm/component.tsx | 962 ++++++++++++++++++ .../components/ui/sdmLoginForm/component.tsx | 197 ++++ apps/dm_new/middleware.ts | 2 +- apps/dm_new/next.config.ts | 18 +- .../public/fonts/NotoSansKR-Regular.woff2 | Bin 0 -> 993100 bytes apps/dm_new/src/shared/lib/index.ts | 2 +- apps/dm_new/src/shared/lib/use-menu-store.ts | 14 + apps/dm_new/src/shared/providers/index.ts | 2 + .../src/shared/providers/prompt-provider.tsx | 61 ++ .../src/shared/providers/store-provider.tsx | 38 + apps/dm_new/src/shared/store/alert-store.ts | 25 + .../src/shared/store/attachment-store.ts | 25 + apps/dm_new/src/shared/store/index.ts | 4 + apps/dm_new/src/shared/store/menu-store.ts | 93 ++ apps/dm_new/src/shared/store/user-store.ts | 57 ++ apps/dm_new/tsconfig.tsbuildinfo | 1 + pnpm-lock.yaml | 70 ++ 74 files changed, 9010 insertions(+), 13 deletions(-) create mode 100644 apps/dm_new/app/(with-layout)/apply/layout.tsx create mode 100644 apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx create mode 100644 apps/dm_new/app/(with-layout)/apply/place/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/apply/register/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/apply/status/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/layout.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/rain/suwon/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/sewage/bolyeong/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/sewage/cheongju/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/sewage/guri/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/sewage/hwanggu/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/sewage/paju/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/example/sewage/yeosu/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/intro/greywater/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/intro/law/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/intro/layout.tsx create mode 100644 apps/dm_new/app/(with-layout)/intro/rain/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/intro/sewage/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/layout.tsx create mode 100644 apps/dm_new/app/(with-layout)/status/greywater/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/status/layout.tsx create mode 100644 apps/dm_new/app/(with-layout)/status/rain/page.tsx create mode 100644 apps/dm_new/app/(with-layout)/status/sewage/page.tsx create mode 100644 apps/dm_new/app/main/SideIndicator.tsx create mode 100644 apps/dm_new/app/main/_hooks/useScrollSpy.ts create mode 100644 apps/dm_new/app/main/_sections/Cases.tsx create mode 100644 apps/dm_new/app/main/_sections/Hero.tsx create mode 100644 apps/dm_new/app/main/_sections/IntroCards.tsx create mode 100644 apps/dm_new/app/main/_sections/ProcessFlow.tsx create mode 100644 apps/dm_new/app/main/layout.tsx create mode 100644 apps/dm_new/app/main/page.tsx create mode 100644 apps/dm_new/app/reuse.module.css create mode 100644 apps/dm_new/app/store/common/index.tsx create mode 100644 apps/dm_new/app/store/index.tsx create mode 100644 apps/dm_new/app/store/map/index.tsx create mode 100644 apps/dm_new/app/store/map/type.tsx create mode 100644 apps/dm_new/app/store/readme.md create mode 100644 apps/dm_new/app/store/sdmMng/index.tsx create mode 100644 apps/dm_new/app/store/store.tsx create mode 100644 apps/dm_new/app/store/wrStatus/index.tsx create mode 100644 apps/dm_new/app/store/wrStatus/type.ts create mode 100644 apps/dm_new/components/CaseImageSlider.tsx create mode 100644 apps/dm_new/components/CaseTabsCarousel.tsx create mode 100644 apps/dm_new/components/ImageSlider.tsx create mode 100644 apps/dm_new/components/Lnb.tsx create mode 100644 apps/dm_new/components/LoginModal.tsx create mode 100644 apps/dm_new/components/SubPageHeader.tsx create mode 100644 apps/dm_new/components/TopNav.tsx create mode 100644 apps/dm_new/components/ui/api/index.ts create mode 100644 apps/dm_new/components/ui/common/searhDemandInput/component.tsx create mode 100644 apps/dm_new/components/ui/common/utils/utils.ts create mode 100644 apps/dm_new/components/ui/config/index.ts create mode 100644 apps/dm_new/components/ui/map/SearchInputText/SearchInputText.tsx create mode 100644 apps/dm_new/components/ui/map/searchBar/searchBar.tsx create mode 100644 apps/dm_new/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx create mode 100644 apps/dm_new/components/ui/sdmInfoInsertForm/component.tsx create mode 100644 apps/dm_new/components/ui/sdmLoginForm/component.tsx create mode 100644 apps/dm_new/public/fonts/NotoSansKR-Regular.woff2 create mode 100644 apps/dm_new/src/shared/lib/use-menu-store.ts create mode 100644 apps/dm_new/src/shared/providers/index.ts create mode 100644 apps/dm_new/src/shared/providers/prompt-provider.tsx create mode 100644 apps/dm_new/src/shared/providers/store-provider.tsx create mode 100644 apps/dm_new/src/shared/store/alert-store.ts create mode 100644 apps/dm_new/src/shared/store/attachment-store.ts create mode 100644 apps/dm_new/src/shared/store/index.ts create mode 100644 apps/dm_new/src/shared/store/menu-store.ts create mode 100644 apps/dm_new/src/shared/store/user-store.ts create mode 100644 apps/dm_new/tsconfig.tsbuildinfo diff --git a/apps/dm_new/.env.local b/apps/dm_new/.env.local index 88b6f8067..62c4be2cc 100644 --- a/apps/dm_new/.env.local +++ b/apps/dm_new/.env.local @@ -1,4 +1,4 @@ -NEXT_PUBLIC_SITE_ID = DM +NEXT_PUBLIC_SITE_ID = DN NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token diff --git a/apps/dm_new/app/(with-layout)/apply/layout.tsx b/apps/dm_new/app/(with-layout)/apply/layout.tsx new file mode 100644 index 000000000..49c8a6266 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/layout.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from "react"; +import Lnb from "@/components/Lnb"; + +export default function ApplyLayout({ children }: { children: ReactNode }) { + return ( +
+ +
{children}
+
+ ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx b/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx new file mode 100644 index 000000000..877eb7d8c --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx @@ -0,0 +1,99 @@ +// apps/dm/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx (수질기준참고 모달) +"use client"; + +import { useEffect, useId } from "react"; +import Image from "next/image"; + +type Props = { + open: boolean; + onClose: () => void; + imageSrc: string; + title?: string; +}; + +export default function WaterQualityRefModal({ + open, + onClose, + imageSrc, + title = "수질 기준 참고", +}: Props) { + const titleId = useId(); + + useEffect(() => { + if (!open) return; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + // 스크롤 잠금 + const prev = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + return () => { + window.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prev; + }; + }, [open, onClose]); + + if (!open) return null; + + return ( +
+ {/* Backdrop */} + + +

+ {title} +

+ +
+
+ + {/* Body (스크롤 영역) */} +
+
+ {/* 이미지가 길어도 여기서 스크롤 */} + 수질 기준 참고 +
+
+
+ + + ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/place/page.tsx b/apps/dm_new/app/(with-layout)/apply/place/page.tsx new file mode 100644 index 000000000..4e13f3449 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/place/page.tsx @@ -0,0 +1,350 @@ +// apps/dm/app/(landing)/reuse/apply/place/page.tsx +"use client"; + +import { useMemo, useState } from "react"; +import SubPageHeader from "@/components/SubPageHeader"; + +type Option = { value: string; label: string }; +type SidoValue = "" | "seoul" | "busan" | "daegu" | "incheon" | "gyeonggi"; + +const SIDO: { value: SidoValue; label: string }[] = [ + { value: "", label: "선택" }, + { value: "seoul", label: "서울특별시" }, + { value: "busan", label: "부산광역시" }, + { value: "daegu", label: "대구광역시" }, + { value: "incheon", label: "인천광역시" }, + { value: "gyeonggi", label: "경기도" }, +]; + +const SIGUNGU_MAP: Record = { + "": [{ value: "", label: "선택" }], + seoul: [ + { value: "", label: "선택" }, + { value: "gangnam", label: "강남구" }, + { value: "yeongdeungpo", label: "영등포구" }, + { value: "mapo", label: "마포구" }, + ], + busan: [ + { value: "", label: "선택" }, + { value: "haeundae", label: "해운대구" }, + { value: "suyeong", label: "수영구" }, + ], + daegu: [ + { value: "", label: "선택" }, + { value: "suseong", label: "수성구" }, + { value: "dalseo", label: "달서구" }, + ], + incheon: [ + { value: "", label: "선택" }, + { value: "namdong", label: "남동구" }, + { value: "yeonsu", label: "연수구" }, + ], + gyeonggi: [ + { value: "", label: "선택" }, + { value: "bundang", label: "성남시 분당구" }, + { value: "suji", label: "용인시 수지구" }, + { value: "ilsandong", label: "고양시 일산동구" }, + ], +}; + +/* --------------------------- + * register/page.tsx 스타일과 맞춘 공통 UI (FormRow/Divider) + * -------------------------- */ + +function Divider() { + return
; +} + +function FormRow({ + label, + required, + children, +}: { + label: React.ReactNode; + required?: boolean; + children: React.ReactNode; +}) { + return ( +
+ {/* 라벨(좌측) */} +
+
+ {label} + {required ? * : null} +
+
+ + {/* 입력(우측) */} +
+ {children} +
+
+ ); +} + +function TextInput({ + value, + onChange, + placeholder, + type = "text", + disabled, + className = "", +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + type?: string; + disabled?: boolean; + className?: string; +}) { + return ( + onChange(e.target.value)} + placeholder={placeholder} + className={[ + "h-11 w-full rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800", + "placeholder:text-neutral-400 focus:border-neutral-400 focus:outline-none", + disabled ? "bg-neutral-100 text-neutral-400" : "", + className, + ].join(" ")} + /> + ); +} + +function SelectBox({ + value, + onChange, + options, + className = "", +}: { + value: T; + onChange: (v: T) => void; + options: { value: T; label: string }[]; + className?: string; +}) { + return ( + + ); +} + +export default function DemandPlaceRegisterPage() { + const [sido, setSido] = useState(""); + const [sigungu, setSigungu] = useState(""); + + const [company, setCompany] = useState(""); + const [bizNo, setBizNo] = useState(""); + const [workerNo, setWorkerNo] = useState(""); + const [noWorkerNo, setNoWorkerNo] = useState(false); + + const [zip, setZip] = useState(""); + const [addr1, setAddr1] = useState(""); + const [addr2, setAddr2] = useState(""); + + const [tel, setTel] = useState(""); + const [pw, setPw] = useState(""); + const [pw2, setPw2] = useState(""); + + // ✅ undefined 불가(Record + sido가 SidoValue) + const sigunguOptions = useMemo(() => SIGUNGU_MAP[sido], [sido]); + + const onSubmit = () => { + if (!sido || !sigungu) return alert("시도/시군구를 선택해 주세요."); + if (!company) return alert("회사명을 입력해 주세요."); + if (!bizNo) return alert("사업자등록번호를 입력해 주세요."); + if (!noWorkerNo && !workerNo) + return alert("종사자등록번호를 입력하거나 '해당없음'을 선택해 주세요."); + if (!addr1) return alert("사업자주소를 입력해 주세요."); + if (!tel) return alert("전화번호를 입력해 주세요."); + if (!pw) return alert("비밀번호를 입력해 주세요."); + if (pw !== pw2) return alert("비밀번호가 일치하지 않습니다."); + + alert("수요처 등록(샘플) 완료!"); + }; + + return ( +
+ + + {/* register/page.tsx와 동일한 테이블 룩앤필 */} +
+ + + value={sido} + onChange={(v) => { + setSido(v); + setSigungu(""); // 시도 변경 시 초기화 + }} + options={SIDO} + /> + + + + + + + + + + + +
+ +
+
+ + + + +
+ +
+
+ + + + +
+
+ +
+ + +
+
+ + + + +
+
+
+ +
+ +
+ + + +
+
+ + + + +
+ +
+
+ + + + +
+ +
+
+ + + + +
+ +
+
+
+ + {/* register/page.tsx와 동일하게 하단 중앙 버튼 */} +
+ +
+
+ ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/register/page.tsx b/apps/dm_new/app/(with-layout)/apply/register/page.tsx new file mode 100644 index 000000000..645dadd67 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/register/page.tsx @@ -0,0 +1,476 @@ +// apps/dm/app/(landing)/reuse/apply/register/page.tsx +"use client"; + +import { useMemo, useState, useRef } from "react"; +import SubPageHeader from "@/components/SubPageHeader"; +import WaterQualityRefModal from "../modal/WaterQualityRefModal"; + +type PHRange = { min: string; max: string }; + +export default function DemandRegisterPage() { + const today = useMemo(() => { + const d = new Date(); + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const dd = String(d.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; + }, []); + + const [openRef, setOpenRef] = useState(false); + + // 기본정보 + const [reqDate, setReqDate] = useState(today); + const [reqQty, setReqQty] = useState(""); + const [usePurpose, setUsePurpose] = useState(""); + const [hopePrice, setHopePrice] = useState(""); + + // 요구수질 + const [coliform, setColiform] = useState(""); + const [noDetect, setNoDetect] = useState(false); + + const [tn, setTn] = useState(""); + const [residualCl, setResidualCl] = useState(""); + const [tp, setTp] = useState(""); + const [turbidity, setTurbidity] = useState(""); + const [ph, setPh] = useState({ min: "", max: "" }); + const [bod, setBod] = useState(""); + const [chloride, setChloride] = useState(""); + const [odor, setOdor] = useState("불쾌하지 않을 것"); + const [ec, setEc] = useState(""); + const [color, setColor] = useState(""); + const [remark, setRemark] = useState(""); + + // 공급처 후보지 선택(필수아님) + const [cand1, setCand1] = useState(""); + const [cand2, setCand2] = useState(""); + const [cand3, setCand3] = useState(""); + + return ( + <> + +
+ + {/* 서브 타이틀 + 브레드크럼 */} + + + {/* 컨텐츠 영역 */} +
+ + {/* 기본정보 테이블 */} +
+ + + + + + + + + + + + + + + + + + + + + +
+ + {/* 요구수질 섹션 타이틀 바 */} +
+
+ 요구수질 + + {/* 수질기준참고 모달 */} + setOpenRef(false)} + imageSrc="/images/landing/water_quality_ref_1.png" + /> +
+
+ + {/* 요구수질 테이블 */} +
+ {/* 1행 */} + + + +
+ } + rightLabel="총질소(T-N)(mg/L)" + rightControl={ + + } + /> + + + {/* 2행 */} + } + rightLabel="총인(T-P)(mg/L)" + rightControl={ + + } + /> + + + {/* 3행 */} + + } + rightLabel="수소이온농도(pH)" + rightControl={ +
+ setPh((p) => ({ ...p, min: v }))} + className="w-32" + /> + ~ + setPh((p) => ({ ...p, max: v }))} + className="w-32" + /> +
+ } + /> + + + {/* 4행 */} + + 생물화학적산소요구량 +
+ (BOD)(mg/L) + + } + leftRequired + leftControl={} + rightLabel="염화물(mgCL/L)" + rightControl={} + /> + + + {/* 5행 */} + setOdor(e.target.value)} + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + + + + + } + rightLabel="전기전도도(µs/cm)" + rightControl={} + /> + + + {/* 6행 */} + } + rightLabel="비고" + rightControl={ +