This commit is contained in:
2026-05-11 22:25:42 +09:00
386 changed files with 27266 additions and 5800 deletions
+1 -2
View File
@@ -18,8 +18,7 @@ NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y}
NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
# NEXT_PUBLIC_GATEWAY_URL=http://localhost:8883 # 개발 서버 API
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
@@ -0,0 +1,242 @@
"use client";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
export type AnchorSection = {
id: string;
label: string;
};
export type BusinessSubNavMenu = {
key: string;
label: string;
href?: string;
current?: boolean;
defaultOpen?: boolean;
sections?: AnchorSection[];
};
type BusinessSubNavProps = {
menus?: BusinessSubNavMenu[];
offsetTop?: number;
className?: string;
};
export default function BusinessSubNav({
menus = [],
offsetTop = 120,
className = "",
}: BusinessSubNavProps) {
const [activeId, setActiveId] = useState("");
const [openMenus, setOpenMenus] = useState<Record<string, boolean>>({});
const allSections = useMemo(
() => menus.flatMap((menu) => menu.sections ?? []),
[menus]
);
useEffect(() => {
const initialOpenState: Record<string, boolean> = {};
menus.forEach((menu) => {
initialOpenState[menu.key] = menu.defaultOpen ?? menu.current ?? false;
});
setOpenMenus(initialOpenState);
}, [menus]);
useEffect(() => {
if (!allSections.length) return;
const observer = new IntersectionObserver(
(entries) => {
const visible = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
if (visible[0]) {
setActiveId(visible[0].target.id);
}
},
{
rootMargin: `-${offsetTop}px 0px -55% 0px`,
threshold: [0.2, 0.35, 0.5, 0.7],
}
);
const targets = allSections
.map((section) => document.getElementById(section.id))
.filter(Boolean) as HTMLElement[];
targets.forEach((el) => observer.observe(el));
const hash = window.location.hash.replace("#", "");
const initialActive =
allSections.find((section) => section.id === hash)?.id ??
allSections[0]?.id ??
"";
setActiveId(initialActive);
return () => observer.disconnect();
}, [allSections, offsetTop]);
useEffect(() => {
if (!activeId) return;
const parentMenu = menus.find((menu) =>
(menu.sections ?? []).some((section) => section.id === activeId)
);
if (!parentMenu) return;
setOpenMenus(() => {
const nextOpenState: Record<string, boolean> = {};
menus.forEach((menu) => {
nextOpenState[menu.key] = menu.key === parentMenu.key;
});
return nextOpenState;
});
}, [activeId, menus]);
const handleMove = (id: string) => {
setActiveId(id);
const target = document.getElementById(id);
if (!target) return;
window.history.replaceState(null, "", `#${id}`);
const top = target.getBoundingClientRect().top + window.scrollY - offsetTop;
window.scrollTo({
top,
behavior: "smooth",
});
};
const toggleMenu = (key: string) => {
setOpenMenus((prev) => ({
...prev,
[key]: !prev[key],
}));
};
if (!menus.length) return null;
return (
<aside className={`fixed right-2 top-[110px] z-50 w-[260px] ${className}`}>
<div className="overflow-hidden bg-white">
{menus.map((menu) => {
const isOpen = openMenus[menu.key] ?? false;
const hasChildren = (menu.sections?.length ?? 0) > 0;
const isCurrentGroup = (menu.sections ?? []).some(
(section) => section.id === activeId
);
return (
<div
key={menu.key}
>
<div className="flex items-center justify-between px-5 py-2">
{menu.href ? (
<Link
href={menu.href}
className={[
"text-[16px] font-medium tracking-[-0.02em]",
isCurrentGroup || menu.current
? "text-sky-500"
: "text-[#767676]",
].join(" ")}
>
{menu.label}
</Link>
) : (
<button
type="button"
onClick={() => {
if (hasChildren) toggleMenu(menu.key);
}}
className={[
"text-left text-[16px] font-medium tracking-[-0.02em]",
isCurrentGroup || menu.current
? "text-sky-500"
: "text-[#767676]",
].join(" ")}
>
{menu.label}
</button>
)}
{hasChildren && (
<button
type="button"
onClick={() => toggleMenu(menu.key)}
className={[
"ml-2 shrink-0",
isCurrentGroup || menu.current
? "text-sky-500"
: "text-[#767676]",
].join(" ")}
aria-label={`${menu.label} 드롭다운 토글`}
>
{isOpen ? (
<ChevronUp className="h-5 w-5" />
) : (
<ChevronDown className="h-5 w-5" />
)}
</button>
)}
</div>
{hasChildren && isOpen && (
<div className="relative px-5 pb-5">
<div className="absolute left-[31px] top-[6px] bottom-[14px] w-[2px] bg-[#CDD5DB]" />
<ul className="space-y-4 pl-2">
{menu.sections!.map((section) => {
const active = activeId === section.id;
return (
<li key={section.id}>
<button
type="button"
onClick={() => handleMove(section.id)}
className="flex items-start gap-3 text-left"
>
<span
className={[
"relative z-[1] mt-[6px] -ml-[2px] h-[11px] w-[11px] shrink-0 rounded-full border",
active
? "border-sky-500 bg-sky-500"
: "border-[#BFC7CF] bg-white",
].join(" ")}
/>
<span
className={[
"break-keep text-[14px] leading-[1.45] transition-colors",
active
? "text-[#4A4A4A]"
: "text-[#D1D7DE] hover:text-[#7B8794]",
].join(" ")}
>
{section.label}
</span>
</button>
</li>
);
})}
</ul>
</div>
)}
</div>
);
})}
</div>
</aside>
);
}
@@ -9,7 +9,7 @@ import { useRouter } from "next/navigation";
const NAV_ITEMS = [
{ href: "/reuse/intro/law", label: "물 재이용 소개" },
{ href: "/reuse/cases/rain", label: "물 재이용 현황" },
{ href: "/reuse/business/rain/suwon", label: "물 재이용 사례" },
{ href: "/reuse/business/rain", label: "물 재이용 사례" },
{ href: "/reuse/apply/place", label: "수요신청" },
];
@@ -1,10 +1,12 @@
// apps/dm/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx (유사사례 모달)
/**
* 유사사례 Modal
*/
"use client";
import { useEffect, useMemo, useState, useId } from "react";
import { createPortal } from "react-dom";
type Criterion = "" | "supplyAmt" | "treatment" | "usePurpose";
type Criterion = "" | "supplyAmt" | "treatment" | "usePurpose"; // 유사기준
export type SimilarCaseRow = {
caseId: string;
@@ -90,7 +92,7 @@ export default function SimilarCaseModal({
// 모달 열릴 때 초기화
useEffect(() => {
if (!open) return;
setCriterion("supplyAmt");
setCriterion("supplyAmt"); // 요구수량(㎥/d)
onSearch("supplyAmt");
setSelectedCaseId("");
}, [open]);
@@ -0,0 +1,116 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function GlobalCaseSection() {
const title = "해외 재이용 사례";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
<section id="example-global-overview" className="scroll-mt-[120px]">
<SectionTitle>사례 개요</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/globalCase/overview_01.png",
alt: "해외 재이용 사례 개요 1",
},
]}
/>
</section>
<Hr className="my-16" />
<section id="example-global-taiwan" className="scroll-mt-[120px]">
<SectionTitle>대만</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/globalCase/taiwan_01.png",
alt: "대만 사례 1",
},
]}
/>
</section>
<Hr className="my-16" />
<section id="example-global-australia" className="scroll-mt-[120px]">
<SectionTitle>호주</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/globalCase/australia_01.png",
alt: "호주 사례 1",
},
]}
/>
</section>
<Hr className="my-16" />
<section id="example-global-singapore" className="scroll-mt-[120px]">
<SectionTitle>싱가포르</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/globalCase/singapore_01.png",
alt: "싱가포르 사례 1",
},
]}
/>
</section>
<Hr className="my-16" />
<section id="example-global-eu" className="scroll-mt-[120px]">
<SectionTitle>EU</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/globalCase/eu_01.png",
alt: "EU 사례 1",
},
]}
/>
</section>
</div>
</div>
);
}
@@ -0,0 +1,191 @@
"use client";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="inline-block h-[14px] w-[24px] rounded-full bg-gradient-to-r from-sky-300 to-lime-300" />
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type SupplyRow = {
facility: string;
usage: string;
amount: string;
};
function SupplyTable({ rows }: { rows: SupplyRow[] }) {
return (
<div className="mt-8 overflow-x-auto">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-2 text-[18px] font-medium text-white">
시설명
</th>
<th className="border border-[#D8DDE3] bg-[#40B5A5] px-4 py-2 text-[18px] font-medium text-white">
이용용도
</th>
<th className="border border-[#D8DDE3] bg-[#73BF4B] px-4 py-2 text-[18px] font-medium text-white">
공급량(㎥)
</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={`${row.facility}-${index}`}>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.facility}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.usage}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.amount}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function InfoList({
projectName,
purpose,
totalCost,
treatment,
}: {
projectName: string;
purpose: string;
totalCost: string;
treatment: string;
}) {
return (
<div className="mt-10 space-y-10">
<div>
<BlockTitle>사업명</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>{projectName}</li>
</ul>
</div>
<div>
<BlockTitle>사업목적</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>{purpose}</li>
</ul>
</div>
<div>
<BlockTitle>사업 주요 내용</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>총사업비 : {totalCost}</li>
<li>처리공법 : {treatment}</li>
</ul>
</div>
<div>
<BlockTitle>사업량</BlockTitle>
</div>
</div>
);
}
export default function GreywaterExampleSection() {
const title = "중수 재이용 사례";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
<section id="example-greywater-ansan" className="scroll-mt-[120px]">
<SectionTitle>경기도 안산시</SectionTitle>
<InfoList
projectName="안산시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="81억원"
treatment="MBR공법"
/>
<SupplyTable
rows={[
{ facility: "경기테크노파크", usage: "청소·화장실용수", amount: "40㎥/일" },
{ facility: "올림픽수영장", usage: "청소·화장실/세척·살수용수", amount: "35㎥/일" },
{ facility: "와스타디움", usage: "청소·화장실/조경용수", amount: "90㎥/일" },
{ facility: "호수실내수영장", usage: "청소·화장실/조경/세척·살수용수", amount: "50㎥/일" },
{ facility: "한양대학교 에리카캠퍼스", usage: "청소·화장실용수", amount: "370㎥/일" },
]}
/>
</section>
<Hr className="my-20" />
<section id="example-greywater-yongin" className="scroll-mt-[120px]">
<SectionTitle>경기도 용인시</SectionTitle>
<InfoList
projectName="용인시 중수도 설치사업"
purpose="물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용"
totalCost="40억원"
treatment="MBR공법"
/>
<SupplyTable
rows={[
{ facility: "여성회관", usage: "청소·화장실용수", amount: "35㎥/일" },
{ facility: "수지아르피아", usage: "청소·화장실용수", amount: "35㎥/일" },
{ facility: "지상작전사령부", usage: "청소·화장실/조경/세척·살수용수", amount: "53㎥/일" },
]}
/>
</section>
<Hr className="my-20" />
<section id="example-greywater-pyeongtaek" className="scroll-mt-[120px]">
<SectionTitle>경기도 평택시</SectionTitle>
<InfoList
projectName="평택시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="21억원"
treatment="MBR공법"
/>
<SupplyTable
rows={[
{
facility: "평택대학교",
usage: "청소·화장실용수",
amount: "50㎥/일",
},
]}
/>
</section>
</div>
</div>
);
}
@@ -0,0 +1,72 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function RainExampleSection() {
const title = "빗물 재이용 사례";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<section id="example-rain-suwon" className="mt-10 scroll-mt-[120px]">
<SectionTitle>경기도 수원시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/rain/rain_01.png",
alt: "수원시 사례 1",
},
{
src: "/images/landing/business/rain/rain_02.png",
alt: "수원시 사례 2",
},
]}
/>
</section>
<Hr className="my-16" />
<section id="example-rain-jeju" className="scroll-mt-[120px]">
<SectionTitle>제주특별자치도</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/rain/jeju_01.png",
alt: "제주 사례 1",
},
{
src: "/images/landing/business/rain/jeju_02.png",
alt: "제주 사례 2",
},
]}
/>
</section>
</div>
);
}
@@ -0,0 +1,256 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function SewageExampleSection() {
const title = "하수처리수 재이용 사례";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<section id="example-sewage-bolyeong" className="scroll-mt-[120px]">
<SectionTitle>충청남도 보령시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/bolyeong_01.png",
alt: "보령시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
충남 보령 하수재이용
<br />: 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해
보령하수처리시설의 하수처리수 재이용 사업 추진
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업 방식</SectionTitle>
<p className="mt-8 text-base text-gray-600">
2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결
</p>
</section>
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
처리공정 : 전처리 UF + 후처리 RO
<br />
시설용량 : 1만m³/일
<br />
관로 : 8.7km
<br />
사업비 : 315억원
<br />
위치: 충청남도 보령시 대청방조제로 322 일원
</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<p className="mt-8 text-base text-gray-600">
국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권
가뭄 시 지역사회 기여 차원에서 기관간 MOU를 바탕으로 추진
</p>
</section>
</section>
<Hr className="my-16" />
<section id="example-sewage-cheongju" className="scroll-mt-[120px]">
<SectionTitle>청주시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/cheongju_01.png",
alt: "청주시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">청주시 하수 재이용 시설</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
청주시 하수처리수 재이용 민간투자사업(BTO)
<br />
공급량 : 35,000㎥/일
<br />
공급관로 : 8.3㎞
<br />
사업비 : 622억원
<br />
수요처 : SK하이닉스 등 일반산업단지
<br />
지역 : 충청북도 청주시 흥덕구 가락리 135-9번지 일원
<br />
적용기술 : 분리막 UF, RO
</p>
</section>
</section>
<Hr className="my-16" />
<section id="example-sewage-guri" className="scroll-mt-[120px]">
<SectionTitle>구리시 도로자동청소시스템</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/guri_01.png",
alt: "구리시 도로자동청소시스템 1",
},
{
src: "/images/landing/business/sewage/guri_02.png",
alt: "구리시 도로자동청소시스템 2",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
구리시 도로자동청소시스템 설치사업
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여
미세먼지 저감 및 폭염 취약성 완화
</p>
</section>
</section>
<Hr className="my-16" />
<section id="example-sewage-hwanggu" className="scroll-mt-[120px]">
<SectionTitle>수원시 황구지천</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/hwanggu_01.png",
alt: "수원시 황구지천 하수 재이용 1",
},
{
src: "/images/landing/business/sewage/hwanggu_02.png",
alt: "수원시 황구지천 하수 재이용 2",
},
{
src: "/images/landing/business/sewage/hwanggu_03.png",
alt: "수원시 황구지천 하수 재이용 3",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
수원시 황구지천 하수처리수 재이용사업
</p>
</section>
</section>
<Hr className="my-16" />
<section id="example-sewage-paju" className="scroll-mt-[120px]">
<SectionTitle>파주시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/paju_01.png",
alt: "파주시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
경기도 파주시 하수재이용
<br />
운정·금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주 LCD
산업단지에 공업용수 공급
</p>
</section>
</section>
<Hr className="my-16" />
<section id="example-sewage-yeosu" className="scroll-mt-[120px]">
<SectionTitle>여수시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/yeosu_01.png",
alt: "여수시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
여수시 하수처리수 재이용 민간 투자사업(BTO)
</p>
</section>
</section>
</div>
</div>
);
}
@@ -1,16 +1,71 @@
// apps/dm/app/(landing)/reuse/business/layout.tsx
"use client";
import type { ReactNode } from "react";
import Lnb from "../_components/Lnb";
import { LNB_BUSINESS } from "../_constants/lnb";
import { useEffect } from "react";
// import { usePathname } from "next/navigation";
// import Lnb from "../_components/Lnb";
// import BusinessSubNav from "../_components/BusinessSubNav";
import {
// fetchAnonymousMenu,
// findActiveTopMenu,
// type MenuItem,
} from "@/app/store/common";
export default function BusinessLayout({ children }: { children: ReactNode }) {
// const pathname = usePathname();
// const [menus, setMenus] = useState<MenuItem[]>([]);
useEffect(() => {
let isMounted = true;
(async () => {
try {
// const loaded = await fetchAnonymousMenu();
if (!isMounted) return;
// setMenus(loaded);
} catch {
if (!isMounted) return;
// setMenus([]);
}
})();
return () => {
isMounted = false;
};
}, []);
// const currentTopMenu = useMemo(
// () => findActiveTopMenu(menus, pathname),
// [menus, pathname],
// );
// const businessMenus = useMemo(
// () => currentTopMenu?.children ?? [],
// [currentTopMenu],
// );
return (
<div className="flex gap-0">
<Lnb title="사례" items={LNB_BUSINESS} />
<main className="flex-1 px-[100px] pt-[80px]">
{children}
</main>
<div className="bg-white min-w-[1440px]">
{/* 좌측 LNB */}
{/* <Lnb /> */}
{/* 컨텐츠 영역 */}
<div className="pl-[260px] pt-[83px]">
<div className="mx-auto flex max-w-[1660px] gap-16 px-[60px] py-[60px]">
{/* 본문 */}
<main className="min-w-0 flex-1 max-w-[980px]">
{children}
</main>
{/* 우측 서브 메뉴 */}
<div className="w-[240px] shrink-0">
{/* <BusinessSubNav menus={businessMenus} /> */}
</div>
</div>
</div>
</div>
);
}
}
@@ -0,0 +1,85 @@
import SubPageHeader from "@/components/SubPageHeader";
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import RainExampleSection from "./_components/RainExampleSection";
import GreywaterExampleSection from "./_components/GreywaterExampleSection";
import SewageExampleSection from "./_components/SewageExampleSection";
import GlobalCaseSection from "./_components/GlobalCaseSection";
const menus: BusinessSubNavMenu[] = [
{
key: "example-rain",
label: "빗물 재이용 사례",
sections: [
{ id: "example-rain-suwon", label: "경기도 수원시" },
{ id: "example-rain-jeju", label: "제주특별자치도" },
],
},
{
key: "example-greywater",
label: "중수 재이용 사례",
defaultOpen: false,
sections: [
{ id: "example-greywater-ansan", label: "경기도 안산시" },
{ id: "example-greywater-yongin", label: "경기도 용인시" },
{ id: "example-greywater-pyeongtaek", label: "경기도 평택시" },
],
},
{
key: "example-sewage",
label: "하수처리수 재이용 사례",
defaultOpen: false,
sections: [
{ id: "example-sewage-bolyeong", label: "충청남도 보령시" },
{ id: "example-sewage-cheongju", label: "청주시" },
{ id: "example-sewage-guri", label: "구리시 도로자동청소시스템" },
{ id: "example-sewage-hwanggu", label: "수원시 황구지천" },
{ id: "example-sewage-paju", label: "파주시" },
{ id: "example-sewage-yeosu", label: "여수시" },
],
},
{
key: "example-global",
label: "해외 재이용 사례",
defaultOpen: false,
sections: [
{ id: "example-global-overview", label: "사례 개요" },
{ id: "example-global-taiwan", label: "대만" },
{ id: "example-global-australia", label: "호주" },
{ id: "example-global-singapore", label: "싱가포르" },
{ id: "example-global-eu", label: "EU" },
],
},
];
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function ExamplePage() {
return (
<div className="relative">
{/* 상단 헤더 */}
<SubPageHeader
title="물 재이용 사례"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 사례" },
]}
/>
{/* 우측 앵커 네비 */}
<BusinessSubNav menus={menus} offsetTop={120} />
<div className="max-w-[1300px] pb-24">
<RainExampleSection />
<Hr className="my-16" />
<GreywaterExampleSection />
<Hr className="my-16" />
<SewageExampleSection />
<Hr className="my-16" />
<GlobalCaseSection />
</div>
</div>
);
}
@@ -0,0 +1,461 @@
"use client";
import React from "react";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 환경부(2024), 「2023 하수도 통계」
</p>
);
}
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
export default function GreywaterStatusSection() {
const title = "중수도(현황)";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
{/* 1) 전체 중수도 설치 및 이용 현황 */}
<section
id="status-greywater-overall"
className="mt-10 scroll-mt-[120px]"
>
<SectionTitle>전체 중수도 설치 및 이용 현황</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "576", "633", "682", "740", "819", "842"],
["건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", "1,494.2", "1,514.4", "1,524.9", "989.1", "1,104.3", "1,015.4"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "39.6", "40.0", "51.1", "47.1", "48.9", "50.7"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "2,223.4", "1,165.2", "6,713.0", "4,681.7", "3,918.2", "898.4"],
],
},
{
group: "법적 대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "146", "154", "156", "170", "166", "169"],
["건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", "674.9", "682.4", "736.1", "421.1", "434.9", "465.5"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "20.0", "20.1", "31.2", "22.8", "21.2", "24.7"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "1,819.4", "712.8", "6,479.9", "4,416.8", "3,663.1", "616.1"],
],
},
{
group: "법적 비대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "430", "479", "526", "570", "653", "673"],
["건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", "819.3", "832.0", "788.8", "568.0", "669.4", "549.9"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "19.6", "19.5", "19.9", "24.3", "27.7", "26.0"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "404.0", "452.4", "233.1", "264.9", "255.1", "282.3"],
],
},
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${
isFirstCol ? "!font-bold !text-neutral-900" : ""
} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 2) 공장 및 발전시설 내 중수도 설치 현황 */}
<section
id="status-greywater-factory"
className="scroll-mt-[120px]"
>
<SectionTitle>공장 및 발전시설 내 중수도 설치 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "125", "127", "139", "146", "151", "176"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "141.3", "141.0", "146.7", "189.0", "190.3", "166.5"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "33,712", "29,300", "33,110", "22,284", "21,593", "21,349"],
],
},
{
group: "법적 대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "59", "63", "78", "78", "82", "86"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "108.6", "109.7", "118.1", "159.5", "142.6", "119.4"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "24,847", "24,021", "28,055", "17,959", "12,241", "12,296"],
],
},
{
group: "법적 비대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "66", "64", "61", "68", "69", "90"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "32.7", "31.4", "28.7", "29.5", "47.7", "47.1"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "8,864.9", "5,278.8", "5,054.7", "4,324.6", "9,353.3", "9,052.4"],
],
},
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${
isFirstCol ? "!font-bold !text-neutral-900" : ""
} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 3) 중수도 건축물별 이용량 현황 */}
<section
id="status-greywater-building"
className="scroll-mt-[120px]"
>
<SectionTitle>중수도 건축물별 이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
이용량 (천m³/년)
</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
["공동주택", "804.0", "245.8", "255.1", "282.3", "255.1", "282.3"],
["판매시설", "1,447.5", "477.1", "802.1", "713.0", "972.3", "742.4"],
["숙박시설", "458.5", "0.4", "177.0", "102.0", "123.3", "140.9"],
["업무시설", "20.7", "0.3", "14.3", "44.9", "31.5", "37.4"],
["의료시설", "97.7", "7.2", "63.7", "55.7", "85.1", "70.4"],
["교육연구시설", "536.6", "1.5", "1,558.4", "772.2", "315.1", "66.7"],
["운수·물류시설", "759.2", "34.2", "459.8", "259.8", "252.8", "410.4"],
["기타", "453.1", "443.3", "3,126.8", "2,451.5", "1,880.8", "199.7"],
["합계", "35,936.3", "30,466.6", "37,487.2", "25,480.6", "24,345.8", "22,247.6"],
].map((row, idx) => (
<tr
key={idx}
className={idx === 8 ? "bg-white font-bold" : "bg-white"}
>
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${
i === 0 ? "!font-bold !text-neutral-900" : ""
} ${[1, 3, 5].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 4) 중수도 용도별 이용량 현황 */}
<section
id="status-greywater-usage"
className="scroll-mt-[120px]"
>
<SectionTitle>중수도 용도별 이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
이용량 (천m³/년)
</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
["공업용수", "33,082", "28,719", "32,312", "21,707", "20,991", "20,470"],
["세척·살수용수", "860.2", "146.5", "133.8", "100.7", "95.1", "149.9"],
["조경용수", "403.7", "99.3", "3,297.0", "2,611.1", "1,827.0", "1,116.7"],
["청소용수", "623.9", "764.2", "1,430.6", "879.7", "1,146.2", "286.0"],
["수세식화장실용수", "530.0", "646.6", "183.5", "161.2", "216.6", "147.6"],
["냉·난방용수", "150.1", "56.2", "57.5", "6.4", "0.9", "0.5"],
["친수용수", "120.8", "18.5", "29.3", "8.2", "4.1", "2.8"],
["기타", "165.6", "16.1", "43.2", "5.7", "64.0", "73.9"],
].map((row, idx) => (
<tr key={idx} className="bg-white">
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${
i === 0 ? "!font-bold !text-neutral-900" : ""
} ${[1, 3, 5].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
@@ -0,0 +1,381 @@
"use client";
import React from "react";
// 공통 섹션 타이틀 컴포넌트
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// 출처 텍스트
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 환경부(2024), 「2023 하수도 통계」
</p>
);
}
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
export default function RainStatusSection() {
const title = "빗물이용시설(현황)";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
{/* 1) 빗물이용시설의 건축물별 빗물이용량 현황 */}
<section id="status-rain-building" className="mt-10 scroll-mt-[120px]">
<SectionTitle>빗물이용시설의 건축물별 빗물이용량 현황</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
style={{ width: "160px" }}
>
구분
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
빗물이용량 (천m³/년)
</th>
</tr>
<tr>
<th
className={`${tableStyles.th} bg-[#01ACE5] text-white`}
style={{ width: "140px" }}
>
2018
</th>
<th
className={`${tableStyles.th} bg-[#17AFC7] text-white`}
style={{ width: "140px" }}
>
2019
</th>
<th
className={`${tableStyles.th} bg-[#2CB2A9] text-white`}
style={{ width: "140px" }}
>
2020
</th>
<th
className={`${tableStyles.th} bg-[#42B58B] text-white`}
style={{ width: "140px" }}
>
2021
</th>
<th
className={`${tableStyles.th} bg-[#57B86D] text-white`}
style={{ width: "140px" }}
>
2022
</th>
<th
className={`${tableStyles.th} bg-[#81C784] text-white`}
style={{ width: "140px" }}
>
2023
</th>
</tr>
</thead>
<tbody>
{[
["골프장", "6,771.8", "6,417.4", "8,371.6", "10,215.4", "11,360", "10,191.6"],
["공공시설", "364", "325.2", "201.9", "244.6", "209.9", "299.6"],
["공동주택", "291", "840.5", "804.2", "2,393.0", "2,386", "2,428.4"],
["대규모점포", "169.9", "214.4", "58.4", "427.2", "371", "293.3"],
["체육시설", "52.6", "44.7", "48.6", "75.3", "71.8", "77.6"],
["학교", "33", "25.5", "22", "155.8", "165.4", "32.6"],
["기타", "311.6", "901.2", "344.5", "537.8", "564", "621.2"],
].map((row, idx) => (
<tr key={idx} className="bg-white">
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${
i === 0 ? "!font-bold !text-neutral-900" : ""
} ${[1, 3, 5].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 2) 빗물이용시설의 용도별 분포 (2023년 기준) */}
<section
id="status-rain-purpose-distribution"
className="scroll-mt-[120px]"
>
<SectionTitle>빗물이용시설의 용도별 분포 (2023년 기준)</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
style={{ width: "160px" }}
>
용도
</th>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
style={{ width: "84px" }}
>
개소수
<br />
(개소)
</th>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
style={{ width: "84px" }}
>
저류용량
<br />
(천m³)
</th>
<th
colSpan={8}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
빗물이용량 (천m³/년)
</th>
</tr>
<tr>
<th
className={`${tableStyles.th} bg-[#01ACE5] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
총사용량
</th>
<th
className={`${tableStyles.th} bg-[#10AED0] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
공공시설
</th>
<th
className={`${tableStyles.th} bg-[#20B0BA] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
공동주택
</th>
<th
className={`${tableStyles.th} bg-[#2FB2A5] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
대규모점포
</th>
<th
className={`${tableStyles.th} bg-[#3FB58F] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
체육시설
</th>
<th
className={`${tableStyles.th} bg-[#4EB77A] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
학교
</th>
<th
className={`${tableStyles.th} bg-[#5EB964] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
골프장
</th>
<th
className={`${tableStyles.th} bg-[#6DBB4F] py-2 text-[13px] text-white`}
style={{ width: "84px" }}
>
기타
</th>
</tr>
</thead>
<tbody>
{[
["청소·환경정화용수", "195", "32.7", "108.8", "24.9", "5.8", "2.6", "4.4", "2.9", "-", "68.1"],
["세척·살수용수", "12", "1.4", "37", "35", "-", "-", "1.9", "-", "-", "0.2"],
["조경용수", "3,165", "7,440.8", "13,554.1", "168.1", "2,421.9", "290.7", "66.2", "29", "10,044", "534.3"],
["냉·난방수", "8", "1.6", "0.1", "-", "-", "-", "-", "0.1", "-", "-"],
["공업용수", "6", "14.9", "53.2", "52.8", "-", "-", "-", "-", "-", "0.4"],
["농업용수", "6.0", "0.5", "0.6", "-", "-", "-", "-", "-", "-", "0.6"],
["기타", "48", "306.1", "155.6", "2.8", "-", "-", "0.6", "0.5", "147.5", "4.2"],
["미기재", "103", "22.9", "34.8", "16.0", "0.6", "-", "4.6", "0.2", "-", "13.5"],
["합계", "3,543", "7,820.8", "13,944.7", "299.6", "2,428.4", "293.3", "77.6", "32.6", "10,191.6", "621.2"],
].map((row, idx) => (
<tr
key={idx}
className={idx === 8 ? "bg-white font-bold" : "bg-white"}
>
{row.map((val, i) => {
const isHighlightedCol = [1, 2].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${
i === 0 ? "!font-bold !text-neutral-900" : ""
} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 3) 빗물이용시설의 용도별 빗물이용량 현황 */}
<section
id="status-rain-purpose-usage"
className="scroll-mt-[120px]"
>
<SectionTitle>빗물이용시설의 용도별 빗물이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
style={{ width: "160px" }}
>
용도
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
빗물이용량 (천m³/년)
</th>
</tr>
<tr>
<th
className={`${tableStyles.th} bg-[#01ACE5] text-white`}
style={{ width: "140px" }}
>
2018
</th>
<th
className={`${tableStyles.th} bg-[#14AFCD] text-white`}
style={{ width: "140px" }}
>
2019
</th>
<th
className={`${tableStyles.th} bg-[#28B2B4] text-white`}
style={{ width: "140px" }}
>
2020
</th>
<th
className={`${tableStyles.th} bg-[#3CB59A] text-white`}
style={{ width: "140px" }}
>
2021
</th>
<th
className={`${tableStyles.th} bg-[#50B880] text-white`}
style={{ width: "140px" }}
>
2022
</th>
<th
className={`${tableStyles.th} bg-[#6DBB4F] text-white`}
style={{ width: "140px" }}
>
2023
</th>
</tr>
</thead>
<tbody>
{[
["청소·화장실용수", "126.5", "134.1", "96.5", "153.9", "140.5", "108.8"],
["세척·살수용수", "14.8", "14.6", "9.5", "49.8", "49.7", "37.0"],
["조경용수", "7,629.8", "8,496.2", "9,415.4", "13,360.4", "14,466.3", "13,554.1"],
["친수용수", "1.0", "0.1", "0.1", "0.1", "0.1", "0.1"],
["공업용수", "152.0", "77.0", "77.0", "75.4", "0.4", "53.2"],
["농업용수", "4.4", "4.3", "4.3", "4.3", "0.6", "0.6"],
["기타", "37.7", "42.6", "230.7", "394.3", "463.2", "155.6"],
].map((row, idx) => (
<tr key={idx} className="bg-white">
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${
i === 0 ? "!font-bold !text-neutral-900" : ""
} ${[1, 3, 5].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
@@ -0,0 +1,705 @@
"use client";
import React from "react";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 환경부(2024), 「2023 하수도 통계」
</p>
);
}
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
export default function SewageStatusSection() {
const title = "하수처리수 재이용시설(현황)";
return (
<div className="relative bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
{/* 1) 연도별 하수처리수 재이용 현황 */}
<section id="status-treated-year" className="mt-10 scroll-mt-[120px]">
<SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "180px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
[
"개소수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
"671",
"681",
"698",
"704",
"714",
"717",
],
[
"하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
"7,163.7",
"7,140.4",
"7,375.6",
"7,335.8",
"7,378.9",
"7,537.2",
],
[
"재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
"1,112.7",
"1,148.7",
"1,148.7",
"1,127.0",
"1,136.8",
"1,137.5",
],
[
"재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>",
"17.4",
"16.5",
"15.5",
"14.1",
"14.3",
"14.4",
],
],
},
{
group: "법적 대상",
rows: [
[
"개소수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
"68",
"82",
"94",
"97",
"101",
"118",
],
[
"하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
"761.2",
"839.0",
"935.3",
"1,158.7",
"1,220.6",
"1,473.6",
],
[
"재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
"162.2",
"190.6",
"218.5",
"244.6",
"247.2",
"324.9",
],
[
"재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>",
"24.7",
"25.4",
"27.8",
"25.7",
"24.2",
"24.1",
],
],
},
{
group: "법적 미대상",
rows: [
[
"개소수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
"603",
"599",
"604",
"607",
"613",
"599",
],
[
"하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
"6,402.5",
"6,301.4",
"6,440.3",
"6,177.1",
"6,158.3",
"6,063.5",
],
[
"재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
"950.6",
"958.1",
"926.2",
"882.4",
"889.5",
"812.7",
],
[
"재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>",
"16.5",
"15.3",
"13.6",
"12.2",
"12.6",
"12.5",
],
],
},
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${
isFirstCol ? "!font-bold !text-neutral-900" : ""
} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 2) 용도별 하수처리수 재이용 현황 */}
<section id="status-treated-usage" className="scroll-mt-[120px]">
<SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "180px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th className={`${tableStyles.th} ${tableStyles.headerBg}`}>
구분
</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
{ label: "총 재이용량", type: "total", values: ["1,112.7", "1,148.7", "1,144.7", "1,127.0", "1,136.8", "1,137.5"] },
{ label: "장내 재이용", type: "category", values: ["520.6", "519.8", "513.9", "520.3", "502.2", "490.0"] },
{ label: "세척수", type: "red-item", values: ["177.9", "174.4", "169.4", "180.9", "196.8", "191.2"] },
{ label: "냉각수", type: "red-item", values: ["45.5", "46.8", "50.4", "40.6", "38.3", "41.7"] },
{ label: "청소수", type: "red-item", values: ["17.6", "21.2", "21.2", "21.7", "20.8", "20.5"] },
{ label: "조경용수", type: "red-item", values: ["0.1", "0.1", "-", "-", "-", "-"] },
{ label: "회생용수", type: "red-item", values: ["15.8", "16.7", "17.8", "15.7", "14.4", "14.0"] },
{ label: "장내급수도", type: "red-item", values: ["2.4", "-", "-", "-", "-", "-"] },
{ label: "장내기타용수", type: "red-item", values: ["261.3", "260.6", "255.0", "261.1", "231.8", "222.6"] },
{ label: "장외 재이용", type: "category", values: ["592.1", "628.9", "630.8", "606.7", "634.6", "647.5"] },
{ label: "청소·화장실용수", type: "blue-item", values: ["0.6", "0.4", "0.4", "0.4", "0.4", "0.4"] },
{ label: "세척·살수용수", type: "blue-item", values: ["5.6", "9.9", "2.9", "2.1", "3.0", "3.6"] },
{ label: "조경용수", type: "blue-item", values: ["10.3", "16.4", "5.9", "4.5", "8.9", "9.5"] },
{ label: "친수용수", type: "blue-item", values: ["7.8", "3.1", "3.1", "3.0", "10.9", "10.5"] },
{ label: "하천유지용수", type: "blue-item", values: ["480.5", "482.5", "496.2", "442.7", "471.7", "484.0"] },
{ label: "농업용수", type: "blue-item", values: ["12.2", "11.9", "13.4", "10.7", "10.6", "11.4"] },
{ label: "지하수 충전", type: "blue-item", values: ["0.0", "0.0", "0.0", "13.4", "0.0", "0.0"] },
{ label: "공업용수", type: "blue-item", values: ["75.2", "104.6", "108.9", "129.9", "129.0", "128.1"] },
].map((row, idx) => {
const isSubItem = row.type === "red-item" || row.type === "blue-item";
const isLastInGroup = idx === 8 || idx === 17;
return (
<tr key={idx} className="bg-white">
<td className={`${tableStyles.td} relative`}>
{isSubItem ? (
<>
<div
className={`absolute left-[1px] w-[16px] bg-white z-10 top-[-2px] ${
isLastInGroup ? "bottom-[0px]" : "bottom-[-2px]"
}`}
/>
<div
className={`absolute left-[16px] w-[4px] z-20 ${
row.type === "red-item" ? "bg-[#FF8B8B]" : "bg-[#4ECBFF]"
} top-[-2px] ${
isLastInGroup ? "bottom-[0px]" : "bottom-[-2px]"
}`}
/>
<span className="text-[#4f4f4f] font-bold block w-full text-center relative z-30 translate-x-[8px]">
{row.label}
</span>
</>
) : (
<span className="font-bold text-neutral-900 block w-full text-center">
{row.label}
</span>
)}
</td>
{row.values.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${
[0, 2, 4].includes(i) ? "bg-[#EAF5F5]" : ""
}`}
>
{val}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 3) 하수처리시설 설치 및 이용 현황 */}
<section id="status-treated-facility" className="scroll-mt-[120px]">
<SectionTitle>하수처리시설 설치 및 이용 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>
2018
</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>
2019
</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>
2020
</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>
2021
</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>
2022
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>
2023
</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
[
"시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
"671",
"681",
"698",
"704",
"714",
"717",
],
[
"시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
"25,846",
"26,067",
"26,244",
"26,492",
"26,586",
"26,766",
],
[
"처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
"1,112.7",
"1,148.7",
"1,144.7",
"1,127.0",
"1,136.8",
"1,137.5",
],
],
},
{
group: "법적 대상",
rows: [
[
"시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
"68",
"82",
"94",
"97",
"101",
"118",
],
[
"시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
"3,088.9",
"3,359.2",
"3,612.6",
"4,412.2",
"4,692.3",
"5,503.8",
],
[
"처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
"162.2",
"190.6",
"218.5",
"244.6",
"247.2",
"324.9",
],
],
},
{
group: "법적 미대상",
rows: [
[
"시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
"603",
"599",
"604",
"607",
"613",
"599",
],
[
"시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
"22,757",
"22,708",
"22,631",
"22,080",
"21,894",
"21,262",
],
[
"처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
"950.6",
"958.1",
"926.2",
"882.4",
"889.5",
"812.7",
],
],
},
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${
isFirstCol ? "!font-bold !text-neutral-900" : ""
} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 4) 시도별 하수처리수 재이용 현황 */}
<section
id="status-treated-region"
className="scroll-mt-[120px]"
>
<SectionTitle>시도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<div className="mb-2 text-right text-[13px] text-neutral-600">
(단위: 백만m³/일)
</div>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
rowSpan={3}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
시도
</th>
<th
rowSpan={3}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
연간
<br />
하수
<br />
처리량
</th>
<th
colSpan={7}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
재이용현황
</th>
<th
rowSpan={3}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
재이용률
<br />
(%)
</th>
<th
rowSpan={3}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
비고
</th>
</tr>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
계
</th>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
장내
</th>
<th
colSpan={5}
className={`${tableStyles.th} ${tableStyles.headerBg} py-1`}
>
장외
</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white py-2 text-[16px]`}>
소계
</th>
<th className={`${tableStyles.th} bg-[#1FB1C2] text-white py-2 text-[13px]`}>
하천유지용수
</th>
<th className={`${tableStyles.th} bg-[#3EB59F] text-white py-2 text-[16px]`}>
공업용수
</th>
<th className={`${tableStyles.th} bg-[#5DB97C] text-white py-2 text-[16px]`}>
농업용수
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white py-2 text-[16px]`}>
기타용수
</th>
</tr>
</thead>
<tbody>
{[
["합계", "7,537.2", "1,137.5", "490.0", "647.5", "484.0", "128.1", "11.4", "24.0", "15.1%", ""],
["서울특별시", "1,567.0", "114.9", "59.3", "55.6", "55.1", "-", "-", "0.5", "7.3%", ""],
["부산광역시", "491.5", "146.2", "120.7", "25.5", "25.3", "-", "-", "0.2", "29.7%", ""],
["대구광역시", "432.7", "102.4", "38.2", "64.2", "64.1", "-", "-", "0.1", "23.7%", ""],
["인천광역시", "302.3", "57.7", "11.6", "46.1", "29.2", "12.1", "0.5", "4.3", "19.1%", ""],
["광주광역시", "211.1", "28.7", "6.5", "22.3", "22.2", "-", "-", "0.1", "13.6%", ""],
["대전광역시", "244.6", "11.9", "11.9", "0.68", "0.0", "-", "-", "0.1", "4.9%", ""],
["울산광역시", "235.1", "36.1", "6.1", "29.9", "29.7", "0.2", "-", "-", "15.3%", ""],
["세종특별자치시", "38.5", "2.5", "2.0", "0.5", "0.4", "-", "0.2", "-", "6.6%", ""],
["경기도", "1,926.8", "313.3", "122.2", "191.0", "153.0", "33.3", "0.4", "4.3", "16.3%", ""],
["강원도", "230.4", "19.6", "15.4", "4.2", "2.1", "-", "-", "2.1", "8.5%", ""],
["충청북도", "190.9", "15.6", "6.3", "9.2", "7.4", "0.6", "-", "1.2", "8.2%", ""],
["충청남도", "241.5", "41.4", "9.8", "31.5", "17.7", "8.6", "4.5", "0.8", "17.1%", ""],
["전라북도", "292.4", "42.7", "7.5", "35.1", "33.5", "0.2", "1.0", "0.5", "14.6%", ""],
["전라남도", "254.0", "23.6", "12.3", "11.3", "8.7", "0.0", "1.0", "1.6", "9.3%", ""],
["경상북도", "378.4", "129.1", "33.7", "95.3", "20.8", "72.5", "0.8", "1.3", "34.1%", ""],
["경상남도", "411.3", "50.1", "25.0", "25.0", "14.7", "0.5", "3.0", "6.9", "12.2%", ""],
["제주특별자치도", "88.6", "1.7", "1.5", "0.12", "-", "-", "0.1", "-", "1.9%", ""],
].map((row, idx) => (
<tr key={idx} className="bg-white">
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${
i === 0 ? "!font-bold !text-neutral-900" : ""
} ${[1, 3, 9].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
@@ -0,0 +1,96 @@
// DM 물 재이용 현황 페이지
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import SubPageHeader from "@/components/SubPageHeader";
import RainStatusSection from "./_components/RainStatusSection";
import GreywaterStatusSection from "./_components/GreywaterStatusSection";
import SewageStatusSection from "./_components/SewageStatusSection";
const menus: BusinessSubNavMenu[] = [
{
key: "status-rain",
label: "빗물이용시설(현황)",
sections: [
{
id: "status-rain-building",
label: "빗물이용시설의 건축물별 빗물이용량 현황",
},
{
id: "status-rain-purpose-distribution",
label: "빗물이용시설의 용도별 분포 (2023년 기준)",
},
{
id: "status-rain-purpose-usage",
label: "빗물이용시설의 용도별 빗물이용량 현황",
},
],
},
{
key: "status-greywater",
label: "중수도(현황)",
defaultOpen: false,
sections: [
{
id: "status-greywater-overall",
label: "전체 중수도 설치 및 이용 현황",
},
{
id: "status-greywater-factory",
label: "공장 및 발전시설 내 중수도 설치 현황",
},
{
id: "status-greywater-building",
label: "중수도 건축물별 이용량 현황",
},
{
id: "status-greywater-usage",
label: "중수도 용도별 이용량 현황",
},
],
},
{
key: "status-sewage",
label: "하수처리수 재이용시설(현황)",
defaultOpen: false,
sections: [
{
id: "status-treated-year",
label: "연도별 하수처리수 재이용 현황",
},
{
id: "status-treated-usage",
label: "용도별 하수처리수 재이용 현황",
},
{
id: "status-treated-facility",
label: "하수처리시설 설치 및 이용 현황",
},
{
id: "status-treated-region",
label: "시도별 하수처리수 재이용 현황",
},
],
},
];
export default function StatusPage() {
return (
<div className="relative">
{/* 상단 헤더 */}
<SubPageHeader
title="물 재이용 현황"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 현황", href: "/status" },
]}
/>
{/* 우측 앵커 네비 */}
<BusinessSubNav menus={menus} offsetTop={120} />
<div className="max-w-[1300px] pb-24">
<RainStatusSection />
<GreywaterStatusSection />
<SewageStatusSection />
</div>
</div>
);
}
@@ -0,0 +1,123 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import lawPlan from "@/public/images/landing/intro/law_timeline.png";
import planPerformance from "@/public/images/landing/intro/performance_left.png";
import domesticChange from "@/public/images/landing/intro/domestic_textbox.png";
import policyHistory from "@/public/images/landing/intro/policy_timeline.png";
import lawRelation from "@/public/images/landing/intro/rel_note.png";
import domesticLinkage from "@/public/images/landing/intro/linkage.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function LawIntroSection() {
const title = "물 재이용 법";
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell
id="law-plan"
title="법률 제·개정 및 기본계획 수립"
className="mt-10"
>
<ImgCard src={lawPlan} alt="법률 제·개정 및 기본계획 수립" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="plan-performance"
title="제2차 계획 대비 실적 현황"
>
<ImgCard src={planPerformance} alt="제2차 계획 대비 실적 현황" />
</SectionShell>
<Hr className="my-11" />
<SectionShell id="domestic-change" title="우리나라의 수자원 현황 및 물이용 여건">
<ImgCard src={domesticChange} alt="우리나라의 수자원 현황 및 물이용 여건" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="policy-history"
title="물 재이용 정책 변화 추이"
>
<ImgCard src={policyHistory} alt="물 재이용 정책 변화 추이" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="law-relation"
title="타법령과의 관계 검토"
>
<ImgCard src={lawRelation} alt="타법령과의 관계 검토" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="domestic-linkage"
title="주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요"
>
<ImgCard src={domesticLinkage} alt="주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요" />
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,99 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import rainSummary from "@/public/images/landing/intro/rainreuse_intro.png";
import rainDuty from "@/public/images/landing/intro/rainreuse_mandatory.png";
import rainDesign from "@/public/images/landing/intro/rainwater_design.png";
import rainStandard from "@/public/images/landing/intro/rainwater_standards.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function RainIntroSection() {
const title = "빗물 이용시설";
return (
<div className="mt-[120px] bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell id="rain-intro" title="빗물 재이용 소개" className="mt-10">
<ImgCard src={rainSummary} alt="빗물 재이용 소개" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="rain-mandatory"
title="빗물이용시설 설치 관리 법적 의무 대상"
>
<ImgCard src={rainDuty} alt="빗물이용시설 설치 관리 법적 의무 대상" />
</SectionShell>
<Hr className="my-11" />
<SectionShell id="rain-design" title="빗물이용시설 설계 방안">
<ImgCard src={rainDesign} alt="빗물이용시설 설계 방안" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="rain-standards"
title="빗물이용시설 시설기준 및 관리기준"
>
<ImgCard src={rainStandard} alt="빗물이용시설 시설기준 및 관리기준" />
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,96 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png";
import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png";
import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function ReuseWaterIntroSection() {
const title = "중수도";
return (
<div className="mt-[120px] bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell
id="greywater-intro"
title="중수 재이용 소개"
className="mt-10"
>
<ImgCard src={greywaterIntro} alt="중수 재이용 소개" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="greywater-mandatory"
title="중수도 설치 의무 대상"
>
<ImgCard src={greywaterMandatory} alt="중수도 설치 의무 대상" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="greywater-standard"
title="중수도 시설기준 및 관리기준"
>
<ImgCard src={greywaterStandard} alt="중수도 시설기준 및 관리기준" />
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,89 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png";
import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function TreatedIntroSection() {
const title = "하수처리수";
return (
<div className="mt-[120px] bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell
id="treatedwater-intro"
title="하수처리수 소개"
className="mt-10"
>
<ImgCard src={treatedwaterIntro} alt="하수처리수" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="treatedwater-mandatory"
title="하수처리수 재이용시설 의무 대상"
>
<ImgCard
src={treatedwaterMandatory}
alt="하수처리수 재이용시설 의무 대상"
/>
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,86 @@
// DM 물 재이용 소개 페이지
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import SubPageHeader from "@/components/SubPageHeader";
import LawIntroSection from "./_components/LawIntroSection";
import RainIntroSection from "./_components/RainIntroSection";
import ReuseWaterIntroSection from "./_components/ReuseWaterIntroSection";
import TreatedIntroSection from "./_components/TreatedIntroSection";
const menus: BusinessSubNavMenu[] = [
{
key: "intro-law",
label: "물 재이용 법",
sections: [
{ id: "law-plan", label: "법률 제·개정 및 기본계획 수립" },
{ id: "plan-performance", label: "제2차 계획 대비 실적 현황" },
{ id: "domestic-change", label: "우리나라의 수자원 현황 및 물이용 여건" },
{ id: "policy-history", label: "물 재이용 정책 변화 추이" },
{ id: "law-relation", label: "타법령과의 관계 검토" },
{ id: "domestic-linkage", label: "주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요" },
],
},
{
key: "intro-rain",
label: "빗물 이용시설",
defaultOpen: false,
sections: [
{ id: "rain-intro", label: "빗물 재이용 소개" },
{
id: "rain-mandatory",
label: "빗물이용시설 설치 관리 법적 의무 대상",
},
{ id: "rain-design", label: "빗물이용시설 설계 방안" },
{
id: "rain-standards",
label: "빗물이용시설 시설기준 및 관리기준",
},
],
},
{
key: "intro-greywater",
label: "중수도",
defaultOpen: false,
sections: [
{ id: "greywater-intro", label: "중수 재이용 소개" },
{ id: "greywater-mandatory", label: "중수도 설치 의무 대상" },
{ id: "greywater-standard", label: "중수도 시설기준 및 관리기준" },
],
},
{
key: "intro-treated",
label: "하수처리수",
defaultOpen: false,
sections: [
{ id: "treatedwater-intro", label: "하수처리수 소개" },
{
id: "treatedwater-mandatory",
label: "하수처리수 재이용시설 의무 대상",
},
],
},
];
export default function IntroPage() {
return (
<div className="relative">
{/* 상단 헤더 */}
<SubPageHeader
title="물 재이용 소개"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 소개", href: "/intro" },
]}
/>
{/* 우측 앵커 네비 */}
<BusinessSubNav menus={menus} offsetTop={120} />
{/* 본문 */}
<div className="max-w-[1300px] pb-24">
<LawIntroSection />
<RainIntroSection />
<ReuseWaterIntroSection />
<TreatedIntroSection />
</div>
</div>
);
}
@@ -1,6 +1,7 @@
"use client";
import Gis from "@common/business_components/ui/map/Gis";
import "@/app/commonOrigin.css";
import { useEffect, useRef, useState } from "react";
import { mapService } from "../service";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
@@ -22,32 +22,36 @@ export default function SelectedFcltList({
const TableContent = (
<>
<thead className="bg-gray-100 text-gray-600 text-[11px] font-medium border-b border-cyan-800">
<thead className="bg-gray-100 text-gray-700 text-[13px]">
<tr>
<th className="px-2 py-1.5 text-left w-1/3">시설명</th>
<th className="px-2 py-1.5 text-right whitespace-nowrap">
처리량(㎥/일)
<th className="px-3 py-2 text-left border-b">시설명</th>
<th className="px-3 py-2 text-right border-b">
하수처리량
<br />
(㎥/일)
</th>
<th className="px-2 py-1.5 text-right whitespace-nowrap">
재이용량(㎥/일)
<th className="px-3 py-2 text-right border-b">
재이용량
<br />
(㎥/일)
</th>
<th className="px-2 py-1.5 text-center w-10">삭제</th>
<th className="px-3 py-2 border-b">삭제</th>
</tr>
</thead>
<tbody>
{data.map((item) => (
<tr key={item.pbnstRuseFcltId} className="border-b border-gray-100 last:border-none hover:bg-gray-50/50">
<td className="px-2 py-1.5 text-[11px] font-medium text-gray-800 break-keep">{item.ruseUtztnFcltNm}</td>
<td className="px-2 py-1.5 text-right text-[11px] text-gray-600">
<tr key={item.pbnstRuseFcltId} className="border-b last:border-none">
<td className="px-3 py-2 text-[12px]">{item.ruseUtztnFcltNm}</td>
<td className="px-3 py-2 text-right text-[12px]">
{Number(item.splyExtraCpct || 0).toLocaleString()}
</td>
<td className="px-2 py-1.5 text-right text-[11px] text-gray-600">
<td className="px-3 py-2 text-right text-[12px]">
{Number(item.ruseCpct || 0).toLocaleString()}
</td>
<td className="px-2 py-1.5 text-center">
<td className="px-3 py-2 text-center">
<button
onClick={() => handleDelete(item.pbnstRuseFcltId)}
className="text-gray-400 hover:text-red-500 text-[11px] font-bold transition-colors"
className="text-red-500 hover:text-red-700 text-sm font-bold"
aria-label="삭제"
title="삭제"
>
@@ -61,12 +65,12 @@ export default function SelectedFcltList({
);
return (
<div className="fixed right-20 top-[3.5rem] w-[22rem] max-h-[80vh] overflow-hidden bg-white/95 backdrop-blur-sm shadow-[0_4px_12px_rgba(0,0,0,0.1)] rounded-lg border border-gray-200 z-50 flex flex-col">
<h3 className="bg-cyan-700 text-white text-[13px] font-semibold p-2">
<div className="fixed right-40 top-[8rem] w-[40rem] max-h-[130rem] overflow-auto bg-white shadow-lg rounded p-4 border border-gray-300 z-50">
<h3 className="bg-cyan-700 text-white text-xl font-bold mb-2 p-2">
선택된 시설
</h3>
<div className="overflow-y-auto w-full">
<table className="w-full text-sm text-gray-800 border-t border-gray-200 table-fixed">
<div className="overflow-x-auto">
<table className="w-full text-sm text-gray-800 border border-gray-200">
{TableContent}
</table>
</div>
+324 -173
View File
@@ -1,6 +1,10 @@
/**
* 수요처 등록/수정
*/
"use client";
import { useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react";
import { useRouter } from "next/navigation";
import SubPageHeader from "@/components/SubPageHeader";
import PostcodeModal from "@/components/PostcodeModal";
import type { AddrItem } from "@/app/store/sdmMng/postcode-service";
@@ -15,6 +19,7 @@ type SessionUser = {
dmdCoNm?: string;
zip?: string;
daddr?: string;
daddr2?: string;
telno?: string;
ctpvCd?: string;
sggCd?: string;
@@ -153,7 +158,12 @@ function SelectBox({
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function DemandPlaceRegisterPage() {
const router = useRouter();
const { Common, initializeCommonData } = useDmStore((state) => state);
const { selectSidoData, selectSigunData } = Common;
@@ -166,7 +176,7 @@ export default function DemandPlaceRegisterPage() {
const [company, setCompany] = useState("");
const [bizNo, setBizNo] = useState("");
const [workerNo, setWorkerNo] = useState("");
const [noWorkerNo, setNoWorkerNo] = useState(false);
const [noWorkerNo, setNoWorkerNo] = useState(true); // 종사업자번호 해당없음 default 선택으로 설정
const [zip, setZip] = useState("");
const [addr1, setAddr1] = useState("");
@@ -176,6 +186,9 @@ export default function DemandPlaceRegisterPage() {
const detailAddrRef = useRef<HTMLInputElement | null>(null);
const [tel, setTel] = useState("");
const [currentPw, setCurrentPw] = useState("");
const [isCurrentPwVerified, setIsCurrentPwVerified] = useState(false);
const [isCheckingCurrentPw, setIsCheckingCurrentPw] = useState(false);
const [pw, setPw] = useState("");
const [pw2, setPw2] = useState("");
@@ -214,7 +227,7 @@ export default function DemandPlaceRegisterPage() {
setCompany(String(user.dmdCoNm ?? ""));
setZip(String(user.zip ?? ""));
setAddr1(String(user.daddr ?? ""));
setAddr2("");
setAddr2(String(user.daddr2 ?? ""));
setTel(formatKoreanPhoneNumber(String(user.telno ?? "")));
setSido(String(user.ctpvCd ?? ""));
setSigungu(String(user.sggCd ?? ""));
@@ -240,6 +253,11 @@ export default function DemandPlaceRegisterPage() {
return () => clearTimeout(timer);
}, [addr1, focusDetailAddrOnClose, isPostcodeModalOpen]);
useEffect(() => {
if (!editMode) return;
setIsCurrentPwVerified(false);
}, [bizNo, workerNo, noWorkerNo, editMode]);
const sidoOptions = useMemo<Option[]>(() => {
const mapped = selectSidoData
.filter((item) => item.val !== "0")
@@ -277,6 +295,57 @@ export default function DemandPlaceRegisterPage() {
}
};
const checkCurrentPassword = async () => {
const bizDigits = bizNo.replace(/\D/g, "").slice(0, 10);
const workerDigits = workerNo.replace(/\D/g, "").slice(0, 4);
const password = currentPw.trim();
if (!bizDigits) return alert("사업자등록번호를 입력해 주세요.");
if (!/^\d{10}$/.test(bizDigits)) {
return alert("사업자등록번호는 10자리 숫자만 입력 가능합니다.");
}
if (!noWorkerNo) {
if (!workerDigits) return alert("종사업자등록번호를 입력해 주세요.");
if (!/^\d{4}$/.test(workerDigits)) {
return alert("종사업자등록번호는 4자리 숫자만 입력 가능합니다.");
}
}
if (!password) return alert("기존 비밀번호를 입력해 주세요.");
const fullDmdInfoId = bizDigits + (noWorkerNo ? "0000" : workerDigits);
const payload = {
dmdInfoId: fullDmdInfoId,
dmdAplyPswd: password,
};
try {
setIsCheckingCurrentPw(true);
const response = await postFetch("/reuseFc/request/checkPassword", payload);
const rawResult = await response.json().catch(() => null);
const checkedCount =
typeof rawResult === "number"
? rawResult
: typeof rawResult?.data === "number"
? rawResult.data
: Number(rawResult ?? 0);
if (checkedCount >= 1) {
setIsCurrentPwVerified(true);
alert("기존 비밀번호가 확인되었습니다.");
return;
}
setIsCurrentPwVerified(false);
alert("기존 비밀번호가 일치하지 않습니다.");
} catch (error) {
setIsCurrentPwVerified(false);
console.error("Failed to check current password", error);
alert("기존 비밀번호 확인에 실패하였습니다.");
} finally {
setIsCheckingCurrentPw(false);
}
};
const onSubmit = async () => {
const bizDigits = bizNo.replace(/\D/g, "").slice(0, 10);
const workerDigits = workerNo.replace(/\D/g, "").slice(0, 4);
@@ -301,9 +370,11 @@ export default function DemandPlaceRegisterPage() {
}
}
const mergedAddress = [addr1.trim(), addr2.trim()].filter(Boolean).join(" ");
if (!mergedAddress) return alert("사업자주소를 입력해 주세요.");
if (mergedAddress.length > 40) return alert("주소는 40자 이내로 입력해주세요.");
const baseAddress = addr1.trim();
const detailAddress = addr2.trim();
if (!baseAddress && !detailAddress) return alert("사업자주소를 입력해 주세요.");
if (baseAddress.length > 40) return alert("주소는 40자 이내로 입력해주세요.");
if (detailAddress.length > 40) return alert("상세 주소는 40자 이내로 입력해주세요.");
if (!telDigits) return alert("전화번호를 입력해 주세요.");
if (!/^\d{8,11}$/.test(telDigits)) {
@@ -317,8 +388,15 @@ export default function DemandPlaceRegisterPage() {
}
if (!passwordConfirm) return alert("비밀번호 확인을 입력해 주세요.");
if (password !== passwordConfirm) return alert("비밀번호가 일치하지 않습니다.");
} else if ((password || passwordConfirm) && password !== passwordConfirm) {
return alert("비밀번호가 일치하지 않습니다.");
} else { //수정
if (password || passwordConfirm) {
if (!password) return alert("비밀번호를 입력해 주세요.");
if (password.length < 8 || password.length > 16) {
return alert("비밀번호는 8자 이상 16자 이내로 입력해주세요.");
}
if (!passwordConfirm) return alert("비밀번호 확인을 입력해 주세요.");
if (password !== passwordConfirm) return alert("비밀번호가 일치하지 않습니다.");
}
}
const fullDmdInfoId = bizDigits + (noWorkerNo ? "0000" : workerDigits);
@@ -326,6 +404,8 @@ export default function DemandPlaceRegisterPage() {
if (!editMode) {
const isDuplicate = await checkDuplicate(fullDmdInfoId);
if (isDuplicate) return alert("이미 등록된 사업자등록번호입니다.");
} else if (!isCurrentPwVerified) {
return alert("기존 비밀번호 확인 후 수정해 주세요.");
}
const myUUID = getCookie(TOKEN_ID_COOKIE) || FALLBACK_UUID;
@@ -337,7 +417,8 @@ export default function DemandPlaceRegisterPage() {
dmdInfoId: fullDmdInfoId,
bzmnNo: bizDigits,
zip: zip.trim(),
daddr: mergedAddress,
daddr: baseAddress,
daddr2: detailAddress,
telno: telDigits,
rgtr: (sessionUser?.rgtr as string) || myUUID,
};
@@ -363,11 +444,24 @@ export default function DemandPlaceRegisterPage() {
alert("수요처 수정 완료!");
} else {
await postFetch("/reuseFc/request", payload);
if (typeof window !== "undefined") {
const nextSessionUser = {
...(sessionUser ?? {}),
...payload,
};
delete nextSessionUser.dmdAplyPswd;
sessionStorage.setItem("user", JSON.stringify(nextSessionUser));
}
alert("수요처 등록 완료!");
router.push("/apply/dmdinfo"); // 수요정보등록
}
setPw("");
setPw2("");
setCurrentPw("");
setIsCurrentPwVerified(false);
} catch (error) {
console.error("Failed to submit demand place", error);
alert(editMode ? "수요처 수정에 실패하였습니다." : "수요처 등록에 실패하였습니다.");
@@ -401,177 +495,234 @@ export default function DemandPlaceRegisterPage() {
const pageTitle = editMode ? "수요처수정" : "수요처등록";
return (
<main className="bg-white">
<SubPageHeader
title={pageTitle}
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "수요신청", href: "/reuse/apply" },
{ label: pageTitle },
]}
/>
<div className="border border-neutral-200 bg-white">
<FormRow label="시도" required>
<SelectBox
value={sido}
onChange={(v) => {
setSido(v);
setSigungu("");
}}
options={sidoOptions}
/>
</FormRow>
<Divider />
<FormRow label="시군구" required>
<select
value={sigungu}
onChange={(e) => setSigungu(e.target.value)}
className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none"
>
{sigunguOptions.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
</FormRow>
<Divider />
<FormRow label="회사명" required>
<div className="max-w-[420px]">
<TextInput
value={company}
onChange={setCompany}
placeholder="회사명을 입력해주세요."
<main className="min-w-0 flex-1">
<div className="relative">
{/* 서브 타이틀 + 브레드크럼 */}
<SubPageHeader
title={pageTitle}
crumbs={[
{ label: "Home", href: "/main" },
{ label: "수요신청", href: "/apply/dmdco" },
{ label: pageTitle },
]}
/>
</div>
</FormRow>
<Divider />
{/* 컨텐츠 영역 */}
<div className="max-w-[1320px] pb-24">
{/* 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{pageTitle}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
{/* 구분선 */}
<Hr className="mt-[40px]" />
<FormRow label="사업자등록번호" required>
<div className="max-w-[420px]">
<TextInput
value={formatBusinessNumber(bizNo)}
onChange={(v) => setBizNo(v.replace(/\D/g, "").slice(0, 10))}
placeholder="사업자 등록번호를 입력해주세요."
/>
</div>
</FormRow>
{/* 필수값 안내 */}
<p className="my-4 text-[16px] text-neutral-600 font-medium">
<span className="text-red-500">*</span> 는 필수 값입니다.
</p>
<Divider />
<div className="border border-neutral-200 bg-white">
<FormRow label="시도" required>
<SelectBox
value={sido}
onChange={(v) => {
setSido(v);
setSigungu("");
}}
options={sidoOptions}
/>
</FormRow>
<FormRow label="종사자등록번호">
<div className="flex flex-wrap items-center gap-4">
<div className="w-[420px] max-w-full">
<TextInput
value={workerNo}
onChange={(v) => setWorkerNo(v.replace(/\D/g, "").slice(0, 4))}
placeholder="종사자등록번호를 입력해주세요."
disabled={noWorkerNo}
/>
<Divider />
<FormRow label="시군구" required>
<select
value={sigungu}
onChange={(e) => setSigungu(e.target.value)}
className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none"
>
{sigunguOptions.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
</FormRow>
<Divider />
<FormRow label="회사명" required>
<div className="max-w-[420px]">
<TextInput
value={company}
onChange={setCompany}
placeholder="회사명을 입력해주세요."
/>
</div>
</FormRow>
<Divider />
<FormRow label="사업자등록번호" required>
<div className="max-w-[420px]">
<TextInput
value={formatBusinessNumber(bizNo)}
onChange={(v) => setBizNo(v.replace(/\D/g, "").slice(0, 10))}
placeholder="사업자 등록번호를 입력해주세요."
/>
</div>
</FormRow>
<Divider />
<FormRow label="종사자등록번호">
<div className="flex flex-wrap items-center gap-4">
<div className="w-[420px] max-w-full">
<TextInput
value={workerNo}
onChange={(v) => setWorkerNo(v.replace(/\D/g, "").slice(0, 4))}
placeholder="종사자등록번호를 입력해주세요."
disabled={noWorkerNo}
/>
</div>
<label className="flex items-center gap-2 text-sm text-neutral-700">
<input
type="checkbox"
checked={noWorkerNo}
onChange={(e) => {
setNoWorkerNo(e.target.checked);
if (e.target.checked) setWorkerNo("");
}}
className="h-4 w-4 rounded border-neutral-300"
/>
해당없음
</label>
</div>
</FormRow>
<Divider />
<FormRow label="사업자주소" required>
<div className="max-w-[420px] space-y-2">
<div className="flex gap-2">
<div className="w-[180px]">
<TextInput value={zip} onChange={setZip} placeholder="우편번호" />
</div>
<button
type="button"
onClick={() => setIsPostcodeModalOpen(true)}
className="h-11 rounded bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
>
우편번호 검색
</button>
</div>
<TextInput value={addr1} onChange={setAddr1} placeholder="주소" />
<TextInput
value={addr2}
onChange={setAddr2}
placeholder="상세 주소를 입력하세요."
inputRef={detailAddrRef}
/>
</div>
</FormRow>
<Divider />
<FormRow label="전화번호" required>
<div className="max-w-[420px]">
<TextInput
value={tel}
onChange={(v) => setTel(formatKoreanPhoneNumber(v))}
placeholder="전화번호를 입력하세요."
/>
</div>
</FormRow>
<Divider />
{editMode ? (
<>
<FormRow label="기존 비밀번호 확인" required>
<div className="max-w-[520px]">
<div className="flex gap-2">
<div className="flex-1">
<TextInput
value={currentPw}
onChange={(value) => {
setCurrentPw(value);
setIsCurrentPwVerified(false);
}}
placeholder="기존 비밀번호를 입력하세요."
type="password"
/>
</div>
<button
type="button"
onClick={checkCurrentPassword}
disabled={isCheckingCurrentPw}
className="h-11 shrink-0 rounded bg-[#0B4EA2] px-4 text-base text-white hover:opacity-90 disabled:bg-neutral-400"
>
{isCheckingCurrentPw ? "확인 중..." : "확인"}
</button>
</div>
<p className={`mt-2 text-sm ${isCurrentPwVerified ? "text-emerald-600" : "text-neutral-500"}`}>
{isCurrentPwVerified ? "기존 비밀번호 확인이 완료되었습니다." : "수정 전 기존 비밀번호 확인이 필요합니다."}
</p>
</div>
</FormRow>
<Divider />
</>
) : null}
<FormRow label="비밀번호" required={!editMode}>
<div className="max-w-[420px]">
<TextInput
value={pw}
onChange={setPw}
placeholder="비밀번호를 입력하세요."
type="password"
/>
</div>
</FormRow>
<Divider />
<FormRow label="비밀번호 확인" required={!editMode}>
<div className="max-w-[420px]">
<TextInput
value={pw2}
onChange={setPw2}
placeholder="비밀번호를 한번 더 입력하세요."
type="password"
/>
</div>
</FormRow>
</div>
<div className="flex justify-center py-12">
<button
type="button"
onClick={onSubmit}
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
>
{editMode ? "수요처 수정" : "수요처 등록"}
</button>
</div>
<PostcodeModal
open={isPostcodeModalOpen}
onClose={() => setIsPostcodeModalOpen(false)}
onSelect={handleSelectAddress}
/>
</div>
<label className="flex items-center gap-2 text-sm text-neutral-700">
<input
type="checkbox"
checked={noWorkerNo}
onChange={(e) => {
setNoWorkerNo(e.target.checked);
if (e.target.checked) setWorkerNo("");
}}
className="h-4 w-4 rounded border-neutral-300"
/>
해당없음
</label>
</div>
</FormRow>
<Divider />
<FormRow label="사업자주소" required>
<div className="max-w-[420px] space-y-2">
<div className="flex gap-2">
<div className="w-[180px]">
<TextInput value={zip} onChange={setZip} placeholder="우편번호" />
</div>
<button
type="button"
onClick={() => setIsPostcodeModalOpen(true)}
className="h-11 rounded bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
>
우편번호 검색
</button>
</div>
<TextInput value={addr1} onChange={setAddr1} placeholder="주소" />
<TextInput
value={addr2}
onChange={setAddr2}
placeholder="상세 주소를 입력하세요."
inputRef={detailAddrRef}
/>
</div>
</FormRow>
<Divider />
<FormRow label="전화번호" required>
<div className="max-w-[420px]">
<TextInput
value={tel}
onChange={(v) => setTel(formatKoreanPhoneNumber(v))}
placeholder="전화번호를 입력하세요."
/>
</div>
</FormRow>
<Divider />
<FormRow label="비밀번호" required={!editMode}>
<div className="max-w-[420px]">
<TextInput
value={pw}
onChange={setPw}
placeholder="비밀번호를 입력하세요."
type="password"
/>
</div>
</FormRow>
<Divider />
<FormRow label="비밀번호 확인" required={!editMode}>
<div className="max-w-[420px]">
<TextInput
value={pw2}
onChange={setPw2}
placeholder="비밀번호를 한번 더 입력하세요."
type="password"
/>
</div>
</FormRow>
</div>
<div className="flex justify-center py-12">
<button
type="button"
onClick={onSubmit}
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
>
{editMode ? "수요처 수정" : "수요처 등록"}
</button>
</div>
<PostcodeModal
open={isPostcodeModalOpen}
onClose={() => setIsPostcodeModalOpen(false)}
onSelect={handleSelectAddress}
/>
</div>
</main>
);
}
@@ -1,4 +1,3 @@
// app/(with-layout)/apply/register/page.tsx
/**
* 수요정보등록(수정) 화면
*
@@ -37,9 +36,9 @@ export default function DemandRegisterPage() {
// 기본정보
const [reqDate, setReqDate] = useState<string>(today);
const [reqQty, setReqQty] = useState<string>("");
const [usePurpose, setUsePurpose] = useState<string>("");
const [hopePrice, setHopePrice] = useState<string>("");
const [reqQty, setReqQty] = useState<string>(""); // 요구수량(㎥/d)
const [usePurpose, setUsePurpose] = useState<string>(""); // 사용용도
const [hopePrice, setHopePrice] = useState<string>(""); // 희망단가
// 요구수질
const [coliform, setColiform] = useState<string>(""); // 총대장균군수
@@ -63,9 +62,9 @@ export default function DemandRegisterPage() {
const [cand3, setCand3] = useState("");
//const [candidateList, setCandidateList] = useState<SubsInfo[]>([]);
const useTypesData = useDmStore((state) => state.Sdm.useTypesData);
const getUseTypesData = useDmStore((state) => state.getUseTypesData);
const [openSimilar, setOpenSimilar] = useState(false);
const useTypesData = useDmStore((state) => state.Sdm.useTypesData); // 사용용도
const getUseTypesData = useDmStore((state) => state.getUseTypesData); // 사용용도 함수
const [openSimilar, setOpenSimilar] = useState(false); // 유사사례 modal open
// 후보지 팝업 메시지 핸들러
const candsRef = useRef<{ name: string; code: string }[]>([]);
@@ -115,15 +114,16 @@ export default function DemandRegisterPage() {
const getSimilarCaseList = useDmStore((state) => state.getSimilarCaseList);
const [farmText, setFarmText] = useState("이하");
const insertSdmInfoData = useDmStore(
(state) => state.insertSdmInfoData
);
const insertSdmInfoData = useDmStore( (state) => state.insertSdmInfoData ); // 수요정보등록
useEffect(() => {
getUseTypesData();
getUseTypesData(); // 사용용도 조회
}, [getUseTypesData]);
// 후보지 선택 팝업이 window.postMessage로 넘긴 선택 결과를 받아
// 현재 화면의 후보지 입력칸(cand1~3)에 반영한다.
useEffect(() => {
// message 이벤트의 payload를 읽어 후보지 목록 상태를 갱신하는 핸들러
const handler = (event: MessageEvent) => {
if (!event.data) return;
@@ -200,25 +200,26 @@ export default function DemandRegisterPage() {
return true;
}
// 유사사례
const rows: SimilarCaseRow[] = similarCaseList.map((el, idx) => ({
caseId: String(idx),
projectName: el.ruseBizNm,
period: `${el.bizBgngDate} ~ ${el.bizEndDate}`,
projectName: el.ruseBizNm, // 사업명
period: `${el.bizBgngDate} ~ ${el.bizEndDate}`, // 사업기간
supplyFacilityType: el.splyFcltKnd,
supplyName: el.splyFcltNm,
supplyCapacity: Number(el.splyFcltCpct),
supplyFacilityType: el.splyFcltKnd, // 시설종류
supplyName: el.splyFcltNm, // 공급처명
supplyCapacity: Number(el.splyFcltCpct), // 공급처 시설용량
demandName: el.dmdFcltNm,
demandAddress: el.dmdFcltAddr,
demandName: el.dmdFcltNm, // 수요처명
demandAddress: el.dmdFcltAddr, // 수요처 주소
facilityCapacity: Number(el.ruseFcltCpct),
pipeKm: Number(el.ruseFcltPplLen),
purpose: el.ruseFcltUsg,
facilityCapacity: Number(el.ruseFcltCpct), // 재이용시설 시설용량
pipeKm: Number(el.ruseFcltPplLen), // 재이용시설 관로길이
purpose: el.ruseFcltUsg, // 재이용시설 용도
supplyAmt: Number(el.ruseFcltSplyCpct),
treatment: el.ruseFcltPrcsMsrc,
usePurpose: el.ruseFcltUsg,
supplyAmt: Number(el.ruseFcltSplyCpct), // 재이용시설 공급(용)량
treatment: el.ruseFcltPrcsMsrc, // 재이용시설 처리공법
usePurpose: el.ruseFcltUsg, // 재이용시설 용도
}));
const detailsByCaseId: Record<string, SimilarCaseDetail> = {};
@@ -312,6 +313,7 @@ export default function DemandRegisterPage() {
}
}
// 후보지 조회 팝업 Open
const searchCandidate = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -328,28 +330,27 @@ export default function DemandRegisterPage() {
window.open(url, "candidatePopup", popupOptions);
};
// 유사기준
const getCriterionCode = (criterion: string) => {
switch (criterion) {
case "supplyAmt":
return "S";
case "money":
return "M";
case "usePurpose":
return "U";
default:
return "";
case "supplyAmt": return "S"; // 공급량
case "money": return "M"; // 단가?
case "usePurpose": return "U"; // 이용용도
default: return "";
}
};
// 유사사례 Modal Open
const searchSimilarCase = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setOpenSimilar(true);
};
// 수요정보 입력란 초기화
const initQualityForm = () => {
setColiform("");
setNoDetect(false);
setNoDetect(false); // 총대장균군수 불검출
setTn("");
setResidualCl("");
setTp("");
@@ -364,6 +365,7 @@ export default function DemandRegisterPage() {
setFarmText("이하");
}
// 사용용도별 초기화
const fncSetOptionValue = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
initQualityForm();
@@ -422,261 +424,278 @@ export default function DemandRegisterPage() {
setUsePurpose(value);
}
const pageTitle = "수요정보등록";
return (
<>
<main className="bg-white">
<main className="min-w-0 flex-1">
<div className="relative">
{/* 서브 타이틀 + 브레드크럼 */}
<SubPageHeader
title={pageTitle}
crumbs={[
{ label: "Home", href: "/main" },
{ label: "수요신청", href: "/apply/dmdco" },
{ label: pageTitle },
]}
/>
{/* 서브 타이틀 + 브레드크럼 */}
<SubPageHeader
title="수요정보등록"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "수요신청", href: "/reuse/apply" },
{ label: "수요정보등록" },
]}
/>
{/* 컨텐츠 영역 */}
<div>
{/* 기본정보 테이블 */}
<div className="border border-neutral-200 bg-white">
<FormRow label="요구시기" required>
<DateInput value={reqDate} onChange={setReqDate} />
</FormRow>
<Divider />
<FormRow label="요구수량(m²/d)" required>
<TextInput value={reqQty} onChange={setReqQty} placeholder="" />
</FormRow>
<Divider />
<FormRow label="사용용도" required>
<select
value={usePurpose}
onChange={(e) => fncSetOptionValue(e)}
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"
>
<option value="">선택하세요</option>
{useTypesData.map((item) => (
<option key={item.val} value={item.val}>
{item.text}
</option>
))}
</select>
</FormRow>
<Divider />
<FormRow label="희망단가(원)" required>
<TextInput value={hopePrice} onChange={setHopePrice} />
</FormRow>
</div>
{/* 요구수질 섹션 타이틀 바 */}
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-6 py-6 text-center">
<span className="text-lg font-semibold text-neutral-800">요구수질</span>
<button
type="button"
onClick={() => setOpenRef(true)}
className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50"
>
수질기준 참고
</button>
{/* 수질기준참고 모달 */}
<WaterQualityRefModal
open={openRef}
onClose={() => setOpenRef(false)}
imageSrc="/images/landing/water_quality_ref_1.png"
/>
</div>
</div>
{/* 요구수질 테이블 */}
<div className="border border-neutral-200 bg-white">
{/* 1행 */}
<QualityRow
leftLabel="총대장균군수(개/mg)"
leftControl={
<div className="flex items-center gap-4">
<TextInput
value={coliform}
onChange={setColiform}
disabled={noDetect}
className={noDetect ? "bg-neutral-100 text-neutral-400" : ""}
/>
<label className="flex items-center gap-2 text-sm text-neutral-700">
<input
type="checkbox"
checked={noDetect}
onChange={(e) => {
setNoDetect(e.target.checked);
if (e.target.checked) setColiform("");
}}
className="h-4 w-4 rounded border-neutral-300"
/>
불검출
</label>
{/* 컨텐츠 영역 */}
<div className="max-w-[1320px] pb-24">
{/* 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{pageTitle}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
}
rightLabel="총질소(T-N)(mg/L)"
rightControl={
<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />
}
/>
<Divider />
{/* 구분선 */}
<Hr className="mt-[40px]" />
{/* 2행 */}
<QualityRow
leftLabel="결합잔류염소(mg/L)"
/* leftRequired */
leftControl={<TextInput value={residualCl} onChange={setResidualCl} />}
rightLabel="총인(T-P)(mg/L)"
rightControl={
<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />
}
/>
<Divider />
{/* 필수값 안내 */}
<p className="my-4 text-[16px] text-neutral-600 font-medium">
<span className="text-red-500">*</span> 는 필수 값입니다.
</p>
{/* 3행 */}
<QualityRow
leftLabel="탁도(NTU)"
/* leftRequired */
leftControl={
<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />
}
rightLabel="수소이온농도(pH)"
rightControl={
<div className="flex items-center gap-2">
<TextInput
value={ph.min}
onChange={(v) => setPh((p) => ({ ...p, min: v }))}
className="w-32"
/>
<span className="text-neutral-500">~</span>
<TextInput
value={ph.max}
onChange={(v) => setPh((p) => ({ ...p, max: v }))}
className="w-32"
/>
</div>
}
/>
<Divider />
{/* 기본정보 테이블 */}
<div className="border border-neutral-200 bg-white">
<FormRow label="요구시기" required>
<DateInput value={reqDate} onChange={setReqDate} />
</FormRow>
{/* 4행 */}
<QualityRow
leftLabel={
<>
생물화학적산소요구량
<br />
(BOD)(mg/L)
</>
}
/* leftRequired */
leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />}
rightLabel="염화물(mgCL/L)"
rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />}
/>
<Divider />
<Divider />
{/* 5행 */}
<QualityRow
leftLabel="냄새"
/* leftRequired */
leftControl={
<select
value={odor}
onChange={(e) => 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"
>
<option>불쾌하지 않을 것</option>
<option>약간 있음</option>
<option>있음</option>
</select>
}
rightLabel="전기전도도(µs/cm)"
rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix={farmText} />}
/>
<Divider />
<FormRow label="요구수량(m²/d)" required>
<TextInput value={reqQty} onChange={setReqQty} placeholder="" />
</FormRow>
{/* 6행 */}
<QualityRow
leftLabel="색도(도)"
/* leftRequired */
leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />}
rightLabel="비고"
rightControl={
<textarea
value={remark}
onChange={(e) => setRemark(e.target.value)}
className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500"
/>
}
/>
<Divider />
<Divider />
{/* 후보지 선택 */}
<div className="grid grid-cols-12">
<LabelCell>
공급처 후보지 선택
<br />
(필수아님)
</LabelCell>
<div className="col-span-12 md:col-span-10 p-6">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
<div className="flex items-center gap-3 xl:ml-2">
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
onClick={(e) => {
searchCandidate(e);
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
}}>
후보지 조회
</button>
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
onClick={(e) => {
// TODO: 유사사례 연결
searchSimilarCase(e);
}}
<FormRow label="사용용도" required>
<select
value={usePurpose}
onChange={(e) => fncSetOptionValue(e)}
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"
>
유사사례
</button>
</div>
</div>
</div>
</div>
</div>
<option value="">선택하세요</option>
{useTypesData.map((item) => (
<option key={item.val} value={item.val}>
{item.text}
</option>
))}
</select>
</FormRow>
{/* 하단 버튼 */}
<div className="flex justify-center py-12">
<button
type="button"
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
onClick={() => {
// TODO: 저장 처리
saveInfo();
}}
>
수요정보 등록
</button>
</div>
<Divider />
<FormRow label="희망단가(원)" required>
<TextInput value={hopePrice} onChange={setHopePrice} />
</FormRow>
</div>
{/* 요구수질 섹션 타이틀 바 */}
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-2 py-6 text-center">
<span className="text-lg font-semibold text-neutral-800">요구수질</span>
<button
type="button"
onClick={() => setOpenRef(true)}
className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50"
>
수질기준 참고
</button>
{/* 수질기준참고 모달 */}
<WaterQualityRefModal
open={openRef}
onClose={() => setOpenRef(false)}
imageSrc="/images/landing/water_quality_ref_1.png"
/>
</div>
</div>
{/* 요구수질 테이블 */}
<div className="border border-neutral-200 bg-white">
{/* 1행 */}
<QualityRow
leftLabel="총대장균군수(개/mg)"
leftControl={
<div className="flex items-center gap-4">
<TextInput
value={coliform}
onChange={setColiform}
disabled={noDetect}
className={noDetect ? "bg-neutral-100 text-neutral-400" : ""}
/>
<label className="flex items-center gap-2 text-sm text-neutral-700">
<input
type="checkbox"
checked={noDetect}
onChange={(e) => {
setNoDetect(e.target.checked);
if (e.target.checked) setColiform("");
}}
className="h-4 w-4 rounded border-neutral-300"
/>
불검출
</label>
</div>
}
rightLabel="총질소(T-N)(mg/L)"
rightControl={
<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />
}
/>
<Divider />
{/* 2행 */}
<QualityRow
leftLabel="결합잔류염소(mg/L)"
/* leftRequired */
leftControl={<TextInput value={residualCl} onChange={setResidualCl} />}
rightLabel="총인(T-P)(mg/L)"
rightControl={
<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />
}
/>
<Divider />
{/* 3행 */}
<QualityRow
leftLabel="탁도(NTU)"
/* leftRequired */
leftControl={
<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />
}
rightLabel="수소이온농도(pH)"
rightControl={
<div className="flex items-center gap-2">
<TextInput
value={ph.min}
onChange={(v) => setPh((p) => ({ ...p, min: v }))}
className="w-32"
/>
<span className="text-neutral-500">~</span>
<TextInput
value={ph.max}
onChange={(v) => setPh((p) => ({ ...p, max: v }))}
className="w-32"
/>
</div>
}
/>
<Divider />
{/* 4행 */}
<QualityRow
leftLabel={
<>
생물화학적산소요구량
<br />
(BOD)(mg/L)
</>
}
/* leftRequired */
leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />}
rightLabel="염화물(mgCL/L)"
rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />}
/>
<Divider />
{/* 5행 */}
<QualityRow
leftLabel="냄새"
/* leftRequired */
leftControl={
<select
value={odor}
onChange={(e) => 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"
>
<option>불쾌하지 않을 것</option>
<option>약간 있음</option>
<option>있음</option>
</select>
}
rightLabel="전기전도도(µs/cm)"
rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix={farmText} />}
/>
<Divider />
{/* 6행 */}
<QualityRow
leftLabel="색도(도)"
/* leftRequired */
leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />}
rightLabel="비고"
rightControl={
<textarea
value={remark}
onChange={(e) => setRemark(e.target.value)}
className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500"
/>
}
/>
<Divider />
{/* 후보지 선택 */}
<div className="grid grid-cols-12">
<LabelCell>
<span className="flex flex-col items-center leading-6">
<span>공급처 후보지 선택</span>
<span className="text-sm">(필수아님)</span>
</span>
</LabelCell>
<div className="col-span-12 md:col-span-10 p-6">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
<div className="flex items-center gap-3 xl:ml-2">
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
onClick={(e) => {
searchCandidate(e);
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
}}
>
후보지 조회
</button>
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
onClick={(e) => searchSimilarCase(e)}
>
유사사례
</button>
</div>
</div>
</div>
</div>
</div>
{/* 하단 버튼 */}
<div className="flex justify-center py-12">
<button
type="button"
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
onClick={() => {
// TODO: 저장 처리
saveInfo();
}}
>
수요정보 등록
</button>
</div>
</div>
</div>
</main>
@@ -686,7 +705,7 @@ export default function DemandRegisterPage() {
rows={rows} // 실제 데이터
detailsByCaseId={detailsByCaseId}
onSearch={async (criterion) => {
const code = getCriterionCode(criterion);
const code = getCriterionCode(criterion); // 'S' 공급량, 'U' 사용용도
if (!code) return;
// 값은 상황에 맞게 (예시: usePurpose)
console.log(code+' req: '+reqQty+' purpose :'+usePurpose);
@@ -836,6 +855,10 @@ function InlineSuffixInput({
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function DateInput({
value,
onChange,
+8 -3
View File
@@ -3,9 +3,14 @@ import Lnb from "@/components/Lnb";
export default function ApplyLayout({ children }: { children: ReactNode }) {
return (
<div className="flex gap-0">
<div className="relative">
{/* 우측 메뉴 */}
<Lnb />
<main className="flex-1 px-[100px] pt-[80px]">{children}</main>
{/* 본문 */}
<div className="flex max-w-[1400px] gap-[56px] py-[50px]">
{children}
</div>
</div>
);
}
}
+452 -386
View File
@@ -1,14 +1,20 @@
/**
* 수요신청현황
*/
"use client";
import { useDmStore } from "@/app/store";
import type { DemandCompBizType } from "@/app/store/sdmMng";
import SubPageHeader from "@/components/SubPageHeader";
import UiPageNations from "@common/business_components/ui/table/uiPageNations";
import { useEffect, useMemo, useRef, useState } from "react";
type TopField = { label: string; key: string, value: string; required?: boolean };
type PairField = { label: string; key: string, value: string };
type SessionUser = { dmdInfoId?: string };
const PAGE_SIZE = 10;
const formatDate = (value: string | Date | "" | undefined) => {
if (!value) return "-";
const date = new Date(value);
@@ -123,7 +129,7 @@ function DateInput({
}
export default function DemandStatusPage() {
const { getSdmInfoStatusList, updateSdmInfoData, Sdm, Common } = useDmStore((state) => state);
const { getSdmInfoStatusList, updateSdmInfoData, Sdm, Common, initializeCommonData } = useDmStore((state) => state);
const { successLogin, sdmInfoStatusList } = Sdm;
const { selectSidoData, selectSigunData } = Common;
@@ -132,6 +138,7 @@ export default function DemandStatusPage() {
const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false);
const [selectedDmdInfoSn, setSelectedDmdInfoSn] = useState<number | "" | null>(null);
const [transformedSdmInfoStatusList, setTransformedSdmInfoStatusList] = useState<DemandCompBizType[]>([]);
const [currentPage, setCurrentPage] = useState(1);
// 수정 유무
const [ isEditMode, setIsEditMode ] = useState(false);
@@ -151,8 +158,9 @@ export default function DemandStatusPage() {
}, []);
useEffect(() => {
getUseTypesData();
}, [getUseTypesData]);
getUseTypesData(); // 용도코드 조회
initializeCommonData(); // 시도, 시군구 조회
}, [getUseTypesData, initializeCommonData]);
useEffect(() => {
if (!successLogin && !isLoginStorageInfo) return;
@@ -209,6 +217,18 @@ export default function DemandStatusPage() {
);
}, [transformedSdmInfoStatusList, selectedDmdInfoSn]);
const pagedSdmInfoStatusList = useMemo(() => {
const startIndex = (currentPage - 1) * PAGE_SIZE;
return transformedSdmInfoStatusList.slice(startIndex, startIndex + PAGE_SIZE);
}, [currentPage, transformedSdmInfoStatusList]);
useEffect(() => {
const totalPages = Math.max(1, Math.ceil(transformedSdmInfoStatusList.length / PAGE_SIZE));
if (currentPage > totalPages) {
setCurrentPage(totalPages);
}
}, [currentPage, transformedSdmInfoStatusList.length]);
const topFields: TopField[] = useMemo(() => {
if (!selectedSdmInfoStatus) return [];
@@ -261,7 +281,15 @@ export default function DemandStatusPage() {
}
setSelectedDmdInfoSn(dmdInfoSn);
requestAnimationFrame(() => {
detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
if (!detailRef.current) return;
const headerOffset = 120;
const nextTop =
detailRef.current.getBoundingClientRect().top + window.scrollY - headerOffset;
window.scrollTo({
top: nextTop,
behavior: "smooth",
});
});
};
@@ -326,402 +354,440 @@ export default function DemandStatusPage() {
};
}, []);
const pageTitle = "수요신청현황";
return (
<main className="bg-white">
<SubPageHeader
title="수요신청현황"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "수요신청", href: "/apply/status" },
{ label: "수요신청현황" },
]}
hideFooterLine
hideGuideText
/>
<main className="min-w-0 flex-1">
<div className="relative">
{/* 서브 타이틀 + 브레드크럼 */}
<SubPageHeader
title={pageTitle}
crumbs={[
{ label: "Home", href: "/main" },
{ label: "수요신청", href: "/apply/dmdco" },
{ label: pageTitle },
]}
/>
<div>
<section className="mt-6">
<div className="text-2xl font-bold text-slate-700">수요정보 신청현황</div>
{/* 컨텐츠 영역 */}
<div className="max-w-[1320px] pb-24">
{/* 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{pageTitle}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<section className="mt-6">
<div className="text-2xl font-bold text-slate-700 text-center w-full">수요정보 신청현황</div>
<div className="mt-3 border border-neutral-200">
<table className="w-full table-fixed">
<thead className="bg-[#E7F4E2]">
<tr className="text-sm text-slate-700">
<th className="w-[60px] px-3 py-3 text-center">NO.</th>
<th className="w-[120px] px-3 py-3 text-center">시도</th>
<th className="w-[120px] px-3 py-3 text-center">시군구</th>
<th className="w-[180px] px-3 py-3 text-center">회사명</th>
<th className="px-3 py-3 text-center">공장주소</th>
<th className="w-[140px] px-3 py-3 text-center">전화번호</th>
<th className="w-[120px] px-3 py-3 text-center">요구시기</th>
<th className="w-[120px] px-3 py-3 text-center">등록일자</th>
<th className="w-[120px] px-3 py-3 text-center">수정일자</th>
<th className="w-[110px] px-3 py-3 text-center">진행상태</th>
</tr>
</thead>
<div className="mt-3 border border-neutral-200">
<table className="w-full table-fixed">
<thead className="bg-[#E7F4E2]">
<tr className="text-sm text-slate-700">
<th className="w-[4%] px-3 py-3 text-center">NO.</th>
<th className="w-[9%] px-3 py-3 text-center">시도</th>
<th className="w-[9%] px-3 py-3 text-center">시군구</th>
<th className="w-[13%] px-3 py-3 text-center">회사명</th>
<th className="px-3 py-3 text-center">주소</th>
<th className="w-[11%] px-3 py-3 text-center">전화번호</th>
<th className="w-[9%] px-3 py-3 text-center">요구시기</th>
<th className="w-[9%] px-3 py-3 text-center">등록일자</th>
<th className="w-[9%] px-3 py-3 text-center">수정일자</th>
<th className="w-[6%] px-3 py-3 text-center">진행상태</th>
</tr>
</thead>
<tbody className="bg-white text-base text-slate-700">
{transformedSdmInfoStatusList.length === 0 ? (
<tr>
<td colSpan={8} className="px-3 py-10 text-center text-slate-400">
조회된 신청내역이 없습니다.
</td>
</tr>
) : (
transformedSdmInfoStatusList.map((sdmInfoStatus, index) => {
const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn;
return (
<tr
key={`${sdmInfoStatus.dmdInfoSn}-${index}`}
role="row"
tabIndex={0}
onClick={() => onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn);
}
}}
<tbody className="bg-white text-base text-slate-700">
{transformedSdmInfoStatusList.length === 0 ? (
<tr>
<td colSpan={10} className="px-3 py-10 text-center text-slate-400">
조회된 신청내역이 없습니다.
</td>
</tr>
) : (
pagedSdmInfoStatusList.map((sdmInfoStatus, index) => {
const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn;
return (
<tr
key={`${sdmInfoStatus.dmdInfoSn}-${index}`}
role="row"
tabIndex={0}
onClick={() => onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn);
}
}}
className={[
"border-t border-neutral-200 outline-none",
"cursor-pointer",
active ? "bg-slate-50" : "hover:bg-slate-50",
"focus-visible:ring-2 focus-visible:ring-slate-300",
].join(" ")}
aria-selected={active}
>
<td className="px-3 py-3 text-center">
{sdmInfoStatus.index || (currentPage - 1) * PAGE_SIZE + index + 1}
</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.daddr)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.telno)}</td>
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.dmndTmng)}</td>
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.regDt)}</td>
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.mdfcnDt)}</td>
<td className="px-3 py-3 text-center">
<StatusBadge v={formatValue(sdmInfoStatus.status)} />
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
{transformedSdmInfoStatusList.length > 0 ? (
<div
className={[
"border-t border-neutral-200 outline-none",
"cursor-pointer",
active ? "bg-slate-50" : "hover:bg-slate-50",
"focus-visible:ring-2 focus-visible:ring-slate-300",
"mt-2 flex justify-center",
"[&_nav]:mt-0 [&_nav]:text-sm",
"[&_ul]:gap-0.5",
"[&_a]:h-8 [&_a]:min-w-8 [&_a]:px-2 [&_a]:py-1 [&_a]:text-sm",
"[&_svg]:h-4 [&_svg]:w-4",
"[&_span]:text-sm",
].join(" ")}
aria-selected={active}
>
<td className="px-3 py-3 text-center">{sdmInfoStatus.index || index + 1}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.daddr)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.telno)}</td>
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.dmndTmng)}</td>
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.regDt)}</td>
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.mdfcnDt)}</td>
<td className="px-3 py-3 text-center">
<StatusBadge v={formatValue(sdmInfoStatus.status)} />
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</section>
<section className="mt-10 pb-16" ref={detailRef}>
<div className="flex justify-between items-center">
<div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div>
{
!isEditMode ? (
<button
type="button"
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
onClick={() => {
setOriginData(selectedSdmInfoStatus);
setEditData({...selectedSdmInfoStatus});
setIsEditMode(true)}
}
>
수정
</button>
) : (
<div className="flex gap-2">
<button
type="button"
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
onClick={() => {
saveInfo();
}}
>
저장
</button>
<button
type="button"
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
onClick={() => {
setEditData(originData);
setIsEditMode(false)
}}
>
취소
</button>
</div>
)
}
</div>
{!selectedSdmInfoStatus ? (
<div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400">
상세 정보를 표시할 신청건이 없습니다.
</div>
) : (
<div className="mt-3 border border-neutral-200">
<div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
요구수량
</div>
<div className="grid grid-cols-12 text-sm">
{topFields.map((field) => (
<div
key={field.label}
className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200"
>
<div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
{field.label}
{field.required ? <span className="ml-1 text-red-500">*</span> : null}
</div>
{
!isEditMode ?
<div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
{field.key==='useUsgCd'? useTypesData.filter(item=>item.val===field.value)[0]?.text: field.value}
</div>
: <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
{field.key ==='dmndTmng'?
<DateInput value={editData?.[field.key] ?? ""} onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: v,
}))
} />
: field.key ==='useUsgCd'?
<select
value={editData?.[field.key] ?? ""}
onChange={(e) =>
setEditData((prev) => ({
...prev,
[field.key]: 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"
>
<option value="">선택하세요</option>
{useTypesData.map((item) => (
<option key={item.val} value={item.val}>
{item.text}
</option>
))}
</select>
: <TextInput value={editData?.[field.key] ?? ""} onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: v,
}))
}></TextInput>
}
</div>
}
</div>
))}
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
요구수질
</div>
{waterFields.map((field) => {
const { a, b } = editData?.ph ? getRange(editData.ph) : { a: "", b: "" };
return (
<div
key={field.label}
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200"
>
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
{field.label}
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{
!isEditMode ? field.value
:
field.key === "grssClfg" ? (
<div className="flex items-center gap-4">
<TextInput
value={
editData?.grssClfg === -1
? ""
: editData?.grssClfg ?? ""
}
onChange={(v) =>
setEditData((prev) => ({
...prev,
grssClfg: v,
}))
}
disabled={editData?.grssClfg === -1}
className={
editData?.grssClfg === -1
? "bg-neutral-100 text-neutral-400"
: ""
}
/>
<label className="flex items-center gap-2 w-3/4 text-sm text-neutral-700">
<input
type="checkbox"
checked={editData?.grssClfg === -1}
onChange={(e) => {
const checked = e.target.checked;
setEditData((prev) => ({
...prev,
grssClfg: checked ? -1 : "",
}));
}}
className="h-4 w-4 rounded border-neutral-300"
/>
불검출
</label>
</div>
) : field.key === 'smll' ? (
<select
value={
editData?.smll === -1
? ""
: editData?.smll ?? ""
}
onChange={(v) =>
setEditData((prev) => ({
...prev,
grssClfg: v,
}))
}
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">
<option>불쾌하지 않을 것</option>
<option>약간 있음</option>
<option>있음</option>
</select>
) : field.key === 'ph' ? (
<>
<TextInput
value={a}
onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: `${v}~${b}`,
}))
}
/>
~
<TextInput
value={b}
onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: `${a}~${v}`,
}))
}
/>
</>
) : (
<>
<TextInput
value={editData?.[field.key] ?? ""}
onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: v,
}))
}
/>
{
field.key==='tn'||field.key === 'tph'||field.key === 'trbt'||field.key==='blgcOxygDmndQnt'||field.key==='clIonQnt'||field.key==='cdtnv'|| field.key=='crmty'
?<>이하</>:<></>
}
</>
)
}
</div>
</div>
)})}
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
공급처 후보지
{
isEditMode &&
<button
type="button"
className="h-8 w-25 ml-12 rounded-md bg-[#888] px-2 text-base text-white hover:bg-neutral-700"
onClick={(e) => {
searchCandidate(e);
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
}}>
후보지 조회
</button>
}
</div>
{candidateNames.map((candidateName, index) => (
isEditMode ? (
<div
key={`candidate-${index}`}
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
>
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
후보지{index + 1}
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
<TextInput
value={candidateName}
disabled={true}
onChange={(v) => {
setEditData((prev) => {
const newList = [...(prev.splyCndtList || [])];
newList[index] = {
...newList[index],
ruseUtztnFcltNm: v,
};
return {
...prev,
splyCndtList: newList,
};
});
}}
<UiPageNations
total={transformedSdmInfoStatusList.length}
cur={currentPage}
pageSize={PAGE_SIZE}
onPageChange={setCurrentPage}
/>
</div>
</div>
) :
<div
key={`candidate-${index}`}
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
>
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
후보지{index + 1}
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{candidateName}
</div>
</div>
))}
) : null}
</section>
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200">
수요신청 진행상태
<section className="mt-10 pb-16" ref={detailRef}>
<div className="flex justify-between items-center">
<div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div>
{
!isEditMode ? (
<button
type="button"
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
onClick={() => {
if (!selectedSdmInfoStatus) return;
if (selectedSdmInfoStatus.status !== "신청") {
alert("진행상태 '신청'인 경우에만 수정이 가능합니다.");
return;
}
setOriginData(selectedSdmInfoStatus);
setEditData({...selectedSdmInfoStatus});
setIsEditMode(true)}
}
>
수정
</button>
) : (
<div className="flex gap-2">
<button
type="button"
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
onClick={() => {
saveInfo();
}}
>
저장
</button>
<button
type="button"
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
onClick={() => {
setEditData(originData);
setIsEditMode(false)
}}
>
취소
</button>
</div>
)
}
</div>
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
진행상태
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{formatValue(selectedSdmInfoStatus.status)}
</div>
</div>
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
공급처매칭결과
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)}
</div>
</div>
</div>
{!selectedSdmInfoStatus ? (
<div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400">
상세 정보를 표시할 신청건이 없습니다.
</div>
) : (
<div className="mt-3 border border-neutral-200">
<div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
요구수량
</div>
<div className="grid grid-cols-12 text-sm">
{topFields.map((field) => (
<div
key={field.label}
className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200"
>
<div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
{field.label}
{field.required ? <span className="ml-1 text-red-500">*</span> : null}
</div>
{
!isEditMode ?
<div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
{field.key==='useUsgCd'? useTypesData.filter(item=>item.val===field.value)[0]?.text: field.value}
</div>
: <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
{field.key ==='dmndTmng'?
<DateInput value={editData?.[field.key] ?? ""} onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: v,
}))
} />
: field.key ==='useUsgCd'?
<select
value={editData?.[field.key] ?? ""}
onChange={(e) =>
setEditData((prev) => ({
...prev,
[field.key]: 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"
>
<option value="">선택하세요</option>
{useTypesData.map((item) => (
<option key={item.val} value={item.val}>
{item.text}
</option>
))}
</select>
: <TextInput value={editData?.[field.key] ?? ""} onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: v,
}))
}></TextInput>
}
</div>
}
</div>
))}
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
요구수질
</div>
{waterFields.map((field) => {
const { a, b } = editData?.ph ? getRange(editData.ph) : { a: "", b: "" };
return (
<div
key={field.label}
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200"
>
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
{field.label}
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{
!isEditMode ? field.value
:
field.key === "grssClfg" ? (
<div className="flex items-center gap-4">
<TextInput
value={
editData?.grssClfg === -1
? ""
: editData?.grssClfg ?? ""
}
onChange={(v) =>
setEditData((prev) => ({
...prev,
grssClfg: v,
}))
}
disabled={editData?.grssClfg === -1}
className={
editData?.grssClfg === -1
? "bg-neutral-100 text-neutral-400"
: ""
}
/>
<label className="flex items-center gap-2 w-3/4 text-sm text-neutral-700">
<input
type="checkbox"
checked={editData?.grssClfg === -1}
onChange={(e) => {
const checked = e.target.checked;
setEditData((prev) => ({
...prev,
grssClfg: checked ? -1 : "",
}));
}}
className="h-4 w-4 rounded border-neutral-300"
/>
불검출
</label>
</div>
) : field.key === 'smll' ? (
<select
value={
editData?.smll === -1
? ""
: editData?.smll ?? ""
}
onChange={(v) =>
setEditData((prev) => ({
...prev,
grssClfg: v,
}))
}
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">
<option>불쾌하지 않을 것</option>
<option>약간 있음</option>
<option>있음</option>
</select>
) : field.key === 'ph' ? (
<>
<TextInput
value={a}
onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: `${v}~${b}`,
}))
}
/>
~
<TextInput
value={b}
onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: `${a}~${v}`,
}))
}
/>
</>
) : (
<>
<TextInput
value={editData?.[field.key] ?? ""}
onChange={(v) =>
setEditData((prev) => ({
...prev,
[field.key]: v,
}))
}
/>
{
field.key==='tn'||field.key === 'tph'||field.key === 'trbt'||field.key==='blgcOxygDmndQnt'||field.key==='clIonQnt'||field.key==='cdtnv'|| field.key=='crmty'
?<>이하</>:<></>
}
</>
)
}
</div>
</div>
)})}
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
공급처 후보지
{
isEditMode &&
<button
type="button"
className="h-8 w-25 ml-12 rounded-md bg-[#888] px-2 text-base text-white hover:bg-neutral-700"
onClick={(e) => {
searchCandidate(e);
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
}}>
후보지 조회
</button>
}
</div>
{candidateNames.map((candidateName, index) => (
isEditMode ? (
<div
key={`candidate-${index}`}
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
>
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
후보지{index + 1}
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
<TextInput
value={candidateName}
disabled={true}
onChange={(v) => {
setEditData((prev) => {
const newList = [...(prev.splyCndtList || [])];
newList[index] = {
...newList[index],
ruseUtztnFcltNm: v,
};
return {
...prev,
splyCndtList: newList,
};
});
}}
/>
</div>
</div>
) :
<div
key={`candidate-${index}`}
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
>
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
후보지{index + 1}
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{candidateName}
</div>
</div>
))}
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200">
수요신청 진행상태
</div>
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
진행상태
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{formatValue(selectedSdmInfoStatus.status)}
</div>
</div>
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
공급처매칭결과
</div>
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
{formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)}
</div>
</div>
</div>
</div>
)}
</section>
</div>
)}
</section>
</div>
</div>
</main>
);
}
@@ -0,0 +1,781 @@
"use client";
import Image from "next/image";
import CaseImageSlider from "@/components/CaseImageSlider";
type Slide = {
src: string;
alt: string;
};
type ContentBlock = {
title: string;
items: TextItem[];
};
type InfoCard = {
title: string;
desc: string;
};
type TableData = {
headers: string[];
rows: string[][];
};
type OverviewCompareRow = {
country: string;
flag?: string;
overseas: string;
current: string;
future: string;
};
type GlobalCase =
| {
type: "overview";
id: string;
title: string;
}
| {
type: "default";
id: string;
title: string;
slides: Slide[];
blocks?: ContentBlock[];
cards?: InfoCard[];
table?: TableData;
};
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle">
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
</span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type TextItem =
| string
| {
text: React.ReactNode;
depth?: number; // 0: 기본 / 1: 하위 / 2: 더 하위
};
function ContentList({ items }: { items: TextItem[] }) {
return (
<ul className="mt-4 space-y-2 text-[16px] leading-[1.8] text-[#4F4F4F]">
{items.map((item, index) => {
const isString = typeof item === "string";
const text = isString ? item : item.text;
const depth = isString ? 0 : item.depth ?? 0;
if (depth === 3) {
return (
<li key={index} className="ml-[56px] list-none text-[#777]">
* {text}
</li>
);
}
if (depth === 2) {
return (
<li key={index} className="ml-[52px] list-none text-[#666]">
{text}
</li>
);
}
if (depth === 1) {
return (
<li key={index} className="ml-[34px] list-none text-[#777]">
- {text}
</li>
);
}
return (
<li key={index} className="ml-6 list-disc pl-1 font-semibold text-[#555]">
{text}
</li>
);
})}
</ul>
);
}
function ContentBlocks({
blocks,
cards,
table,
}: {
blocks?: ContentBlock[];
cards?: InfoCard[];
table?: TableData;
}) {
if (!blocks?.length) return null;
return (
<div className="mt-10 space-y-9 px-10">
{blocks.map((block, index) => (
<div key={`${block.title}-${index}`}>
<BlockTitle>{block.title}</BlockTitle>
{!!block.items.length && (
<ContentList items={block.items} />
)}
{block.title.includes("재생 수자원 개발법") && (
<InfoCards cards={cards} />
)}
{block.title.includes("대만 재이용 시설별 재이용수 공급단지") && (
<DataTable table={table} />
)}
</div>
))}
</div>
);
}
function InfoCards({ cards }: { cards?: InfoCard[] }) {
if (!cards?.length) return null;
return (
<div className="mt-8 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-4">
{cards.map((card) => (
<div
key={card.title}
className="rounded-[8px] border-2 border-[#01ACE5] bg-[#EAF5F5] p-5"
>
<strong className="block text-[20px] font-bold text-[#333]">
{card.title}
</strong>
<p className="mt-3 text-[15px] leading-[1.6] text-[#555]">
{card.desc}
</p>
</div>
))}
</div>
);
}
function DataTable({ table }: { table?: TableData }) {
if (!table) return null;
return (
<div className="mt-8 px-[120px]">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
{table.headers.map((header, index) => (
<th
key={header}
className={[
"border border-[#D8DDE3] px-4 py-3 text-[18px] font-medium",
index === 0
? "bg-[#BFC9D2] text-[#222]"
: index === 1
? "bg-[#14A9D9] text-white"
: "bg-[#70BF4B] text-white",
].join(" ")}
>
{header}
</th>
))}
</tr>
</thead>
<tbody>
{table.rows.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((cell, cellIndex) => (
<td
key={`${rowIndex}-${cellIndex}`}
className={[
"border border-[#D8DDE3] px-4 py-3 text-[16px] text-[#4F4F4F]",
cellIndex === 1
? "bg-[#EAF7F8]"
: cellIndex === 2
? "bg-[#EAF8E6]"
: "bg-white",
].join(" ")}
>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
/* =========================
사례개요 전용 컴포넌트
========================= */
const compareRows: OverviewCompareRow[] = [
{
country: "한국",
flag: "/images/landing/business/globalCase/flag_kr.png",
overseas: "12.1%",
current: "15.6%",
future: "17%",
},
{
country: "대만",
flag: "/images/landing/business/globalCase/flag_tw.png",
overseas: "2.4%",
current: "4.3%",
future: "16.5%",
},
];
const reuseRateRows = [
["이스라엘", "중동&북아프리카", "92.3", "튀르키예", "동유럽&중앙아시아", "26.6"],
["쿠웨이트", "중동&북아프리카", "89.3", "독일", "서유럽", "25.0"],
["호주", "동아시아&태평양", "49.0", "스페인", "서유럽", "24.4"],
["싱가포르", "동아시아&태평양", "44.4", "한국", "동아시아&태평양", "12.1"],
["그리스", "서유럽", "39.1", "미국", "북아메리카", "13.1"],
["사우디아라비아", "중동&북아프리카", "32.8", "프랑스", "서유럽", "9.5"],
["이탈리아", "서유럽", "31.7", "대만", "동아시아&태평양", "2.4"],
["룩셈부르크", "서유럽", "29.1", "일본", "동아시아&태평양", "0.8"],
["포르투갈", "서유럽", "29.0", "", "", ""],
];
function WaterBullet() {
return (
<Image
src="/images/landing/business/globalCase/icon_bullet_water.png"
alt="bullet"
className="h-[33px] w-[27px] mt-1"
width={27}
height={33}
/>
);
}
function OverviewNoticeBox() {
return (
<div className="mx-auto mt-5 max-w-[1000px] rounded-[20px] bg-[#EAF7F8] px-8 py-7">
<div className="flex items-start gap-2">
<WaterBullet />
<div>
<strong className="block text-[26px] font-extrabold text-[#007FA4]">
우리나라 하수처리수 재이용률은 15% 수준
</strong>
<ul className="mt-5 list-disc space-y-2 pl-5 text-[16px] leading-[1.7] text-[#555]">
<li>
2024년 하수도 통계 기준 <b>15.6%</b>이며, 제2차 물 재이용 기본 계획을 고려한{" "}
<b style={{ color: "#1A1A1A" }}>2030년 목표는 17%</b>
</li>
<li>
대만은 가뭄과 공업용수 증가에 대응하여 <b style={{ color: "#1A1A1A" }}>현재 4.3% 수준이며, 16.5%까지 확대 계획 수립</b>
</li>
</ul>
</div>
</div>
</div>
);
}
function OverviewCompareTable() {
return (
<div className="mx-auto mt-5 max-w-[1000px] overflow-x-auto">
<table className="w-full min-w-[760px] text-center">
<thead>
<tr>
<th className="bg-[#BFC9D2] px-4 py-4 text-[18px] font-medium text-[#222] rounded-tl-[10px] rounded-tr-[10px]">
국가
</th>
<th className="bg-[#14A9D9] px-4 py-4 text-[18px] font-medium text-white rounded-tl-[10px] rounded-tr-[10px]">
해외정보
</th>
<th className="bg-[#55BDA5] px-4 py-4 text-[18px] font-medium text-white rounded-tl-[10px] rounded-tr-[10px]">
현재
</th>
<th className="bg-[#6AB84E] px-4 py-4 text-[18px] font-medium text-white rounded-tl-[10px] rounded-tr-[10px]">
미래(목표, 예측치)
</th>
</tr>
</thead>
<tbody>
{compareRows.map((row) => (
<tr key={row.country}>
<td className="border border-[#AEB9C2] bg-white px-4 py-4 text-[20px] font-bold text-[#333]">
<div className="flex items-center justify-center gap-3">
{row.flag && (
<Image
key={row.country}
src={row.flag}
alt={`${row.country} 국기`}
className="h-[28px] w-[42px] object-cover"
width={42}
height={28}
/>
)}
{row.country}
</div>
</td>
<td className="border border-[#AEB9C2] bg-[#EAF7F8] px-4 py-4 text-[28px] font-bold text-[#4F4F4F]">
{row.overseas}
</td>
<td className="border border-[#AEB9C2] bg-white px-4 py-4 text-[28px] font-bold text-[#4F4F4F]">
{row.current}
</td>
<td className="border border-[#AEB9C2] bg-[#EAF8E6] px-4 py-4 text-[28px] font-bold text-[#4F4F4F]">
{row.future}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function OverviewDescriptionBox() {
return (
<div className="mx-auto mt-5 max-w-[1000px] rounded-[20px] bg-[#EAF7F8] px-7 py-6">
<ul className="list-disc space-y-2 pl-5 text-[16px] leading-[1.7] text-[#555]">
<li>
전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐
</li>
<li>
이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편
</li>
</ul>
</div>
);
}
function OverviewReuseRateTable() {
return (
<div className="mx-auto mt-5 max-w-[1000px] overflow-x-auto">
<table className="w-full min-w-[960px] text-center">
<thead>
<tr>
{["국가명", "지역", "재이용률", "국가명", "지역", "재이용률"].map((header, index) => (
<th
key={`${header}-${index}`}
className={[
"px-4 py-3 text-[17px] font-bold rounded-tl-[10px] rounded-tr-[10px]",
index % 3 === 0
? "bg-[#BFC9D2] text-[#222]"
: index % 3 === 1
? "bg-[#14A9D9] text-white"
: "bg-[#6AB84E] text-white",
].join(" ")}
>
{header}
</th>
))}
</tr>
</thead>
<tbody>
{reuseRateRows.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((cell, cellIndex) => (
<td
key={`${rowIndex}-${cellIndex}`}
className={[
"border border-[#AEB9C2] px-4 py-3 text-[15px] text-[#4F4F4F]",
cellIndex % 3 === 1
? "bg-[#EAF7F8]"
: cellIndex % 3 === 2
? "bg-[#EAF8E6]"
: "bg-white font-semibold",
].join(" ")}
>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
function OverviewSection() {
return (
<div className="mt-10 px-10">
<div>
<BlockTitle>주요 국가 물 재이용 계획</BlockTitle>
<OverviewNoticeBox />
<OverviewCompareTable />
</div>
<div className="mt-8">
<BlockTitle>주요 국가의 하수처리수 재이용률 (%)</BlockTitle>
<OverviewDescriptionBox />
<OverviewReuseRateTable />
</div>
</div>
);
}
/* =========================
데이터
========================= */
const globalCases: GlobalCase[] = [
{
type: "overview",
id: "example-global-overview",
title: "사례 개요",
},
{
type: "default",
id: "example-global-taiwan",
title: "[대만] 국가 주도의 공업용수 공급",
slides: [
{
src: "/images/landing/business/globalCase/taiwan_01.png",
alt: "대만 국가 주도의 공업용수 공급 1",
},
{
src: "/images/landing/business/globalCase/taiwan_02.png",
alt: "대만 국가 주도의 공업용수 공급 2",
},
{
src: "/images/landing/business/globalCase/taiwan_03.png",
alt: "대만 국가 주도의 공업용수 공급 3",
},
],
blocks: [
{
title: "재생 수자원 개발법 제정 (2015년제정, 2022개정)",
items: [
{ text: "목적: 수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결" },
{ text: "내용: 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등" }
],
},
{
title: "대만정부 하수재이용 사업 시행",
items: [
{ text: "대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑)" },
{ text: "1,518억의 사업비 중 92% 국비, 지방비 8% 투입", depth: 1 },
{ text: "재이용수는 24km 관로로 타이난 TSMC로 공급", depth: 1 },
{ text: "안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능" },
{ text: "공급단가는 수질과 운영조건에 따라 다르게 책정" },
],
},
{
title: "대만 재이용 시설별 재이용수 공급단지",
items: [],
},
{
title: "특이사항",
items: [
"대만은 강수량이 풍부하지만 지역별 편차로 수자원 확보에 어려움을 겪고 있으며, 특히 남부 지역은 우기와 건기의 강수 차이가 큼",
"최근 수년간 심각한 가뭄이 발생하면서 반도체 산업을 국가 핵심 산업으로 지정, 공업용수 수요가 크게 증가",
"주요 반도체 공장이 남부에 집중되어 있어 물 부족 문제가 지속적으로 발생, 2021년 가뭄 당시 산업단지 용수 공급에도 차질 발생",
"정부는 2023년부터 산업용수 추가 요금을 부과하고, 농업용수 규제를 강화하는 등 용수 관리 정책 시행",
"기후변화 대응을 위해 수자원 보호 및 재이용 정책을 지속적으로 확대",
"산업용수 다소비 산업과 하수처리수를 재이용수 주요 공급원으로 활용 계획",
],
},
],
cards: [
{
title: "제4조.",
desc: "물 소비 계획에 따른 재이용수 이용 관련 내용 (일정 비율 재이용수 사용 규정)",
},
{
title: "제5조.",
desc: "재이용수의 운영자, 사용자 관련 내용 (지자체에서 하수 무상공급 제공 가능)",
},
{
title: "제7조.",
desc: "재이용수 이용 범위, 이용량 재이용수 수질 기준 내용",
},
{
title: "제8조.",
desc: "재이용수 사업 주체, 책임 등에 관한 내용 (광역인 경우, 정부가 지자체 지정)",
},
],
table: {
headers: ["사업", "가격(NTD/㎥)", "가격(원/㎥)"],
rows: [
["Fongshan(가오슝)", "23.39", "1,038원"],
["Linhai(가오슝)", "19.28", "858원"],
["Anping(타이난)", "38.28", "1,699원"],
["Yongkang(타이난)", "23.67", "1,050원"],
["Rende(타이난)", "28.26", "1,254원"],
["Shuinan(타이중)", "15.00", "668원"],
["Quaitou(가오슝)", "35.44", "1,573원"],
],
},
},
{
type: "default",
id: "example-global-singapore",
title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화",
slides: [
{
src: "/images/landing/business/globalCase/singapore_01.png",
alt: "싱가포르 NEWater 사례 1",
},
{
src: "/images/landing/business/globalCase/singapore_02.png",
alt: "싱가포르 NEWater 사례 2",
},
{
src: "/images/landing/business/globalCase/singapore_03.png",
alt: "싱가포르 NEWater 사례 3",
},
],
blocks: [
{
title: "싱가포르 NEWater 비중 확대",
items: [
"2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설",
"NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용",
"2060년까지 NEWater 비중 55%까지 높이는 것이 싱가포르의 목표",
],
},
{
title: "싱가포르 재이용수 의무화 및 지원정책",
items: [
{ text: "공업용수 수요가 높은 산업에 대해 물 재이용을 의무화" },
{ text: "반도체업계 전체 비 재이용수(non-domestic용수) 17% 차지 → 공업용수 수요 원인", depth: 1 },
{ text: "2024년 1월 1일부터 연간 최소 60,000㎥를 소비하는 사업자는 재이용수 사용 의무화", depth: 1 },
{ text: "반도체 공장의 경우 최소 50% 이상의 재이용수를 요구함(현재는 43% 정도)", depth: 1 },
{ text: "재정 지원을 통해 재이용 활성화를 지원" },
{ text: "재이용 설비를 기준으로 2025년까지 싱가포르 수자원공사의 2가지 금융상품 제공", depth: 1 },
{ text: "1. Water Efficiency Fund", depth: 2 },
{ text: "2. Industrial Water Solutions Demonstration Fund", depth: 2 },
{ text: "2025년 기준 지원 한도 상향 → 물 재이용 활성 도모", depth: 1 },
{ text: "투입비용 50% → 75% 지원 / WSDP: $400만 → $500만", depth: 3 },
{ text: "물 효율이 높은 장비를 도입하는 데에도 최대 $300만 지원 가능", depth: 3 },
],
},
{
title: "특이사항",
items: [
{ text: "싱가포르는 천연 수자원이 많지 않아, 말레이시아 조호르와 1962년 맺은 협약으로 물을 수입해왔으며, 재이용수를 늘리기 위한 노력을 계속하고 있음" },
{ text: "싱가포르는 국토 내에 호수와 하천이 적고 집수공간도 적어 빗물로 확보할 수 있는 수자원량은 전체 물 수요의 20%에 불과함", depth: 1 },
{ text: "싱가포르는 2002년부터 하수 재이용수인 NEWater를 개발, 비음용수와 간접음용수로 활용하고 있음" },
{ text: "재이용수를 NEWater라는 이름으로 명명하고 NEWater에 대한 신뢰 확보를 위해 NEWater 방문자 센터도 건설함", depth: 1 },
{ text: "비음용수로 많이 사용하며 건기에는 NEWater를 저수지에 추가하여 원수와 섞음. 원수는 상수도에서 처리 한 후 가정에 수돗물로 공급함", depth: 1 },
{ text: "NEWater는 전용 파이프를 통해 주로 웨이퍼 제조 공장, 산업단지, 상업용 건물에서 산업 및 냉각수로 사용. 제일 많이 사용하는 곳은 웨이퍼 제조 공장", depth: 1 },
{ text: "2024년 4월 기준 1㎥ 당 공업용수 가격은 $1.66이며, Non-domestic 기준 NEWater 가격은 $2.41" },
{ text: "공업용수 가격 $1.66은 수도요금(water tariff)과 수자원세(waterborne tax)로 구분됨. $1.66에는 $1만큼의 수자원세가 포함됨" },
{ text: "2023년 싱가포르는 산업용수 수요가 높은 산업에 대해 물 재이용을 의무화했으며, 정부는 기금을 통해 물 재이용을 지원함" },
],
},
],
},
{
type: "default",
id: "example-global-australia",
title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급",
slides: [
{
src: "/images/landing/business/globalCase/australia_01.png",
alt: "호주 생활 속 물 재이용 1",
},
{
src: "/images/landing/business/globalCase/australia_02.png",
alt: "호주 광역 물 재이용수 공급 2",
},
{
src: "/images/landing/business/globalCase/australia_03.png",
alt: "호주 광역 물 재이용수 공급 3",
},
],
blocks: [
{
title: "생활용수로 재이용수의 적극 활용",
items: [
"재이용수를 공원, 골프장 등의 관계용수로 공급(초록색 지역)",
"Dual pipe로 재이용수를 특정 주거단지로 공급(보라색 지역)",
"현재 7,000여 가구가 재이용수를 사용",
"2030년까지 43,000가구로 확대 예정",
],
},
{
title: "호주 재이용수 광역 활용(퀸즈랜드주 브리즈번)",
items: [
{ text: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망" },
{ text: "이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급" },
{
text: (
<>
12개 댐, 32개 수처리 시설, 3개 재이용시설, 담수화 플랜트 1개, 저수지 28개와{" "}
<span className="text-[#01ACE5]">
600km 이상의 물 공급 관로를 건설
</span>
</>
),
depth: 1,
},
{
text: (
<>
지역적으로 강수량이 고르지 않아,{" "}
<span className="text-[#01ACE5]">
댐의 저수율이 불균등 할 때
</span>{" "}
지역의 물 공급을 보충하는 중요한 역할 수행
</>
),
depth: 1,
},
{ text: "안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능" },
{ text: "공급단가는 수질과 운영조건에 따라 다르게 책정" },
],
},
{
title: "특이사항",
items: [
"호주는 2000~2009년 강수량 감소로 밀레니엄 가뭄이 발생하여 심각한 물 부족 현상이 발생함",
"호주는 해안 도시에 인구가 밀집하여 거주하고 있으며 인구 증가율이 타 선진국 대비 높아, 향후 도시에 공급할 수자원 확보가 필요함",
"호주정부는 1990년대부터 하·폐수가 방류될 경우의 환경 피해에 대해 인식하기 시작하고 배출규제와 위원회를 구성하기 시작",
"연방정부는 주에 인센티브를 주는 방식으로 물 정책을 장려하고 토지소유권과 물 권리를 구분하여 거래를 허용하고 물 재이용 활성화를 위한 Water Smart Australia 프로그램을 시행",
"호주의 용수공급 체계는 영국과 미국의 특성들이 존재하지만, 주의 특성과 주정부의 정책 방향에 따라 주마다 많은 차이가 있음",
],
},
],
},
{
type: "default",
id: "example-global-eu",
title: "[EU] 법률을 제정, EU국가에서 물 재이용 유도",
slides: [
{
src: "/images/landing/business/globalCase/eu_01.png",
alt: "EU 물 재이용 기술 사례",
},
{
src: "/images/landing/business/globalCase/eu_02.png",
alt: "EU 물 재이용 유도 사례",
},
],
blocks: [
{
title: "물 재이용에 대한 법률을 제정(2020 제정, 2023 시행)",
items: [
"유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741)",
"2023년부터 모든 EU국가에서 시행",
],
},
{
title: "유럽연합 회원국 물 재이용 지원정책",
items: [
{ text: "유럽연합 차원에서 다양한 물 재이용 프로그램 자금 지원" },
{ text: "2021년부터 3년간 Life RECYCLIO 프로그램 지원 → 세부내용 물 재이용 프로그램", depth: 1 },
{ text: "1. 환경 관련 사업에 자금을 지원하는 Life 프로그램의 일환으로 80만 유로를 지원", depth: 2 },
{ text: "2. 식품·농업 분야 물 재이용 확대를 위해 기술 이전 및 평가 실시", depth: 2 },
{ text: "물 재이용 정책 수행을 위해 BOOST-INN 180만 유로를 지원", depth: 1 },
{ text: "프랑스, 이탈리아, 스페인 등 유럽 선진국 솔루션 공유", depth: 3 },
{ text: "EU산하 JRC(공동연구센터)에서 물 재이용 기술제공 지원" },
{ text: (
<>
‘물 재이용 회원 관리 실무 커뮤니티(Water Reuse Management Community of Practice)’ 시범사업 수행
<br />
&nbsp; 유럽 전역에 성공적인 모델 활용 및 혁신적인 물 재이용 솔루션을 확장할 수 있는 기술을 제공
</>
),
depth: 1
}
],
},
{
title: "특이사항",
items: [
"2019년 기준 EU 인구의 38%가 물 부족의 영향을 받을 것으로 예상됨. 이 흐름대로면 2030년까지 유럽 유역 전반에 물 부족 문제가 발생하게 될 것으로 예상. 인구 증가, 도시화의 확대, 염분화 및 기후변화들이 수자원 문제들의 주요 요인이며, 식습관 변화와 산업용수 수요 증가 등으로 악화되고 있는 상황",
"유럽의 물 부족 상황은 지역에 따라 다르게 나타나 스페인을 비롯한 남부유럽에서 특히 심각함. 전반적으로 북유럽에 비해 남유럽의 물 스트레스가 심하며, 특히 스페인과 포르투갈, 프랑스 동부지역의 물 스트레스가 심각함",
"유럽 연합에서도 물 부족 문제를 인식하고 2000년 EU Water Framework Directive를 개발, 이후 2020년 재이용에 관한 법률을 제정",
],
},
],
},
];
function DefaultGlobalSection({
item,
}: {
item: Extract<GlobalCase, { type: "default" }>;
}) {
return (
<>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={item.slides}
/>
<ContentBlocks
blocks={item.blocks}
cards={item.cards}
table={item.table}
/>
</>
);
}
export default function GlobalCaseSection() {
const title = "해외 재이용 사례";
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
{globalCases.map((item, index) => (
<section key={item.id} id={item.id} className="scroll-mt-[120px]">
<SectionTitle>{item.title}</SectionTitle>
{item.type === "overview" ? (
<OverviewSection />
) : (
<DefaultGlobalSection item={item} />
)}
{index !== globalCases.length - 1 && <Hr className="my-20" />}
</section>
))}
</div>
</div>
);
}
@@ -0,0 +1,191 @@
"use client";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle"> <span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" /> <span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" /></span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type SupplyRow = {
facility: string;
usage: string;
amount: string;
};
function SupplyTable({ rows }: { rows: SupplyRow[] }) {
return (
<div className="mt-8 px-10">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-2 text-[18px] font-medium text-white">
시설명
</th>
<th className="border border-[#D8DDE3] bg-[#40B5A5] px-4 py-2 text-[18px] font-medium text-white">
이용용도
</th>
<th className="border border-[#D8DDE3] bg-[#73BF4B] px-4 py-2 text-[18px] font-medium text-white">
공급량(㎥)
</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={`${row.facility}-${index}`}>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.facility}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.usage}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.amount}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function InfoList({
projectName,
purpose,
totalCost,
treatment,
}: {
projectName: string;
purpose: string;
totalCost: string;
treatment: string;
}) {
return (
<div className="mt-10 space-y-10 px-10">
<div>
<BlockTitle>사업명</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>{projectName}</li>
</ul>
</div>
<div>
<BlockTitle>사업목적</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>{purpose}</li>
</ul>
</div>
<div>
<BlockTitle>사업 주요 내용</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>총사업비 : {totalCost}</li>
<li>처리공법 : {treatment}</li>
</ul>
</div>
<div>
<BlockTitle>사업량</BlockTitle>
</div>
</div>
);
}
export default function GreywaterExampleSection() {
const title = "중수 재이용 사례";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
<section id="example-greywater-ansan" className="scroll-mt-[120px]">
<SectionTitle>경기도 안산시</SectionTitle>
<InfoList
projectName="안산시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="81억원"
treatment="MBLM공법"
/>
<SupplyTable
rows={[
{ facility: "경기테크노파크", usage: "청소·화장실용수", amount: "40㎥/일" },
{ facility: "올림픽수영장", usage: "청소·화장실/세척·살수용수", amount: "35㎥/일" },
{ facility: "와스타디움", usage: "청소·화장실/조경용수", amount: "90㎥/일" },
{ facility: "호수실내수영장", usage: "청소·화장실/조경/세척·살수용수", amount: "50㎥/일" },
{ facility: "한양대학교 에리카캠퍼스", usage: "청소·화장실용수", amount: "370㎥/일" },
]}
/>
</section>
<Hr className="my-20" />
<section id="example-greywater-yongin" className="scroll-mt-[120px]">
<SectionTitle>경기도 용인시</SectionTitle>
<InfoList
projectName="용인시 중수도 설치사업"
purpose="물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용"
totalCost="40억원"
treatment="MBLM공법"
/>
<SupplyTable
rows={[
{ facility: "여성회관", usage: "청소·화장실용수", amount: "35㎥/일" },
{ facility: "수지아르피아", usage: "청소·화장실용수", amount: "35㎥/일" },
{ facility: "지상작전사령부", usage: "청소·화장실/조경/세척·살수용수", amount: "53㎥/일" },
]}
/>
</section>
<Hr className="my-20" />
<section id="example-greywater-pyeongtaek" className="scroll-mt-[120px]">
<SectionTitle>경기도 평택시</SectionTitle>
<InfoList
projectName="평택시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="21억원"
treatment="MBLM공법"
/>
<SupplyTable
rows={[
{
facility: "평택대학교",
usage: "청소·화장실용수",
amount: "50㎥/일",
},
]}
/>
</section>
</div>
</div>
);
}
@@ -0,0 +1,225 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle">
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
</span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function InfoList({
projectName,
purpose,
details,
}: {
projectName: string;
purpose: string;
details: string[];
}) {
return (
<div className="mt-10 space-y-9 px-10">
<div>
<BlockTitle>사업명</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
<li>{projectName}</li>
</ul>
</div>
<div>
<BlockTitle>사업목적</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
<li>{purpose}</li>
</ul>
</div>
<div>
<BlockTitle>사업 주요 내용</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
{details.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
</div>
);
}
type RainTableRow = {
facility: string;
storage: string;
supply: string;
pipe: string;
};
function RainTable({ rows }: { rows: RainTableRow[] }) {
return (
<div className="mt-8 overflow-x-auto">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-3 text-[18px] font-medium text-white">
시설명
</th>
<th className="border border-[#D8DDE3] bg-[#39B2BD] px-4 py-3 text-[18px] font-medium text-white">
저류조용량(㎥)
</th>
<th className="border border-[#D8DDE3] bg-[#58B99A] px-4 py-3 text-[18px] font-medium text-white">
빗물공급기
</th>
<th className="border border-[#D8DDE3] bg-[#70BF4B] px-4 py-3 text-[18px] font-medium text-white">
빗물분사장치(m)
</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={`${row.facility}-${index}`}>
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
{row.facility}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
{row.storage}
</td>
{index === 0 && (
<td
rowSpan={rows.length}
className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]"
>
{row.supply}
</td>
)}
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
{row.pipe}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default function RainExampleSection() {
const title = "빗물 재이용 사례";
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
<section id="example-rain-suwon" className="scroll-mt-[120px]">
<SectionTitle>경기도 수원시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{ src: "/images/landing/business/rain/suwon_01.png", alt: "수원시 빗물이용시설 사례 1" },
{ src: "/images/landing/business/rain/suwon_02.png", alt: "수원시 빗물이용시설 사례 2" },
{ src: "/images/landing/business/rain/suwon_03.png", alt: "수원시 빗물이용시설 사례 3" },
{ src: "/images/landing/business/rain/suwon_04.png", alt: "수원시 빗물이용시설 사례 4" },
{ src: "/images/landing/business/rain/suwon_05.png", alt: "수원시 빗물이용시설 사례 5" },
]}
/>
<InfoList
projectName="수원시 빗물이용시설 설치사업"
purpose="월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물공급기를 설치하여 축서기 도시 열섬효과 및 도로 비산먼지 저감"
details={[
"총사업비 : 20억원",
"위치 : 수원시 영통구, 팔달구 및 장안구 일원",
]}
/>
<div className="mt-9 px-10">
<BlockTitle>사업량</BlockTitle>
<RainTable
rows={[
{
facility: "광교중학교",
storage: "4,500",
supply: "1대",
pipe: "160",
},
{
facility: "수원월드컵경기장",
storage: "12,000",
supply: "1대",
pipe: "451",
},
{
facility: "수원종합운동장",
storage: "10,000",
supply: "1대",
pipe: "231",
},
{
facility: "수원컨벤션센터",
storage: "3,600",
supply: "1대",
pipe: "460",
},
]}
/>
</div>
</section>
<Hr className="my-20" />
<section id="example-rain-jeju" className="scroll-mt-[120px]">
<SectionTitle>제주특별자치도</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{ src: "/images/landing/business/rain/jeju_01.png", alt: "제주특별자치도 빗물이용시설 사례 1" },
{ src: "/images/landing/business/rain/jeju_02.png", alt: "제주특별자치도 빗물이용시설 사례 2" },
{ src: "/images/landing/business/rain/jeju_03.png", alt: "제주특별자치도 빗물이용시설 사례 3" },
]}
/>
<InfoList
projectName="제주특별자치도 중규모 빗물이용시설 설치사업"
purpose="강우시 비닐하우스에서 발생하는 대체수자원인 빗물을 순환 활용하고자 하는 사업으로, 지하수 사용 저감과 집중호우시 도로와 농경지 침수피해 예방"
details={[
"총사업비 : 278억원",
"위치 : 제주특별자치도 서귀포시 남원읍 위미리 일원",
"사업량 : 저류조 7,000㎥, 배수갑 500㎡, 관로 21.7km",
"사업기간 : 2024. 5. ~ 2028. 12.",
]}
/>
</section>
</div>
</div>
);
}
@@ -0,0 +1,367 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
type SewageCase = {
id: string;
title: string;
slides: {
src: string;
alt: string;
}[];
projectName: string;
purpose: string[];
method?: string[];
details: string[];
note?: string[];
};
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle">
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
</span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function InfoBlock({
title,
items,
}: {
title: string;
items?: string[];
}) {
if (!items || items.length === 0) return null;
return (
<div>
<BlockTitle>{title}</BlockTitle>
<ul className="mt-4 list-disc space-y-2 pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
{items.map((item, index) => (
<li key={`${title}-${index}`}>{item}</li>
))}
</ul>
</div>
);
}
function InfoList({ item }: { item: SewageCase }) {
return (
<div className="mt-10 space-y-9 px-10">
<InfoBlock title="사업명" items={[item.projectName]} />
<InfoBlock title="사업목적" items={item.purpose} />
<InfoBlock title="사업방식" items={item.method} />
<InfoBlock title="사업 주요 내용" items={item.details} />
<InfoBlock title="특이사항" items={item.note} />
</div>
);
}
const sewageCases: SewageCase[] = [
{
id: "example-sewage-guri",
title: "경기도 구리시",
slides: [
{
src: "/images/landing/business/sewage/guri_01.png",
alt: "구리시 도로자동청소시스템",
},
{
src: "/images/landing/business/sewage/guri_02.png",
alt: "구리시 도로자동청소시스템 위치도",
},
{
src: "/images/landing/business/sewage/guri_03.png",
alt: "구리시 도로자동청소시스템 위치도2",
},
{
src: "/images/landing/business/sewage/guri_04.png",
alt: "구리시 도로자동청소시스템 원리",
},
],
projectName: "구리시 도로자동청소시스템 설치사업",
purpose: [
"고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화",
],
details: [
"총사업비 : 61억원",
"위치 : 구리시 경춘로 및 갈매중앙로 일대 등",
"관로 : 5.8km",
"공급량 : 178㎥/일",
],
},
{
id: "example-sewage-bucheon",
title: "경기도 부천시",
slides: [
{
src: "/images/landing/business/sewage/bucheon_01.png",
alt: "부천시 하수 재이용수 생산시설",
},
{
src: "/images/landing/business/sewage/bucheon_02.png",
alt: "부천시 하수처리수 재이용 사례",
},
],
projectName: "부천시 하수 재이용수 생산시설 설치 운영",
purpose: [
"굴포하수처리시설 하수처리수 재이용수 생산시설 설치 운영을 통해 여월천 등 부천 관내 하천의 건천화 방지, 하천 생태계 보존 및 주민들에게 친수 공간 제공",
],
details: [
"총사업비 : 212억원",
"위치 : 부천시 여월천, 삼정천, 심곡천 및 시민의 강",
"관로 : 23.6km",
"공급량 : 50,000㎥/일",
],
},
{
id: "example-sewage-suwon",
title: "경기도 수원시",
slides: [
{
src: "/images/landing/business/sewage/suwon_01.png",
alt: "수원시 황구지천1",
},
{
src: "/images/landing/business/sewage/suwon_02.png",
alt: "수원시 황구지천2",
},
{
src: "/images/landing/business/sewage/suwon_03.png",
alt: "수원시 황구지천 지도",
},
],
projectName: "수원시 황구지천 하수처리수 재이용사업",
purpose: [
"황구지천 공공하수처리시설 하수처리수 재이용사업으로 황구 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전",
],
details: [
"총사업비 : 110억원",
"위치 : 수원시 권선구 호매실천, 금곡천",
"관로 : 3.9km",
"공급량 : 25,000㎥/일(하수처리시설 용량 : 45,000㎥/일)",
],
},
{
id: "example-sewage-paju",
title: "경기도 파주시",
slides: [
{
src: "/images/landing/business/sewage/paju_01.png",
alt: "파주시 하수처리수 시설 전경1",
},
{
src: "/images/landing/business/sewage/paju_02.png",
alt: "파주시 하수처리수 시설 전경2",
},
],
projectName: "파주시 하수처리수 재이용사업",
purpose: [
"운정 및 금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주LCD산업단지 내 3개 업체에 공업용수를 공급(LG디스플레이, 희성전자, 엔비텍)",
"하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례",
],
method: ["민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)"],
details: [
"총사업비 : 651억원",
"위치 : 파주시 금촌동 714번지 일원",
"관로 : 13.6km",
"공급량 : 40,000㎥/일(시설용량 : 41,200㎥/일)",
],
},
{
id: "example-sewage-pohang",
title: "경상북도 포항시",
slides: [
{
src: "/images/landing/business/sewage/pohang_01.png",
alt: "포항시 하수처리수 전경1",
},
{
src: "/images/landing/business/sewage/pohang_02.png",
alt: "포항시 하수처리수 전경2",
},
{
src: "/images/landing/business/sewage/pohang_03.png",
alt: "포항시 하수처리수 재이용 공급계통",
},
],
projectName: "포항시 하수처리수 재이용사업",
purpose: [
"포항시 내 공단 개발 확대로 지속적이고 안정적인 공업용수 공급 추진(포스코, 동국산업 등)",
],
method: ["민간투자사업(운영사 : ㈜포웰, 한국수자원공사)"],
details: [
"총사업비 : 1,400억원",
"위치 : 포항시 남구 상도동 125번지 일원",
"관로 : 11.7km",
"공급량 : 100,000㎥/일(시설용량 : 100,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
},
{
id: "example-sewage-ulsan",
title: "울산광역시",
slides: [
{
src: "/images/landing/business/sewage/ulsan_01.png",
alt: "울산광역시 폐수처리수 전경1",
},
{
src: "/images/landing/business/sewage/ulsan_02.png",
alt: "울산광역시 폐수처리수 전경2",
},
{
src: "/images/landing/business/sewage/ulsan_03.png",
alt: "울산광역시 폐수처리수 시설1",
},
{
src: "/images/landing/business/sewage/ulsan_04.png",
alt: "울산광역시 폐수처리수 시설2",
},
],
projectName: "울산광역시 폐수처리수 재이용사업",
purpose: [
"’18년 갈수기시 낙동강 수질 악화로 울산석유화학단지 용수 공급 부족을 해소하기 위해 미포산단 내 한화케미칼 등 8개사 공업용수 공급",
],
method: ["민간투자사업(시행사 :울산화이텍워터(주))"],
details: [
"총사업비 : 1,729억원",
"위치 : 울산광역시 울주군 청량읍 상개로 14 일원",
"관로 : 11.7km",
"공급량 : 17,000㎥/일(시설용량 : 86,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O, EDI)",
],
},
{
id: "example-sewage-yeosu",
title: "전라남도 여수시",
slides: [
{
src: "/images/landing/business/sewage/yeosu_01.png",
alt: "여수시 하수처리수 재이용시설1",
},
{
src: "/images/landing/business/sewage/yeosu_02.png",
alt: "여수시 하수처리수 재이용시설2",
},
],
projectName: "여수시 하수처리수 재이용사업",
purpose: [
"수요자가 원하는 양질의 공업용수를 저렴하게 공급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선을 위한 여수국가산단 내 공업용수 공급(GS칼텍스, YNCC 등)",
],
method: ["민간투자사업(시행사 : 여수그린허브(주), 운영사 : 테크로스)"],
details: [
"총사업비 : 750억원",
"위치 : 여수시 신월로 284 일원",
"관로 : 18.6km",
"공급량 : 50,000㎥/일(시설용량 : 50,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
},
{
id: "example-sewage-boryeong",
title: "충청남도 보령시",
slides: [
{
src: "/images/landing/business/sewage/boryeong_01.png",
alt: "보령시 하수처리수 재이용시설1",
},
{
src: "/images/landing/business/sewage/boryeong_02.png",
alt: "보령시 하수처리수 재이용시설2",
},
],
projectName: "보령시 하수처리수 재이용사업",
purpose: [
"한국중부발전(주)의 보령 및 신보령화력발전소에 10,000㎥/일의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진",
"국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(기후에너지환경부, 충청남도, 보령시, 한국중부발전(주))를 바탕으로 추진되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모",
],
method: ["재정사업(2017년 한국환경공단과 사업대행 위·수탁협약 체결)"],
details: [
"총사업비 : 315억원",
"위치 : 보령시 대천방조제로 322 일원",
"관로 : 8.7km",
"공급량 : 10,000㎥/일(시설용량 : 10,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
note: [
"국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모",
],
},
{
id: "example-sewage-asan",
title: "충청남도 아산시",
slides: [
{
src: "/images/landing/business/sewage/asan_01.png",
alt: "아산시 하수처리수 재이용시설",
},
{
src: "/images/landing/business/sewage/asan_02.png",
alt: "아산시 하수처리수 재이용 공정도",
},
],
projectName: "아산시 하수처리수 재이용사업",
purpose: [
"신도시 개발에 따른 인구 급증으로 발생하는 하수를 재처리하여 탕정산업단지(삼성디스플레이)에 공업용수를 안정적으로 공급함으로써 지역기반 및 활성화를 도모",
],
method: ["민간투자사업(시행사 : 아산스마트원터, 운영사 : 한국수자원공사)"],
details: [
"총사업비 : 297억원",
"위치 : 아산시 탕정면 갈산리 807 일원",
"관로 : 4.2km",
"공급량 : 27,000㎥/일(시설용량 : 45,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
},
];
export default function SewageExampleSection() {
const title = "하수처리 재이용 사례";
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10 pb-[120px]">
{sewageCases.map((item, index) => (
<section key={item.id} id={item.id} className="scroll-mt-[120px]">
<SectionTitle>{item.title}</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={item.slides}
/>
<InfoList item={item} />
{index !== sewageCases.length - 1 && <Hr className="my-20" />}
</section>
))}
</div>
</div>
);
}
@@ -1,151 +0,0 @@
// apps/dm/app/(landing)/reuse/business/globalCase/australia/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "해외 재이용 사례" },
{ label: "호주", href: "/reuse/business/globalCase/australia" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/globalCase/australia_01.png", alt: "호주 사례 1" },
]}
/>
{/* 생활용수로 재이용수의 적극 활용 */}
<section className="mt-8">
<SectionTitle>생활용수로 재이용수의 적극 활용</SectionTitle>
<div className="px-4 mt-6">
<ul className="space-y-4 text-base text-[#4F4F4F]">
<li>
– 재이용수를 공원, 골프장 등의 관개용수로 공급(초록색 지역)
</li>
<li>
– Dual pipe로 재이용수를 특정 주거단지로 공급(보라색 지역)
</li>
<li>
– 현재 7,000여 가구가 재이용수를 사용
</li>
<li>
– 2030년까지 43,000가구로 확대 예정
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
{/* 호주 재이용수 광역 활용(퀸즈랜드주 브리즈번) */}
<section className="mt-6">
<SectionTitle>호주 재이용수 광역 활용(퀸즈랜드주 브리즈번)</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망
</li>
<li>
• 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급<br />
<ul className="list-inside text-[#767676] ml-4 mt-2">
<li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li>
<li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li>
</ul>
</li>
<li>
• 12개 댐, 32개 수처리 시설, 3개 재이용시설, 담수화 플랜트 1개, 저수지 28개와 600km 이상의 물 공급 관로를 건설
</li>
<li>
• 지역적으로 강수량이 고르지 않아, 댐의 저수율이 불균등할 때 지역의 물 공급을 보충하는 중요한 역할을 수행
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
{/* 특이사항 */}
<section className="pb-[200px]">
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>호주는 2000~2009년 강수량 감소로 밀레니엄 가뭄이 발생하여 심각한 물 부족 현상이 발생함</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>호주는 해안 도시에 인구가 밀집하여 거주하고 있으며 인구 증가율이 타 선진국 대비 높아, 향후 도시에 공급할 수자원 확보가 필요함</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>호주정부는 1990년대부터 하·폐수가 방류될 경우의 환경 피해에 대해 인식하기 시작하고 배출규제와 위원회를 구성하기 시작</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>연방정부는 주에 인센티브를 주는 방식으로 물 정책을 장려하고 토지소유권과 물 권리를 구분하여 거래를 허용하고 물 재이용 활성화를 위한 Water Smart Australia 프로그램을 시행</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>호주의 용수공급 체계는 영국과 미국의 특성들이 존재하지만, 주의 특성과 주 정부의 정책 방향에 따라 주마다도 많은 차이가 있음. 특히 대부분의 주에서 수 자원 개발 기능과 수도 공급의 기능을 분리하여 주정부<br/>산하 기관에서 운영하는게 특징</li>
</ul>
</li>
</ul>
</section>
</div>
);
}
@@ -1,142 +0,0 @@
// apps/dm/app/(landing)/reuse/business/globalCase/eu/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="[EU] 법률을 제정, EU국가에서 물 재이용 유도"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "해외 재이용 사례" },
{ label: "EU", href: "/reuse/business/globalCase/eu" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/globalCase/eu_01.png", alt: "EU 사례 1" },
]}
/>
{/* 물 재이용에 대한 법률을 제정(2020 제정, 2023 시행) */}
<section className="mt-8">
<SectionTitle>물 재이용에 대한 법률을 제정(2020 제정, 2023 시행)</SectionTitle>
<div className="px-4 mt-6">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741)
</li>
<li>
• 2023년부터 모든 EU국가에서 시행
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
{/* 유럽연합 회원국 물 재이용 지원정책 */}
<section className="mt-6">
<SectionTitle>유럽연합 회원국 물 재이용 지원정책</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 유럽연합 차원에서 다양한 물 재이용 프로그램 자금 지원<br />
<ul className="list-inside space-y-4 text-[#767676] text-sm ml-4 mt-2">
<li>– 2021년부터 3년간 Life RECYCLIO 프로그램 지원 → 세부내용 물 재이용 프로그램<br/>
<ul className="list-inside space-y-2 text-[#767676] ml-4 mt-2">
<li>1. 환경 관련 사업에 자금을 지원하는 Life 프로그램의 일환으로 80만 유로를 지원</li>
<li>2. 식품·농업 분야 물 재이용 확대를 위해 기술 이전 및 평가 실시</li>
</ul>
</li>
<li>– 물 재이용 정책 수행을 위해 BOOST-INN 180만 유로를 지원<br/>
<ul className="list-inside space-y-2 text-[#767676] ml-4 mt-2">
<li> *프랑스, 이탈리아, 스페인 등 유럽 선진국 솔루션 공유</li>
</ul>
</li>
</ul>
</li>
<li>
• EU산하 JRC(공동연구센터)에서 물 재이용 기술제공 지원<br />
<ul className="list-inside space-y-2 text-[#767676] text-sm ml-4 mt-2">
<li>‘물 재이용 회원 관리 실무 커뮤니티(Water Reuse Management Community of Practice)’ 시범사업 수행</li>
<li>유럽 전역에 성공적인 모델 활용 및 혁신적인 물 재이용 솔루션을 확장할 수 있는 기술을 제공</li>
</ul>
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
{/* 특이사항 */}
<section className="pb-[200px]">
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>2019년 기준 EU 인구의 38%가 물 부족의 영향을 받을 것으로 예상됨. 이 흐름대로면 2030년까지 유럽 유역 전반에 물 부족 문제가 발생하게 될 것으로 예상. 인구 증가, 도시화의 확대, 염분화 및 기후변화들이 수자원 문제들의 주요 요인이며, 식습관 변화와 산업용수 수요 증가 등으로 악화되고 있는 상황</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>유럽의 물 부족 상황은 지역에 따라 다르게 나타나 스페인을 비롯한 남부유럽 에서 특히 심각함. 전반적으로 북유럽에 비해 남유럽의 물 스트레스가 심하며, 특히 스페인과 포르투갈, 프랑스 동부지역의 물 스트레스가 심각함</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>유럽 연합에서도 물 부족 문제를 인식하고 2000년 EU Water Framework Directive을 개발, 이후 2020년 재이용에 관한 법률을 제정</li>
</ul>
</li>
</ul>
</section>
</div>
);
}
@@ -1,229 +0,0 @@
// apps/dm/app/(landing)/reuse/business/globalCase/overview/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="해외 재이용 사례 개요"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "해외 재이용 사례" },
{ label: "사례개요", href: "/reuse/business/globalCase/overview" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/globalCase/overview_01.png", alt: "해외 재이용 사례 1" },
]}
/>
<section className="mt-6">
<div className="rounded-md bg-[#e9f0f1] p-8">
<h3 className="text-xl font-bold text-gray-800 leading-relaxed">
2024년 하수도 통계 기준 15.6%이며,<br />
제2차 물 재이용 기본 계획을 고려한 2030년 목표는 <span className="font-extrabold">17%</span>
</h3>
{/* 진행 바 영역 */}
<div className="mt-6 space-y-4">
{/* 2024 */}
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">’24년도</span>
<div className="relative h-7 rounded-r-lg">
<div className="h-7 w-[112px] rounded-r-lg bg-gray-400" />
</div>
<span className="text-sm text-gray-700">15.6%</span>
</div>
{/* 2030 */}
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">’30년도</span>
<div className="relative">
<div className="h-10 w-[327px] rounded-r-lg bg-gradient-to-r from-sky-500 to-green-500" />
</div>
<span className="text-xl font-bold text-gray-900">17%</span>
</div>
</div>
</div>
{/* 설명 문구 */}
<ul className="mt-6 space-y-2 text-base text-gray-600">
<li>
• 우리나라의 하수 처리 재이용률은 <span className="text-sky-600 ">15% 수준</span>
</li>
<li>
• 대만은 가뭄과 공업용수 증가에 대응하여
<span className="text-sky-600 ">
{" "}현재 4.3% 수준이며, 16.5%까지 확대 계획 수립
</span>
</li>
</ul>
{/* 테이블 */}
<div className="mt-6">
<table className="w-full text-center text-sm">
<thead>
<tr>
<th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가</th>
<th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">해외정보</th>
<th className="bg-teal-500 px-4 py-3 text-white rounded-t-lg">현재</th>
<th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">미래(목표, 예측치)</th>
</tr>
</thead>
<tbody className="bg-white border border-t-0">
<tr className="border-t">
<td className="px-4 py-3 font-medium">한국</td>
<td className="px-4 py-3 bg-sky-50 text-gray-700">12.1%</td>
<td className="px-4 py-3">15.6%</td>
<td className="px-4 py-3 bg-sky-50 text-gray-700">17%</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 font-medium">대만</td>
<td className="px-4 py-3 bg-sky-50 text-gray-700">2.4%</td>
<td className="px-4 py-3">4.3%</td>
<td className="px-4 py-3 bg-sky-50 text-gray-700">16.5%</td>
</tr>
</tbody>
</table>
</div>
</section>
<Hr className="my-11" />
{/* 주요 국가의 하수처리수 재이용률 (%) */}
<section className="pb-[200px]">
<SectionTitle>주요 국가의 하수처리수 재이용률 (%)</SectionTitle>
{/* 설명 문구 */}
<ul className="mt-6 space-y-2 text-base text-gray-600">
<li>
• 전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐
</li>
<li>
• 이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편
</li>
</ul>
{/* 테이블 (2단 병렬: 좌/우 데이터) */}
<div className="mt-6">
<div className="overflow-x-auto">
<table className="min-w-[900px] w-full text-center text-sm">
<thead>
<tr>
{/* 좌측 3컬럼 */}
<th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가명</th>
<th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">지역</th>
<th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">재이용률</th>
{/* 우측 3컬럼 */}
<th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가명</th>
<th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">지역</th>
<th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">재이용률</th>
</tr>
</thead>
<tbody className="bg-white border border-t-0">
{[
{
L: { c: "이스라엘", r: "중동&북아프리카", v: "92.3" },
R: { c: "튀르키예", r: "동유럽&중앙아시아", v: "26.6" },
},
{
L: { c: "쿠웨이트", r: "중동&북아프리카", v: "89.3" },
R: { c: "독일", r: "서유럽", v: "25.0" },
},
{
L: { c: "호주", r: "동아시아&태평양", v: "49.0" },
R: { c: "스페인", r: "서유럽", v: "24.4" },
},
{
L: { c: "싱가포르", r: "동아시아&태평양", v: "44.4" },
R: { c: "한국", r: "동아시아&태평양", v: "12.1" },
},
{
L: { c: "그리스", r: "서유럽", v: "39.1" },
R: { c: "미국", r: "북아메리카", v: "13.1" },
},
{
L: { c: "사우디아라비아", r: "중동&북아프리카", v: "32.8" },
R: { c: "프랑스", r: "서유럽", v: "9.5" },
},
{
L: { c: "이탈리아", r: "서유럽", v: "31.7" },
R: { c: "대만", r: "동아시아&태평양", v: "2.4" },
},
{
L: { c: "룩셈부르크", r: "서유럽", v: "29.1" },
R: { c: "일본", r: "동아시아&태평양", v: "0.8" },
},
{
L: { c: "포르투갈", r: "서유럽", v: "29.0" },
R: { c: "", r: "", v: "" }, // 마지막 우측은 빈칸(이미지처럼)
},
].map((row, idx) => (
<tr key={idx} className="border-t">
{/* 좌측 */}
<td className="px-4 py-3 font-medium text-gray-700">{row.L.c}</td>
<td className="px-4 py-3 bg-sky-50 text-gray-700">{row.L.r}</td>
<td className="px-4 py-3 bg-green-50 text-gray-700">{row.L.v}</td>
{/* 우측 */}
<td className="px-4 py-3 font-medium text-gray-700">{row.R.c}</td>
<td className="px-4 py-3 bg-sky-50 text-gray-700">{row.R.r}</td>
<td className="px-4 py-3 bg-green-50 text-gray-700">{row.R.v}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</section>
</div>
);
}
@@ -0,0 +1,844 @@
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function GlobalCasePage() {
return (
<div className="bg-white">
<SubPageHeader
title="해외 재이용 사례"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "해외 재이용 사례" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 사례 개요 */}
<section id="overview" className="scroll-mt-[120px]">
<SectionTitle>사례 개요</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{
src: "/images/landing/business/globalCase/overview_01.png",
alt: "해외 재이용 사례 개요 1",
},
]}
/>
<section className="mt-6">
<div className="rounded-md bg-[#e9f0f1] p-8">
<h3 className="text-xl font-bold leading-relaxed text-gray-800">
2024년 하수도 통계 기준 15.6%이며,
<br />
제2차 물 재이용 기본 계획을 고려한 2030년 목표는{" "}
<span className="font-extrabold">17%</span>
</h3>
<div className="mt-6 space-y-4">
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">’24년도</span>
<div className="relative h-7 rounded-r-lg">
<div className="h-7 w-[112px] rounded-r-lg bg-gray-400" />
</div>
<span className="text-sm text-gray-700">15.6%</span>
</div>
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">’30년도</span>
<div className="relative">
<div className="h-10 w-[327px] rounded-r-lg bg-gradient-to-r from-sky-500 to-green-500" />
</div>
<span className="text-xl font-bold text-gray-900">17%</span>
</div>
</div>
</div>
<ul className="mt-6 space-y-2 text-base text-gray-600">
<li>
• 우리나라의 하수 처리 재이용률은{" "}
<span className="text-sky-600">15% 수준</span>
</li>
<li>
• 대만은 가뭄과 공업용수 증가에 대응하여
<span className="text-sky-600">
{" "}
현재 4.3% 수준이며, 16.5%까지 확대 계획 수립
</span>
</li>
</ul>
<div className="mt-6 overflow-x-auto">
<table className="w-full min-w-[640px] text-center text-sm">
<thead>
<tr>
<th className="rounded-t-lg bg-[#C2CAD2] px-4 py-3 text-black">
국가
</th>
<th className="rounded-t-lg bg-sky-600 px-4 py-3 text-white">
해외정보
</th>
<th className="rounded-t-lg bg-teal-500 px-4 py-3 text-white">
현재
</th>
<th className="rounded-t-lg bg-green-500 px-4 py-3 text-white">
미래(목표, 예측치)
</th>
</tr>
</thead>
<tbody className="border border-t-0 bg-white">
<tr className="border-t">
<td className="px-4 py-3 font-medium">한국</td>
<td className="bg-sky-50 px-4 py-3 text-gray-700">12.1%</td>
<td className="px-4 py-3">15.6%</td>
<td className="bg-sky-50 px-4 py-3 text-gray-700">17%</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 font-medium">대만</td>
<td className="bg-sky-50 px-4 py-3 text-gray-700">2.4%</td>
<td className="px-4 py-3">4.3%</td>
<td className="bg-sky-50 px-4 py-3 text-gray-700">16.5%</td>
</tr>
</tbody>
</table>
</div>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>주요 국가의 하수처리수 재이용률 (%)</SectionTitle>
<ul className="mt-6 space-y-2 text-base text-gray-600">
<li>
• 전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이
잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐
</li>
<li>
• 이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이
제한되어 있어 재이용량 비중도 매우 높은 편
</li>
</ul>
<div className="mt-6 overflow-x-auto">
<table className="min-w-[900px] w-full text-center text-sm">
<thead>
<tr>
<th className="rounded-t-lg bg-[#C2CAD2] px-4 py-3 text-black">
국가명
</th>
<th className="rounded-t-lg bg-sky-600 px-4 py-3 text-white">
지역
</th>
<th className="rounded-t-lg bg-green-500 px-4 py-3 text-white">
재이용률
</th>
<th className="rounded-t-lg bg-[#C2CAD2] px-4 py-3 text-black">
국가명
</th>
<th className="rounded-t-lg bg-sky-600 px-4 py-3 text-white">
지역
</th>
<th className="rounded-t-lg bg-green-500 px-4 py-3 text-white">
재이용률
</th>
</tr>
</thead>
<tbody className="border border-t-0 bg-white">
{[
{
L: { c: "이스라엘", r: "중동&북아프리카", v: "92.3" },
R: { c: "튀르키예", r: "동유럽&중앙아시아", v: "26.6" },
},
{
L: { c: "쿠웨이트", r: "중동&북아프리카", v: "89.3" },
R: { c: "독일", r: "서유럽", v: "25.0" },
},
{
L: { c: "호주", r: "동아시아&태평양", v: "49.0" },
R: { c: "스페인", r: "서유럽", v: "24.4" },
},
{
L: { c: "싱가포르", r: "동아시아&태평양", v: "44.4" },
R: { c: "한국", r: "동아시아&태평양", v: "12.1" },
},
{
L: { c: "그리스", r: "서유럽", v: "39.1" },
R: { c: "미국", r: "북아메리카", v: "13.1" },
},
{
L: { c: "사우디아라비아", r: "중동&북아프리카", v: "32.8" },
R: { c: "프랑스", r: "서유럽", v: "9.5" },
},
{
L: { c: "이탈리아", r: "서유럽", v: "31.7" },
R: { c: "대만", r: "동아시아&태평양", v: "2.4" },
},
{
L: { c: "룩셈부르크", r: "서유럽", v: "29.1" },
R: { c: "일본", r: "동아시아&태평양", v: "0.8" },
},
{
L: { c: "포르투갈", r: "서유럽", v: "29.0" },
R: { c: "", r: "", v: "" },
},
].map((row, idx) => (
<tr key={idx} className="border-t">
<td className="px-4 py-3 font-medium text-gray-700">
{row.L.c}
</td>
<td className="bg-sky-50 px-4 py-3 text-gray-700">
{row.L.r}
</td>
<td className="bg-green-50 px-4 py-3 text-gray-700">
{row.L.v}
</td>
<td className="px-4 py-3 font-medium text-gray-700">
{row.R.c}
</td>
<td className="bg-sky-50 px-4 py-3 text-gray-700">
{row.R.r}
</td>
<td className="bg-green-50 px-4 py-3 text-gray-700">
{row.R.v}
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</section>
<Hr className="my-16" />
{/* 대만 */}
<section id="taiwan" className="scroll-mt-[120px]">
<SectionTitle>대만</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{
src: "/images/landing/business/globalCase/taiwan_01.png",
alt: "대만 사례 1",
},
]}
/>
<section className="mt-8">
<SectionTitle>재생 수자원 개발법 제정(2015년제정, 2022개정)</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-2 text-base text-gray-600">
<li>
• 목적: 수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족
문제 해결
</li>
<li>
• 내용: 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등
</li>
</ul>
<div className="mt-5 rounded-sm bg-[#e9f0f1] p-8">
<ul className="space-y-1 text-2xl text-[#1A1A1A]">
<li>
– 제4조. 물 소비 계획에 따른 재이용수 이용 관련 내용 (일정 비율
재이용수 사용 규정)
</li>
<li>
– 제5조. 재이용수의 운영자, 사용자 관련 내용 (지자체에서 하수
무상공급 제공 가능)
</li>
<li>
– 제7조. 재이용수 이용 범위, 이용량 재이용수 수질 기준 내용
(식품, 제약산업 사용 불가)
</li>
<li>
– 제8조. 재이용수 사업 주체, 책임 등에 관한 내용 (광역인 경우,
정부가 지자체 지정)
</li>
</ul>
</div>
</div>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>대만정부 하수재이용 사업 시행</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업
시행(안핑)
<br />
<ul className="ml-4 mt-2 list-inside text-[#767676]">
<li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li>
<li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li>
</ul>
</li>
<li>• 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능</li>
<li>• 공급단가는 수질과 운영조건에 따라 다르게 책정</li>
</ul>
<div className="mt-3 overflow-x-auto">
<p className="py-2 text-center text-2xl">
〈대만 재이용 시설별 재이용수 공급단지〉
</p>
<table className="min-w-[900px] w-full text-center text-sm">
<thead className="border">
<tr>
<th className="bg-[#C2CAD2] px-4 py-3 text-black">사업</th>
<th className="bg-sky-600 px-4 py-3 text-white">
가격(NTD/㎥)
</th>
<th className="bg-green-500 px-4 py-3 text-white">
가격(원/㎥)
</th>
</tr>
</thead>
<tbody className="border border-t-0 bg-white text-[#4F4F4F]">
<tr className="border-t">
<td className="bg-white px-4 py-3">Fongshan(가오슝)</td>
<td className="bg-[#EAF5F5] px-4 py-3">23.39</td>
<td className="bg-[#E7F4E2] px-4 py-3">1,038원</td>
</tr>
<tr className="border-t">
<td className="bg-white px-4 py-3">Linhai(가오슝)</td>
<td className="bg-[#EAF5F5] px-4 py-3">19.28</td>
<td className="bg-[#E7F4E2] px-4 py-3">858원</td>
</tr>
<tr className="border-t">
<td className="bg-white px-4 py-3">Anping(타이난)</td>
<td className="bg-[#EAF5F5] px-4 py-3">38.28</td>
<td className="bg-[#E7F4E2] px-4 py-3">1,699원</td>
</tr>
<tr className="border-t">
<td className="bg-white px-4 py-3">Yongkang(타이난)</td>
<td className="bg-[#EAF5F5] px-4 py-3">23.67</td>
<td className="bg-[#E7F4E2] px-4 py-3">1,050원</td>
</tr>
<tr className="border-t">
<td className="bg-white px-4 py-3">Rende(타이난)</td>
<td className="bg-[#EAF5F5] px-4 py-3">28.26</td>
<td className="bg-[#E7F4E2] px-4 py-3">1,254원</td>
</tr>
<tr className="border-t">
<td className="bg-white px-4 py-3">Shuinan(타이중)</td>
<td className="bg-[#EAF5F5] px-4 py-3">15.00</td>
<td className="bg-[#E7F4E2] px-4 py-3">668원</td>
</tr>
<tr className="border-t">
<td className="bg-white px-4 py-3">Quaitou(가오슝)</td>
<td className="bg-[#EAF5F5] px-4 py-3">35.44</td>
<td className="bg-[#E7F4E2] px-4 py-3">1,573원</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
대만은 풍부한 강수량에도, 지형적 특성으로 인해 수자원 확보에
어려움을 겪고 있으며, 대만 남부지역의 경우 우기와 건기 사이의
강수량 차이가 큼
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
대만은 최근 심각한 가뭄이 여러 해에 걸쳐서 발생하고 있으며,
강수량 감소와 함께 대만 정부 주도하에 반도체 산업을 국가 핵심
산업으로 선정하고 반도체 산업 육성 계획을 세움. 그 결과,
2023년 파운드리 시장의 68.3%를 차지하는 반도체 강국으로
성장했으며 이로 인한 공업용수 수요가 크게 증가
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
반도체 산업은 물 소비가 많아, 안정적인 용수공급이 매우
중요한데 현재 19개의 반도체 공장이 물이 부족한 대만 남부지역에
위치함. 2021년 가뭄 발생 당시, 산업단지 기업들도 용수 공급에
어려움을 겪음. 이는 대만 남부 지역의 물 부족, 물 분배 등에
대한 문제를 야기함
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
대만 정부는 산업단지 기업들에게 2023년부터 물에 대한
추가요금 부과를 시작했으며, 농업용수에 대해서는 강력한
억제정책을 시행함. 안정적인 용수 공급을 위해서 정부와
기업에서 자체적으로 재이용수 시설에 대한 투자가 이루어지고
있음.
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
대만은 2004년 기후변화로 인한 물 부족 상황을 해결하기 위해
수자원 보호 전략을 승인하고, 2015년 대만 환경부는 재생 수자원
개발법을 발표, 2022년에는 한 차례 개정
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
대만은 물 소비량이 많은 산업(high water consuming
industries)과 하수처리장(municipal wastewater treatment
plants), 두 곳을 재이용수의 주요 공급원으로 계획
</li>
</ul>
</li>
</ul>
</section>
</section>
<Hr className="my-16" />
{/* 호주 */}
<section id="australia" className="scroll-mt-[120px]">
<SectionTitle>호주</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{
src: "/images/landing/business/globalCase/australia_01.png",
alt: "호주 사례 1",
},
]}
/>
<section className="mt-8">
<SectionTitle>생활용수로 재이용수의 적극 활용</SectionTitle>
<div className="mt-6 px-4">
<ul className="space-y-4 text-base text-[#4F4F4F]">
<li>– 재이용수를 공원, 골프장 등의 관개용수로 공급(초록색 지역)</li>
<li>
– Dual pipe로 재이용수를 특정 주거단지로 공급(보라색 지역)
</li>
<li>– 현재 7,000여 가구가 재이용수를 사용</li>
<li>– 2030년까지 43,000가구로 확대 예정</li>
</ul>
</div>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>호주 재이용수 광역 활용(퀸즈랜드주 브리즈번)</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해
설정한 광역 수로망
</li>
<li>
• 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급
<br />
<ul className="ml-4 mt-2 list-inside text-[#767676]">
<li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li>
<li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li>
</ul>
</li>
<li>
• 12개 댐, 32개 수처리 시설, 3개 재이용시설, 담수화 플랜트 1개,
저수지 28개와 600km 이상의 물 공급 관로를 건설
</li>
<li>
• 지역적으로 강수량이 고르지 않아, 댐의 저수율이 불균등할 때
지역의 물 공급을 보충하는 중요한 역할을 수행
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
호주는 2000~2009년 강수량 감소로 밀레니엄 가뭄이 발생하여
심각한 물 부족 현상이 발생함
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
호주는 해안 도시에 인구가 밀집하여 거주하고 있으며 인구
증가율이 타 선진국 대비 높아, 향후 도시에 공급할 수자원 확보가
필요함
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
호주정부는 1990년대부터 하·폐수가 방류될 경우의 환경 피해에
대해 인식하기 시작하고 배출규제와 위원회를 구성하기 시작
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
연방정부는 주에 인센티브를 주는 방식으로 물 정책을 장려하고
토지소유권과 물 권리를 구분하여 거래를 허용하고 물 재이용
활성화를 위한 Water Smart Australia 프로그램을 시행
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
호주의 용수공급 체계는 영국과 미국의 특성들이 존재하지만, 주의
특성과 주 정부의 정책 방향에 따라 주마다도 많은 차이가 있음.
특히 대부분의 주에서 수 자원 개발 기능과 수도 공급의 기능을
분리하여 주정부 산하 기관에서 운영하는게 특징
</li>
</ul>
</li>
</ul>
</section>
</section>
<Hr className="my-16" />
{/* 싱가포르 */}
<section id="singapore" className="scroll-mt-[120px]">
<SectionTitle>싱가포르</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{
src: "/images/landing/business/globalCase/singapore_01.png",
alt: "싱가포르 사례 1",
},
]}
/>
<section className="mt-8">
<SectionTitle>싱가포르 NEWater 비중 확대</SectionTitle>
<div className="mt-6 px-4">
<ul className="space-y-4 text-base text-gray-600">
<li>
• 2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해
NEWater Visitor Center를 함께 건설
</li>
<li>• NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용</li>
<li>
• 2060년까지 NEWater 비중 55%까지 높이는 것이 싱가포르의 목표
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>싱가포르 재이용수 의무화 및 지원정책</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 공업용수 수요가 높은 산업에 대해 물 재이용을 의무화
<br />
<ul className="ml-4 mt-2 list-inside text-sm text-[#767676]">
<li>
– 반도체업계 전체 비 재이용수(non-domestic용수) 17% 차지 →
공업용수 수요 원인
</li>
<li>
– 2024년 1월 1일부터 연간 최소 60,000㎥를 소비하는 사업자는
재이용수 사용 의무화
</li>
<li>
– 반도체 공장의 경우 최소 50% 이상의 재이용수를 요구함(현재는
43% 정도)
</li>
</ul>
</li>
<li>
• 재정 지원을 통해 재이용 활성화를 지원
<br />
<ul className="ml-4 mt-2 list-inside text-sm text-[#767676]">
<li>
– 재이용 설비를 기준으로 2025년까지 싱가포르 수자원공사의
2가지 금융상품 제공
<ul className="ml-4 mt-2 list-inside text-[#767676]">
<li>1. Water Efficiency Fund</li>
<li>2. Industrial Water Solutions Demonstration Fund</li>
</ul>
</li>
<li className="mt-2">
– 2025년 기준 지원 한도 상향 → 물 재이용 활성 도모
<ul className="ml-4 mt-2 list-inside text-[#767676]">
<li>
투입비용 50% → 75% 지원 / WSDP: $400만 → $500만
</li>
<li>
물 효율이 높은 장비를 도입하는 데에도 최대 $300만 지원
가능
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
싱가포르는 천연 수자원이 많지 않아, 말레이시아 조호르와 1962년
맺은 협약으로 물을 수입해왔으며, 재이용수를 늘리기 위한 노력을
계속하고 있음
<br />
<ul className="ml-4 mt-2 list-inside text-sm text-[#767676]">
<li>
- 싱가포르는 국토 내에 호수와 하천이적고 집수공간도 적어
빗물로 확보할 수 있는 수자원량은 전체 물 수요의 20%에
불과함
</li>
</ul>
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
싱가포르는 도심에 저수지를 건설하 고 건물에 빗물이용시설을
건설하여 빗물을 재이용하고 있음
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
싱가포르는 2002년부터 하수재이용수인 NEWater를 개발, 비음
용수와 간접음용수로 활용하고 있음
<br />
<ul className="ml-4 mt-2 list-inside text-sm/6 text-[#767676]">
<li>
- 재이용수를 NEWater라는 이름으로 명명하고 NEWater에 대한
신뢰 확보를 위해 NEWater 방문자 센터도 건설함
</li>
<li>
- 비음용수로 많이 사용하며 건기에는 NEWater를 저수지에
추가하여 원수와 섞음. 원수는 상수도에서 처리 한 후 가정에
수돗물로 공급함
</li>
<li>
- NEWater는 전용 파이프를 통해 주로 웨이퍼 제조 공장,
산업단지, 상업용 건물에서 산업 및 냉각수로 사용. 제일 많이
사용하는 곳은 웨이퍼 제조 공장
</li>
</ul>
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
2024년 4월 기준 1㎥ 당 공업용수 가격은 $1.66이며,
Non-domestic 기준 NEWater 가격은 $2.41
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
공업용수 가격 $1.66은 수도요금(water tariff)과 수자원세
(waterborne tax)로 구분됨. $1.66에는 $1만큼의 수자원세가
포함됨
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
2023년 싱가포르는 산업용수 수요가 높은 산업에 대해 물 재
이용을 의무화했으며, 정부는 기금을 통해 물 재이용을 지원함
</li>
</ul>
</li>
</ul>
</section>
</section>
<Hr className="my-16" />
{/* EU */}
<section id="eu" className="scroll-mt-[120px] pb-[200px]">
<SectionTitle>EU</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{
src: "/images/landing/business/globalCase/eu_01.png",
alt: "EU 사례 1",
},
]}
/>
<section className="mt-8">
<SectionTitle>물 재이용에 대한 법률을 제정(2020 제정, 2023 시행)</SectionTitle>
<div className="mt-6 px-4">
<ul className="space-y-4 text-base text-gray-600">
<li>
• 유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을
제정 (2020/741)
</li>
<li>• 2023년부터 모든 EU국가에서 시행</li>
</ul>
</div>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>유럽연합 회원국 물 재이용 지원정책</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 유럽연합 차원에서 다양한 물 재이용 프로그램 자금 지원
<br />
<ul className="ml-4 mt-2 list-inside space-y-4 text-sm text-[#767676]">
<li>
– 2021년부터 3년간 Life RECYCLIO 프로그램 지원 → 세부내용
물 재이용 프로그램
<ul className="ml-4 mt-2 list-inside space-y-2 text-[#767676]">
<li>
1. 환경 관련 사업에 자금을 지원하는 Life 프로그램의
일환으로 80만 유로를 지원
</li>
<li>
2. 식품·농업 분야 물 재이용 확대를 위해 기술 이전 및 평가
실시
</li>
</ul>
</li>
<li>
– 물 재이용 정책 수행을 위해 BOOST-INN 180만 유로를 지원
<ul className="ml-4 mt-2 list-inside space-y-2 text-[#767676]">
<li>
*프랑스, 이탈리아, 스페인 등 유럽 선진국 솔루션 공유
</li>
</ul>
</li>
</ul>
</li>
<li>
• EU산하 JRC(공동연구센터)에서 물 재이용 기술제공 지원
<br />
<ul className="ml-4 mt-2 list-inside space-y-2 text-sm text-[#767676]">
<li>
‘물 재이용 회원 관리 실무 커뮤니티(Water Reuse Management
Community of Practice)’ 시범사업 수행
</li>
<li>
유럽 전역에 성공적인 모델 활용 및 혁신적인 물 재이용
솔루션을 확장할 수 있는 기술을 제공
</li>
</ul>
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
2019년 기준 EU 인구의 38%가 물 부족의 영향을 받을 것으로
예상됨. 이 흐름대로면 2030년까지 유럽 유역 전반에 물 부족
문제가 발생하게 될 것으로 예상. 인구 증가, 도시화의 확대,
염분화 및 기후변화들이 수자원 문제들의 주요 요인이며, 식습관
변화와 산업용수 수요 증가 등으로 악화되고 있는 상황
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
유럽의 물 부족 상황은 지역에 따라 다르게 나타나 스페인을
비롯한 남부유럽 에서 특히 심각함. 전반적으로 북유럽에 비해
남유럽의 물 스트레스가 심하며, 특히 스페인과 포르투갈, 프랑스
동부지역의 물 스트레스가 심각함
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside space-y-1 pl-6 leading-relaxed">
<li>
유럽 연합에서도 물 부족 문제를 인식하고 2000년 EU Water
Framework Directive을 개발, 이후 2020년 재이용에 관한 법률을
제정
</li>
</ul>
</li>
</ul>
</section>
</section>
</div>
);
}
@@ -1,172 +0,0 @@
// apps/dm/app/(landing)/reuse/business/globalCase/singapore/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="[싱가포르] 수요가 높은 산업의 물 재이용 의무화"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "해외 재이용 사례" },
{ label: "싱가포르", href: "/reuse/business/globalCase/singapore" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/globalCase/singapore_01.png", alt: "싱가포르 사례 1" },
]}
/>
{/* 싱가포르 NEWater 비중 확대 */}
<section className="mt-8">
<SectionTitle>싱가포르 NEWater 비중 확대</SectionTitle>
<div className="px-4 mt-6">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설
</li>
<li>
• NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용
</li>
<li>
• 2060년까지 NEWater 비중 55%까지 높이는 것이 싱가포르의 목표
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
{/* 싱가포르 재이용수 의무화 및 지원정책 */}
<section className="mt-6">
<SectionTitle>싱가포르 재이용수 의무화 및 지원정책</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 공업용수 수요가 높은 산업에 대해 물 재이용을 의무화<br />
<ul className="list-inside text-[#767676] text-sm ml-4 mt-2">
<li>– 반도체업계 전체 비 재이용수(non-domestic용수) 17% 차지 → 공업용수 수요 원인</li>
<li>– 2024년 1월 1일부터 연간 최소 60,000㎥를 소비하는 사업자는 재이용수 사용 의무화</li>
<li>– 반도체 공장의 경우 최소 50% 이상의 재이용수를 요구함(현재는 43% 정도)</li>
</ul>
</li>
<li>
• 재정 지원을 통해 재이용 활성화를 지원<br />
<ul className="list-inside text-[#767676] text-sm ml-4 mt-2">
<li>– 재이용 설비를 기준으로 2025년까지 싱가포르 수자원공사의 2가지 금융상품 제공<br/>
<ul className="list-inside text-[#767676] ml-4 mt-2">
<li>1. Water Efficiency Fund</li>
<li>2. Industrial Water Solutions Demonstration Fund</li>
</ul>
</li>
<li className="mt-2">– 2025년 기준 지원 한도 상향 → 물 재이용 활성 도모<br/>
<ul className="list-inside text-[#767676] ml-4 mt-2">
<li>투입비용 50% → 75% 지원 / WSDP: $400만 → $500만</li>
<li>물 효율이 높은 장비를 도입하는 데에도 최대 $300만 지원 가능</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</section>
<Hr className="my-11" />
{/* 특이사항 */}
<section className="pb-[200px]">
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>싱가포르는 천연 수자원이 많지 않아, 말레이시아 조호르와 1962년 맺은 협약으로 물을 수입해왔으며, 재이용수를 늘리기 위한 노력을 계속하고 있음<br/>
<ul className="list-inside text-[#767676] text-sm ml-4 mt-2">
<li>- 싱가포르는 국토 내에 호수와 하천이적고 집수공간도 적어 빗물로 확보할 수 있는 수자원량은 전체 물 수요의 20%에 불과함</li>
</ul>
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>싱가포르는 도심에 저수지를 건설하 고 건물에 빗물이용시설을 건설하여 빗물을 재이용하고 있음</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>싱가포르는 2002년부터 하수재이용수인 NEWater를 개발, 비음 용수와 간접음용수로 활용하고 있음<br/>
<ul className="list-inside text-[#767676] text-sm/6 ml-4 mt-2">
<li>- 재이용수를 NEWater라는 이름으로 명명하고 NEWater에 대한 신뢰 확보를 위해 NEWater 방문자 센터도 건설함</li>
<li>- 비음용수로 많이 사용하며 건기에는 NEWater를 저수지에 추가하여 원수와 섞음. 원수는 상수도에서 처리 한 후 가정에 수돗물로 공급함</li>
<li>- NEWater는 전용 파이프를 통해 주로 웨이퍼 제조 공장, 산업단지, 상업용 건물에서 산업 및 냉각수로 사용. 제일 많이 사용하는 곳은 웨이퍼 제조 공장</li>
</ul>
</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>2024년 4월 기준 1㎥ 당 공업용수 가격은 $1.66이며, Non-domestic 기준 NEWater 가격은 $2.41</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>공업용수 가격 $1.66은 수도요금(water tariff)과 수자원세(waterborne tax)로 구분됨. $1.66에는 $1만큼의 수자원세가 포함됨</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>2023년 싱가포르는 산업용수 수요가 높은 산업에 대해 물 재 이용을 의무화했으며, 정부는 기금을 통해 물 재이용을 지원함</li>
</ul>
</li>
</ul>
</section>
</div>
);
}
@@ -1,216 +0,0 @@
// apps/dm/app/(landing)/reuse/business/globalCase/taiwan/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="[대만] 국가 주도의 공업용수 공급"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "해외 재이용 사례" },
{ label: "대만", href: "/reuse/business/globalCase/taiwan" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/globalCase/taiwan_01.png", alt: "대만 사례 1" },
]}
/>
{/* 재생 수자원 개발법 제정(2015년제정, 2022개정) */}
<section className="mt-8">
<SectionTitle>재생 수자원 개발법 제정(2015년제정, 2022개정)</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-2 text-base text-gray-600">
<li>
• 목적: 수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결
</li>
<li>
• 내용: 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등
</li>
</ul>
<div className="rounded-sm bg-[#e9f0f1] p-8 mt-5">
<ul className="space-y-1 text-2xl text-[#1A1A1A]">
<li>
– 제4조. 물 소비 계획에 따른 재이용수 이용 관련 내용 (일정 비율 재이용수 사용 규정)
</li>
<li>
– 제5조. 재이용수의 운영자, 사용자 관련 내용 (지자체에서 하수 무상공급 제공 가능)
</li>
<li>
– 제7조. 재이용수 이용 범위, 이용량 재이용수 수질 기준 내용 (식품, 제약산업 사용 불가)
</li>
<li>
– 제8조. 재이용수 사업 주체, 책임 등에 관한 내용 (광역인 경우, 정부가 지자체 지정)
</li>
</ul>
</div>
</div>
</section>
<Hr className="my-11" />
{/* 대만정부 하수재이용 사업 시행 */}
<section className="mt-6">
<SectionTitle>대만정부 하수재이용 사업 시행</SectionTitle>
<div className="px-4">
<ul className="mt-6 space-y-4 text-base text-gray-600">
<li>
• 대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑)<br />
<ul className="list-inside text-[#767676] ml-4 mt-2">
<li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li>
<li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li>
</ul>
</li>
<li>
• 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능
</li>
<li>
• 공급단가는 수질과 운영조건에 따라 다르게 책정
</li>
</ul>
{/* 테이블 (2단 병렬: 좌/우 데이터) */}
<div className="mt-3">
<div className="overflow-x-auto">
<p className="text-center text-2xl py-2">〈대만 재이용 시설별 재이용수 공급단지〉</p>
<table className="min-w-[900px] w-full text-center text-sm">
<thead className="border">
<tr>
<th className="bg-[#C2CAD2] px-4 py-3 text-black">사업</th>
<th className="bg-sky-600 px-4 py-3 text-white">가격(NTD/㎥)</th>
<th className="bg-green-500 px-4 py-3 text-white">가격(원/㎥)</th>
</tr>
</thead>
<tbody className="bg-white text-[#4F4F4F] border border-t-0">
<tr className="border-t">
<td className="px-4 py-3 bg-white">Fongshan(가오슝)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">23.39</td>
<td className="px-4 py-3 bg-[#E7F4E2]">1,038원</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 bg-white">Linhai(가오슝)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">19.28</td>
<td className="px-4 py-3 bg-[#E7F4E2]">858원</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 bg-white">Anping(타이난)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">38.28</td>
<td className="px-4 py-3 bg-[#E7F4E2]">1,699원</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 bg-white">Yongkang(타이난)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">23.67</td>
<td className="px-4 py-3 bg-[#E7F4E2]">1,050원</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 bg-white">Rende(타이난)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">28.26</td>
<td className="px-4 py-3 bg-[#E7F4E2]">1,254원</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 bg-white">Shuinan(타이중)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">15.00</td>
<td className="px-4 py-3 bg-[#E7F4E2]">668원</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3 bg-white">Quaitou(가오슝)</td>
<td className="px-4 py-3 bg-[#EAF5F5]">35.44</td>
<td className="px-4 py-3 bg-[#E7F4E2]">1,573원</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<Hr className="my-11" />
{/* 특이사항 */}
<section className="pb-[200px]">
<SectionTitle>특이사항</SectionTitle>
<ul className="mt-6 space-y-4 pl-4 text-base text-gray-600">
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>대만은 풍부한 강수량에도, 지형적 특성으로 인해 수자원 확보에 어려움을 겪고 있으며, 대만 남부지역의 경우 우기와 건기 사이의 강수량 차이가 큼</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>대만은 최근 심각한 가뭄이 여러 해에 걸쳐서 발생하고 있으며, 강수량 감소와 함께 대만 정부 주도하에 반도체 산업을 국가 핵심 산업으로 선정하고 반도체 산업 육성 계획을 세움. 그 결과, 2023년 파운드리 시장의 68.3%를 차지하는 반도체 강국으로 성장했으며 이로 인한 공업용수 수요가 크게 증가</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>반도체 산업은 물 소비가 많아, 안정적인 용수공급이 매우 중요한데 현재 19개의 반도체 공장이 물이 부족한 대만 남부지역에 위치함. 2021년 가뭄 발생 당시, 산업단지 기업들도 용수 공급에 어려움을 겪음. 이는 대만 남부 지역의 물 부족, 물 분배 등에 대한 문제를 야기함</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>대만 정부는 산업단지 기업들에게 2023년부터 물에 대한 추가요금 부과를 시작했으며, 농업용수에 대해서는 강력한 억제정책을 시행함. 안정적인 용수 공급을 위해서 정부와 기업에서 자체적으로 재이용수 시설에 대한 투자가 이루어지고 있음.</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>대만은 2004년 기후변화로 인한 물 부족 상황을 해결하기 위해 수자원 보호 전략을 승인하고, 2015년 대만 환경부는 재생 수자원 개발법을 발표, 2022년에는 한 차례 개정</li>
</ul>
</li>
<li>
<ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed">
<li>대만은 물 소비량이 많은 산업(high water consuming industries)과 하수처리장(municipal wastewater treatment plants), 두 곳을 재이용수의 주요 공급원으로 계획</li>
</ul>
</li>
</ul>
</section>
</div>
);
}
@@ -0,0 +1,232 @@
"use client";
import SubPageHeader from "@/components/SubPageHeader";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-10 w-[4px] bg-sky-500" />
<h3 className="text-[38px] font-bold leading-none text-neutral-900">
{children}
</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="inline-block h-[14px] w-[24px] rounded-full bg-gradient-to-r from-sky-300 to-lime-300" />
<h4 className="text-[28px] font-bold text-[#2B2B2B]">{children}</h4>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type SupplyRow = {
facility: string;
usage: string;
amount: string;
};
function SupplyTable({ rows }: { rows: SupplyRow[] }) {
return (
<div className="mt-8 overflow-x-auto">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-5 text-[24px] font-bold text-white">
시설명
</th>
<th className="border border-[#D8DDE3] bg-[#40B5A5] px-4 py-5 text-[24px] font-bold text-white">
이용용도
</th>
<th className="border border-[#D8DDE3] bg-[#73BF4B] px-4 py-5 text-[24px] font-bold text-white">
공급량(㎥)
</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={`${row.facility}-${index}`}>
<td className="border border-[#D8DDE3] bg-white px-4 py-4 text-[22px] text-[#4F4F4F]">
{row.facility}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-4 text-[22px] text-[#4F4F4F]">
{row.usage}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-4 text-[22px] text-[#4F4F4F]">
{row.amount}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function InfoList({
projectName,
purpose,
totalCost,
treatment,
}: {
projectName: string;
purpose: string;
totalCost: string;
treatment: string;
}) {
return (
<div className="mt-10 space-y-10">
<div>
<BlockTitle>사업명</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[22px] leading-[1.8] text-[#4F4F4F]">
<li>{projectName}</li>
</ul>
</div>
<div>
<BlockTitle>사업목적</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[22px] leading-[1.8] text-[#4F4F4F]">
<li>{purpose}</li>
</ul>
</div>
<div>
<BlockTitle>사업 주요 내용</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[22px] leading-[1.8] text-[#4F4F4F]">
<li>총사업비 : {totalCost}</li>
<li>처리공법 : {treatment}</li>
</ul>
</div>
<div>
<BlockTitle>사업량</BlockTitle>
</div>
</div>
);
}
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="중수 재이용 사례"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "중수 재이용 사례" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 안산시 */}
<section id="ansan" className="scroll-mt-[120px]">
<SectionTitle>경기도 안산시</SectionTitle>
<InfoList
projectName="안산시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="81억원"
treatment="MBR공법"
/>
<SupplyTable
rows={[
{
facility: "경기테크노파크",
usage: "청소·화장실용수",
amount: "40㎥/일",
},
{
facility: "올림픽수영장",
usage: "청소·화장실/세척·살수용수",
amount: "35㎥/일",
},
{
facility: "와스타디움",
usage: "청소·화장실/조경용수",
amount: "90㎥/일",
},
{
facility: "호수실내수영장",
usage: "청소·화장실/조경/세척·살수용수",
amount: "50㎥/일",
},
{
facility: "한양대학교 에리카캠퍼스",
usage: "청소·화장실용수",
amount: "370㎥/일",
},
]}
/>
</section>
<Hr className="my-20" />
{/* 용인시 */}
<section id="yongin" className="scroll-mt-[120px]">
<SectionTitle>경기도 용인시</SectionTitle>
<InfoList
projectName="용인시 중수도 설치사업"
purpose="물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용"
totalCost="40억원"
treatment="MBR공법"
/>
<SupplyTable
rows={[
{
facility: "여성회관",
usage: "청소·화장실용수",
amount: "35㎥/일",
},
{
facility: "수지아르피아",
usage: "청소·화장실용수",
amount: "35㎥/일",
},
{
facility: "지상작전사령부",
usage: "청소·화장실/조경/세척·살수용수",
amount: "53㎥/일",
},
]}
/>
</section>
<Hr className="my-20" />
{/* 평택시 */}
<section id="pyeongtaek" className="scroll-mt-[120px] pb-[200px]">
<SectionTitle>경기도 평택시</SectionTitle>
<InfoList
projectName="평택시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="21억원"
treatment="MBR공법"
/>
<SupplyTable
rows={[
{
facility: "평택대학교",
usage: "청소·화장실용수",
amount: "50㎥/일",
},
]}
/>
</section>
</div>
);
}
+14 -4
View File
@@ -1,11 +1,21 @@
"use client";
import type { ReactNode } from "react";
import Lnb from "@/components/Lnb";
// import BusinessSubNav from "@/components/BusinessSubNav";
export default function BusinessLayout({ children }: { children: ReactNode }) {
export default function ExampleLayout({ children }: { children: ReactNode }) {
return (
<div className="flex gap-0">
<div className="relative">
<Lnb />
<main className="flex-1 px-[100px] pt-[80px]">{children}</main>
<div className="flex max-w-[1400px] gap-[56px] py-[50px]">
<main className="min-w-0 flex-1">
{children}
</main>
{/* 우측 메뉴 영역 */}
{/* <BusinessSubNav /> */}
</div>
</div>
);
}
}
@@ -0,0 +1,85 @@
import SubPageHeader from "@/components/SubPageHeader";
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import RainExampleSection from "./_components/RainExampleSection";
import GreywaterExampleSection from "./_components/GreywaterExampleSection";
import SewageExampleSection from "./_components/SewageExampleSection";
import GlobalCaseSection from "./_components/GlobalCaseSection";
const menus: BusinessSubNavMenu[] = [
{
key: "example-rain",
label: "빗물 재이용 사례",
sections: [
{ id: "example-rain-suwon", label: "경기도 수원시" },
{ id: "example-rain-jeju", label: "제주특별자치도" },
],
},
{
key: "example-greywater",
label: "중수 재이용 사례",
defaultOpen: false,
sections: [
{ id: "example-greywater-ansan", label: "경기도 안산시" },
{ id: "example-greywater-yongin", label: "경기도 용인시" },
{ id: "example-greywater-pyeongtaek", label: "경기도 평택시" },
],
},
{
key: "example-sewage",
label: "하수처리 재이용 사례",
defaultOpen: false,
sections: [
{ id: "example-sewage-guri", label: "경기도 구리시" },
{ id: "example-sewage-bucheon", label: "경기도 부천시" },
{ id: "example-sewage-suwon", label: "경기도 수원시" },
{ id: "example-sewage-paju", label: "경기도 파주시" },
{ id: "example-sewage-pohang", label: "경기도 포항시" },
{ id: "example-sewage-ulsan", label: "울산광역시" },
{ id: "example-sewage-yeosu", label: "전라남도 여수시" },
{ id: "example-sewage-boryeong", label: "충청남도 보령시" },
{ id: "example-sewage-asan", label: "충청남도 아산시" },
],
},
{
key: "example-global",
label: "해외 재이용 사례",
defaultOpen: false,
sections: [
{ id: "example-global-overview", label: "사례 개요" },
{ id: "example-global-taiwan", label: "대만" },
{ id: "example-global-singapore", label: "싱가포르" },
{ id: "example-global-australia", label: "호주" },
{ id: "example-global-eu", label: "EU" },
],
},
];
// function Hr({ className = "" }: { className?: string }) {
// return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
// }
export default function ExamplePage() {
return (
<div className="relative">
{/* 상단 헤더 */}
<SubPageHeader
title="물 재이용 사례"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 사례" },
]}
/>
{/* 우측 앵커 네비 */}
<BusinessSubNav menus={menus} offsetTop={120} />
<div className="max-w-[1320px] pb-24">
<RainExampleSection />
<GreywaterExampleSection />
<SewageExampleSection />
<GlobalCaseSection />
</div>
</div>
);
}
@@ -0,0 +1,69 @@
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/ImageSlider";
import BusinessSubNav from "@/components/BusinessSubNav";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function RainPage() {
return (
<div className="relative bg-white">
{/* ✅ 추가 */}
<BusinessSubNav />
<SubPageHeader
title="빗물 재이용 사례"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 사례", href: "/example/rain" },
{ label: "빗물 재이용 사례" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
<section id="suwon" className="scroll-mt-[120px]">
<SectionTitle>경기도 수원시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/rain/rain_01.png", alt: "수원시 사례 1" },
{ src: "/images/landing/business/rain/rain_02.png", alt: "수원시 사례 2" },
]}
/>
</section>
<Hr className="my-16" />
<section id="jeju" className="scroll-mt-[120px] pb-[120px]">
<SectionTitle>제주특별자치도</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/rain/jeju_01.png", alt: "제주 사례 1" },
{ src: "/images/landing/business/rain/jeju_02.png", alt: "제주 사례 2" },
]}
/>
</section>
</div>
);
}
@@ -1,93 +0,0 @@
// apps/dm/app/(landing)/reuse/business/rain/suwon/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="경기도 수원시 빗물이용과 중수도"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "빗물 재이용 사례" },
{ label: "수원시", href: "/reuse/business/rain/suwon" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/rain/rain_01.png", alt: "수원시 하수 재이용 1" },
{ src: "/images/landing/business/rain/rain_02.png", alt: "수원시 하수 재이용 2" },
{ src: "/images/landing/business/rain/rain_03.png", alt: "수원시 하수 재이용 3" },
{ src: "/images/landing/business/rain/rain_04.png", alt: "수원시 하수 재이용 4" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
경기도 수원시 빗물이용과 중수도
</p>
</section>
<Hr className="my-11" />
{/* 사업 방식 */}
<section className="pb-[200px]">
<SectionTitle>사업 방식</SectionTitle>
<p className="mt-8 text-base text-gray-600">
(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로 비산먼지 저감<br/>
※ 시설 가동중 도로 노면 온도의 7.2 ~ 26.3℃ 감소 효과 확인(‘21.7월, 수원시청).<br/>
다만, 가뭄기나 강우량이 적을 경우 빗물을 원활히 공급하기 다소 곤란<br/><br/>
- (중수도) 수원시청, 장안구청, 월드컵경기장에 빗물 연계 중수도를 화장실·조경용수 등으로 사용하여 수도요금 절감(연 7백~30백만원)
</p>
</section>
</div>
);
}
@@ -1,111 +0,0 @@
// apps/dm/app/(landing)/reuse/business/sewage/bolyeong/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="충남보령 하수 재이용"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
{ label: "충청남도 보령시", href: "/reuse/business/sewage/bolyeong" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/sewage/bolyeong_01.png", alt: "보령시 하수 재이용 1" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
충남 보령 하수재이용<br/>
: 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진
</p>
</section>
<Hr className="my-11" />
{/* 사업 방식 */}
<section className="mt-6">
<SectionTitle>사업 방식</SectionTitle>
<p className="mt-8 text-base text-gray-600">
2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결
</p>
</section>
{/* 사업 주요 내용 */}
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
처리공정 : 전처리 UF + 후처리 RO<br/>
시설용량 : 1만m³/일<br/>
관로 : 8.7km<br/>
사업비 : 315억원<br/>
위치: 충청남도 보령시 대청방조제로 322 일원
</p>
</section>
<Hr className="my-11" />
{/* 특이사항 */}
<section className="pb-[200px]">
<SectionTitle>특이사항</SectionTitle>
<p className="mt-8 text-base text-gray-600">
국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로<br/>
국가전력 기반시설의 안정적 운영 도모
</p>
</section>
</div>
);
}
@@ -1,94 +0,0 @@
// apps/dm/app/(landing)/reuse/business/sewage/cheongju/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="청주시 하수 재이용 시설"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
{ label: "청주시", href: "/reuse/business/sewage/cheongju" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/sewage/cheongju_01.png", alt: "청주시 하수 재이용 1" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
청주시 하수 재이용 시설
</p>
</section>
<Hr className="my-11" />
{/* 사업 주요 내용 */}
<section className="pb-[200px]">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
청주시 하수처리수 재이용 민간투자사업(BTO)<br/>
공급량 : 35,000㎥/일<br/>
공급관로 : 8.3㎞<br/>
사업비 : 622억원<br/>
수요처 : SK하이닉스 등 일반산업단지<br/>
지역 (위치) : 충청북도 청주시 흥덕구 가락리 135-9번지 일원<br/>
처리용량 : 35.000(m³/일)<br/>
적용기술 : 분리막 (UF : 전처리), (RO : 주처리)
</p>
</section>
</div>
);
}
@@ -1,148 +0,0 @@
// apps/dm/app/(landing)/reuse/business/sewage/guri/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="구리시 도로자동청소시스템"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
{ label: "구리시", href: "/reuse/business/sewage/guri" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/sewage/guri_01.png", alt: "구리시 도로자동청소시스템 1" },
{ src: "/images/landing/business/sewage/guri_02.png", alt: "구리시 도로자동청소시스템 2" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
구리시 도로자동청소시스템 설치사업
</p>
</section>
<Hr className="my-11" />
{/* 사업목적 */}
<section className="mt-6">
<SectionTitle>사업목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화
</p>
</section>
<Hr className="my-11" />
{/* 사업 주요 내용 */}
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
총사업비 : 61억원<br/>
위 치 : 구리시 경춘로 및 갈매중앙로 일대 등<br/>
관 로 : 5.8km<br/>
공급계획<br/>
</p>
</section>
<Hr className="my-11" />
{/* 공급계획 */}
<section className="pb-[200px]">
<SectionTitle>공급계획</SectionTitle>
{/* 데이터 테이블 */}
<div className="overflow-x-auto mt-8">
<table className="w-full border border-[#E3E7EA] text-center">
<thead>
<tr>
<th className="bg-[#01ACE5] text-white font-semibold py-4 border border-gray-300">
시설명
</th>
<th className="bg-[#33B5C8] text-white font-semibold py-4 border border-gray-300">
재이용수 용도
</th>
<th className="bg-[#55BCA2] text-white font-semibold py-4 border border-gray-300">
공급량(㎥/일)
</th>
<th className="bg-[#6DBB4F] text-white font-semibold py-4 border border-gray-300">
원수
</th>
</tr>
</thead>
<tbody>
<tr>
<td
className="bg-white py-6 border border-gray-300 text-gray-700"
>
도로자동청소시스템
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700">
세척살수용수
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700">
178
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700">
구리 공공하수처리장 재이용수
</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
);
}
@@ -1,173 +0,0 @@
// apps/dm/app/(landing)/reuse/business/sewage/hwanggu/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="수원시 황구지천 하천유지용수"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
{ label: "수원시 황구지천", href: "/reuse/business/sewage/hwanggu" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/sewage/hwanggu_01.png", alt: "수원시 황구지천 하수 재이용 1" },
{ src: "/images/landing/business/sewage/hwanggu_02.png", alt: "수원시 황구지천 하수 재이용 2" },
{ src: "/images/landing/business/sewage/hwanggu_03.png", alt: "수원시 황구지천 하수 재이용 3" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
수원시 황수원시 황구지천 하수처리수 재이용사업
</p>
</section>
<Hr className="my-11" />
{/* 사업목적 */}
<section className="mt-6">
<SectionTitle>사업목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전
</p>
</section>
<Hr className="my-11" />
{/* 사업 주요 내용 */}
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
총사업비 : 110억원<br/>
위 치 : 수원시 권선구 호매실천, 금곡천<br/>
관 로 : 3.9km<br/>
공 급 량 : 25,000㎥/일(하수처리시설 용량: 45,000㎥/일)<br/>
</p>
</section>
<Hr className="my-11" />
{/* 공급계획 */}
<section className="pb-[200px]">
<SectionTitle>공급계획</SectionTitle>
{/* 데이터 테이블 */}
<div className="overflow-x-auto mt-8">
<table className="w-full border border-[#E3E7EA] text-center">
<thead>
<tr>
{/* 구분 */}
<th className="w-1/4 bg-[#E3E7EA] text-black font-semibold py-4 border border-gray-300">
구분
</th>
{/* 공급량 */}
<th className="w-2/4 bg-[#1FA3C6] text-white font-semibold py-4 border border-gray-300">
공급량
</th>
{/* 비고 */}
<th className="w-2/4 bg-[#72B84F] text-white font-semibold py-4 border border-gray-300">
비고
</th>
</tr>
</thead>
<tbody>
<tr>
{/* 좌측 병합 */}
<td
rowSpan={3}
className="bg-white font-medium text-gray-700 border border-gray-300"
>
하천 등 유지용수
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium">
12,500m³/일
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700">
호매실천 공급
</td>
</tr>
<tr>
<td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium">
6,250m³/일
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700">
금곡천1 공급
</td>
</tr>
<tr>
<td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium">
6,250m³/일
</td>
<td className="bg-white py-6 border border-gray-300 text-gray-700">
금곡천2 공급
</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
);
}
@@ -0,0 +1,477 @@
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
export default function SewagePage() {
return (
<div className="bg-white">
<SubPageHeader
title="하수 재이용 사례"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 보령 */}
<section id="bolyeong" className="scroll-mt-[120px]">
<SectionTitle>충청남도 보령시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/bolyeong_01.png",
alt: "보령시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
충남 보령 하수재이용
<br />: 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해
보령하수처리시설의 하수처리수 재이용 사업 추진
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업 방식</SectionTitle>
<p className="mt-8 text-base text-gray-600">
2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결
</p>
</section>
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
처리공정 : 전처리 UF + 후처리 RO
<br />
시설용량 : 1만m³/일
<br />
관로 : 8.7km
<br />
사업비 : 315억원
<br />
위치: 충청남도 보령시 대청방조제로 322 일원
</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<p className="mt-8 text-base text-gray-600">
국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권
가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시,
한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로
<br />
국가전력 기반시설의 안정적 운영 도모
</p>
</section>
</section>
<Hr className="my-16" />
{/* 청주 */}
<section id="cheongju" className="scroll-mt-[120px]">
<SectionTitle>청주시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/cheongju_01.png",
alt: "청주시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
청주시 하수 재이용 시설
</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
청주시 하수처리수 재이용 민간투자사업(BTO)
<br />
공급량 : 35,000㎥/일
<br />
공급관로 : 8.3㎞
<br />
사업비 : 622억원
<br />
수요처 : SK하이닉스 등 일반산업단지
<br />
지역 (위치) : 충청북도 청주시 흥덕구 가락리 135-9번지 일원
<br />
처리용량 : 35.000(m³/일)
<br />
적용기술 : 분리막 (UF : 전처리), (RO : 주처리)
</p>
</section>
</section>
<Hr className="my-16" />
{/* 구리 */}
<section id="guri" className="scroll-mt-[120px]">
<SectionTitle>구리시 도로자동청소시스템</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/guri_01.png",
alt: "구리시 도로자동청소시스템 1",
},
{
src: "/images/landing/business/sewage/guri_02.png",
alt: "구리시 도로자동청소시스템 2",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
구리시 도로자동청소시스템 설치사업
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생
재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염
취약성 완화
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
총사업비 : 61억원
<br />
위 치 : 구리시 경춘로 및 갈매중앙로 일대 등
<br />
관 로 : 5.8km
<br />
공급계획
</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>공급계획</SectionTitle>
<div className="mt-8 overflow-x-auto">
<table className="w-full border border-[#E3E7EA] text-center">
<thead>
<tr>
<th className="border border-gray-300 bg-[#01ACE5] py-4 font-semibold text-white">
시설명
</th>
<th className="border border-gray-300 bg-[#33B5C8] py-4 font-semibold text-white">
재이용수 용도
</th>
<th className="border border-gray-300 bg-[#55BCA2] py-4 font-semibold text-white">
공급량(㎥/일)
</th>
<th className="border border-gray-300 bg-[#6DBB4F] py-4 font-semibold text-white">
원수
</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
도로자동청소시스템
</td>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
세척살수용수
</td>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
178
</td>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
구리 공공하수처리장 재이용수
</td>
</tr>
</tbody>
</table>
</div>
</section>
</section>
<Hr className="my-16" />
{/* 황구지천 */}
<section id="hwanggu" className="scroll-mt-[120px]">
<SectionTitle>수원시 황구지천</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/hwanggu_01.png",
alt: "수원시 황구지천 하수 재이용 1",
},
{
src: "/images/landing/business/sewage/hwanggu_02.png",
alt: "수원시 황구지천 하수 재이용 2",
},
{
src: "/images/landing/business/sewage/hwanggu_03.png",
alt: "수원시 황구지천 하수 재이용 3",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
수원시 황수원시 황구지천 하수처리수 재이용사업
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및
금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
총사업비 : 110억원
<br />
위 치 : 수원시 권선구 호매실천, 금곡천
<br />
관 로 : 3.9km
<br />
공 급 량 : 25,000㎥/일(하수처리시설 용량: 45,000㎥/일)
<br />
</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>공급계획</SectionTitle>
<div className="mt-8 overflow-x-auto">
<table className="w-full border border-[#E3E7EA] text-center">
<thead>
<tr>
<th className="w-1/4 border border-gray-300 bg-[#E3E7EA] py-4 font-semibold text-black">
구분
</th>
<th className="w-2/4 border border-gray-300 bg-[#1FA3C6] py-4 font-semibold text-white">
공급량
</th>
<th className="w-2/4 border border-gray-300 bg-[#72B84F] py-4 font-semibold text-white">
비고
</th>
</tr>
</thead>
<tbody>
<tr>
<td
rowSpan={3}
className="border border-gray-300 bg-white font-medium text-gray-700"
>
하천 등 유지용수
</td>
<td className="border border-gray-300 bg-white py-6 text-lg font-medium text-gray-700">
12,500m³/일
</td>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
호매실천 공급
</td>
</tr>
<tr>
<td className="border border-gray-300 bg-white py-6 text-lg font-medium text-gray-700">
6,250m³/일
</td>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
금곡천1 공급
</td>
</tr>
<tr>
<td className="border border-gray-300 bg-white py-6 text-lg font-medium text-gray-700">
6,250m³/일
</td>
<td className="border border-gray-300 bg-white py-6 text-gray-700">
금곡천2 공급
</td>
</tr>
</tbody>
</table>
</div>
</section>
</section>
<Hr className="my-16" />
{/* 파주 */}
<section id="paju" className="scroll-mt-[120px]">
<SectionTitle>파주시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/paju_01.png",
alt: "파주시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
경기도 파주시 하수재이용
<br />: 운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로
재처리하여 파주 LCD 산업단지내 3개 업체*에 공업용수 공급
<br />
*LG디스플레이(P10공장), 희성전자, 엔비텍 등
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업 배경 및 목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)
</p>
</section>
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
처리공정 : 전처리 UF + 후처리 RO
<br />
공급량 : 4만m³/일
<br />
관로 : 13.6km
<br />
사업비 : 651억원
<br />
위치 : 경기도 파주시 금촌동 714번지 일원
<br />
시설용량 : 41,20m³/일
</p>
</section>
<Hr className="my-11" />
<section>
<SectionTitle>특이사항</SectionTitle>
<p className="mt-8 text-base text-gray-600">
하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례
</p>
</section>
</section>
<Hr className="my-16" />
{/* 여수 */}
<section id="yeosu" className="scroll-mt-[120px] pb-[200px]">
<SectionTitle>여수시</SectionTitle>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{
src: "/images/landing/business/sewage/yeosu_01.png",
alt: "여수시 하수 재이용 1",
},
]}
/>
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
여수시 하수처리수 재이용 민간 투자사업(BTO)
</p>
</section>
<Hr className="my-11" />
<section className="mt-6">
<SectionTitle>사업 목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
수요자가 원하는 양질의 공업용수를 저렴하게 곰급하여 한정된
수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한
오염총량 저감 및 방류수역 수질개선
</p>
</section>
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
총사업비 : GS칼텍스, YNCC(주) 등 750억원
<br />
시행사 : 여수그린허브(주)
<br />
운영사 : 테크로스
<br />
위치: 여수시 신월로 2844일원 (여수 하수처리시설 부지 내)
<br />
처리공법 : 전처리시설(MF) + 주처리시설(RO)
<br />
시설용량 및 공급량 : 5만m³/일
<br />
관로 : 18.6km
</p>
</section>
</section>
</div>
);
}
@@ -1,112 +0,0 @@
// apps/dm/app/(landing)/reuse/business/sewage/paju/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="경기도 파주시 하수 재이용"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
{ label: "파주시", href: "/reuse/business/sewage/paju" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/sewage/paju_01.png", alt: "파주시 하수 재이용 1" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
경기도 파주시 하수재이용<br/>
: 운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에 공업용수 공급<br/>
*LG디스플레이(P10공장), 희성전자, 엔비텍 등
</p>
</section>
<Hr className="my-11" />
{/* 사업 배경 및 목적 */}
<section className="mt-6">
<SectionTitle>사업 배경 및 목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)
</p>
</section>
{/* 사업 주요 내용 */}
<section className="mt-6">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
처리공정 : 전처리 UF + 후처리 RO<br/>
공급량 : 4만m³/일<br/>
관로 : 13.6km<br/>
사업비 : 651억원<br/>
위치 : 경기도 파주시 금촌동 714번지 일원<br/>
시설용량 : 41,20m³/일
</p>
</section>
<Hr className="my-11" />
{/* 특이사항 */}
<section className="pb-[200px]">
<SectionTitle>특이사항</SectionTitle>
<p className="mt-8 text-base text-gray-600">
하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례
</p>
</section>
</div>
);
}
@@ -1,101 +0,0 @@
// apps/dm/app/(landing)/reuse/business/sewage/yeosu/page.tsx
"use client";
import SubPageHeader from "@/components/SubPageHeader";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
// type ImgCardProps = {
// src: string;
// alt: string;
// };
// function ImgCard({ src, alt }: ImgCardProps) {
// return (
// <div className="w-full overflow-x-auto">
// <img
// src={src}
// alt={alt}
// className="block mx-auto max-w-none"
// />
// </div>
// );
// }
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="여수시 하수처리수 재이용 민간투자사업(BTO)"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 사례" },
{ label: "하수 재이용 사례" },
{ label: "여수시", href: "/reuse/business/sewage/yeosu" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 슬라이드 이미지 영역 */}
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[760px]"
slides={[
{ src: "/images/landing/business/sewage/yeosu_01.png", alt: "여수시 하수 재이용 1" },
]}
/>
{/* 사업명 */}
<section className="mt-6">
<SectionTitle>사업명</SectionTitle>
<p className="mt-8 text-base text-gray-600">
여수시 하수처리수 재이용 민간 투자사업(BTO)
</p>
</section>
<Hr className="my-11" />
{/* 사업 목적 */}
<section className="mt-6">
<SectionTitle>사업 목적</SectionTitle>
<p className="mt-8 text-base text-gray-600">
수요자가 원하는 양질의 공업용수를 저렴하게 곰급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선
</p>
</section>
{/* 사업 주요 내용 */}
<section className="pb-[200px]">
<SectionTitle>사업 주요 내용</SectionTitle>
<p className="mt-8 text-base text-gray-600">
총사업비 : GS칼텍스, YNCC(주) 등 750억원<br/>
시행사 : 여수그린허브(주)<br/>
운영사 : 테크로스<br/>
위치: 여수시 신월로 2844일원 (여수 하수처리시설 부지 내)<br/>
처리공법 : 전처리시설(MF) + 주처리시설(RO)<br/>
시설용량 및 공급량 : 5만m³/일<br/>
관로 : 18.6km
</p>
</section>
</div>
);
}
@@ -0,0 +1,123 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import lawPlan from "@/public/images/landing/intro/law_timeline.png";
import planPerformance from "@/public/images/landing/intro/performance_left.png";
import domesticChange from "@/public/images/landing/intro/domestic_textbox.png";
import policyHistory from "@/public/images/landing/intro/policy_timeline.png";
import lawRelation from "@/public/images/landing/intro/rel_note.png";
import domesticLinkage from "@/public/images/landing/intro/linkage.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function LawIntroSection() {
const title = "물 재이용 법";
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell
id="law-plan"
title="법률 제·개정 및 기본계획 수립"
className="mt-10"
>
<ImgCard src={lawPlan} alt="법률 제·개정 및 기본계획 수립" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="plan-performance"
title="제2차 계획 대비 실적 현황"
>
<ImgCard src={planPerformance} alt="제2차 계획 대비 실적 현황" />
</SectionShell>
<Hr className="my-11" />
<SectionShell id="domestic-change" title="우리나라의 수자원 현황 및 물이용 여건">
<ImgCard src={domesticChange} alt="우리나라의 수자원 현황 및 물이용 여건" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="policy-history"
title="물 재이용 정책 변화 추이"
>
<ImgCard src={policyHistory} alt="물 재이용 정책 변화 추이" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="law-relation"
title="타법령과의 관계 검토"
>
<ImgCard src={lawRelation} alt="타법령과의 관계 검토" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="domestic-linkage"
title="주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요"
>
<ImgCard src={domesticLinkage} alt="주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요" />
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,99 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import rainSummary from "@/public/images/landing/intro/rainreuse_intro.png";
import rainDuty from "@/public/images/landing/intro/rainreuse_mandatory.png";
import rainDesign from "@/public/images/landing/intro/rainwater_design.png";
import rainStandard from "@/public/images/landing/intro/rainwater_standards.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function RainIntroSection() {
const title = "빗물 이용시설";
return (
<div className="mt-[120px] bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell id="rain-intro" title="빗물 재이용 소개" className="mt-10">
<ImgCard src={rainSummary} alt="빗물 재이용 소개" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="rain-mandatory"
title="빗물이용시설 설치 관리 법적 의무 대상"
>
<ImgCard src={rainDuty} alt="빗물이용시설 설치 관리 법적 의무 대상" />
</SectionShell>
<Hr className="my-11" />
<SectionShell id="rain-design" title="빗물이용시설 설계 방안">
<ImgCard src={rainDesign} alt="빗물이용시설 설계 방안" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="rain-standards"
title="빗물이용시설 시설기준 및 관리기준"
>
<ImgCard src={rainStandard} alt="빗물이용시설 시설기준 및 관리기준" />
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,96 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png";
import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png";
import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function ReuseWaterIntroSection() {
const title = "중수도";
return (
<div className="mt-[120px] bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell
id="greywater-intro"
title="중수 재이용 소개"
className="mt-10"
>
<ImgCard src={greywaterIntro} alt="중수 재이용 소개" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="greywater-mandatory"
title="중수도 설치 의무 대상"
>
<ImgCard src={greywaterMandatory} alt="중수도 설치 의무 대상" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="greywater-standard"
title="중수도 시설기준 및 관리기준"
>
<ImgCard src={greywaterStandard} alt="중수도 시설기준 및 관리기준" />
</SectionShell>
</div>
</div>
);
}
@@ -0,0 +1,89 @@
"use client";
import React from "react";
import Image, { type StaticImageData } from "next/image";
import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png";
import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[120px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function TreatedIntroSection() {
const title = "하수처리수";
return (
<div className="mt-[120px] bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-10">
<SectionShell
id="treatedwater-intro"
title="하수처리수 소개"
className="mt-10"
>
<ImgCard src={treatedwaterIntro} alt="하수처리수" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="treatedwater-mandatory"
title="하수처리수 재이용시설 의무 대상"
>
<ImgCard
src={treatedwaterMandatory}
alt="하수처리수 재이용시설 의무 대상"
/>
</SectionShell>
</div>
</div>
);
}
@@ -1,91 +0,0 @@
// apps/dm/app/(landing)/reuse/intro/greywater/page.tsx
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png";
import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png";
import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type ImgCardProps = {
src: StaticImageData;
alt: string;
};
function ImgCard({ src, alt }: ImgCardProps) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
export default function GreywaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="중수도"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 소개", href: "/reuse/intro/law" },
{ label: "중수도" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 1) 중수 재이용 소개 */}
<section className="mt-6">
<SectionTitle>중수 재이용 소개</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterIntro} alt="중수 재이용 소개" />
</div>
</section>
<Hr className="my-11" />
{/* 2) 중수도 설치 의무 대상 */}
<section>
<SectionTitle>중수도 설치 의무 대상</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterMandatory} alt="중수도 설치 의무 대상 설명" />
</div>
</section>
<Hr className="my-11" />
{/* 3) 중수도 시설기준 및 관리기준 */}
<section className="pb-[200px]">
<SectionTitle>중수도 시설기준 및 관리기준</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterStandard} alt="중수도 시설기준 및 관리기준 설명" />
</div>
</section>
</div>
);
}
+90 -57
View File
@@ -1,8 +1,10 @@
// apps/dm/app/(landing)/reuse/intro/law/page.tsx
// DM 물 재이용 소개(물 재이용 법) 페이지
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
// import BusinessSubNav from "@/components/BusinessSubNav";
import lawTimeline from "@/public/images/landing/intro/law_timeline.png";
import performanceLeft from "@/public/images/landing/intro/performance_left.png";
import domesticTextbox from "@/public/images/landing/intro/domestic_textbox.png";
@@ -38,93 +40,124 @@ function ImgCard({ src, alt }: ImgCardProps) {
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
priority={false}
/>
</div>
);
}
type SectionShellProps = {
id: string;
title: string;
children: React.ReactNode;
className?: string;
};
function SectionShell({ id, title, children, className = "" }: SectionShellProps) {
return (
<section
id={id}
className={[
"scroll-mt-[140px]",
className,
].join(" ")}
>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function WaterReuseLawPage() {
return (
<div className="bg-white">
<div className="relative bg-white">
{/* <BusinessSubNav /> */}
<SubPageHeader
title="물 재이용 법"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 소개", href: "/reuse/intro/law" },
{ label: "물 재이용 법" },
{ label: "Home", href: "/main" },
{ label: "물 재이용 소개", href: "/intro/law" },
{ label: "물 재이용 법" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
<div className="pr-0">
<Hr className="my-7" />
{/* 1) 법제도/계획 수립 타임라인 */}
<section className="mt-6">
<SectionTitle>법제도 · 계획 및 기본계획 수립</SectionTitle>
<div className="mt-8">
<ImgCard src={lawTimeline} alt="법제도 타임라인" />
</div>
</section>
<SectionShell
id="law-plan"
title="법률 제·개정 및 기본계획 수립"
className="mt-6"
>
<ImgCard src={lawTimeline} alt="법률 제·개정 및 기본계획 수립" />
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 2) 제2차 계획 대비 실적 현황 (2컬럼) */}
<section>
<SectionTitle>제 2차 계획 대비 실적 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={performanceLeft} alt="실적 현황 요약" />
</div>
</section>
<SectionShell
id="plan-performance"
title="제2차 계획 대비 실적 현황"
>
<ImgCard src={performanceLeft} alt="제2차 계획 대비 실적 현황" />
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 3) 국내 관련 현황 */}
<section>
<SectionTitle>국내 관련 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={domesticTextbox} alt="국내 관련 현황 설명" />
</div>
</section>
<SectionShell
id="domestic-change"
title="국내 여건 변화"
>
<ImgCard src={domesticTextbox} alt="국내 여건 변화" />
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 4) 정책 변천 추이 */}
<section>
<SectionTitle>물 재이용 정책 변천 추이</SectionTitle>
<div className="mt-8">
<ImgCard src={policyTimeline} alt="정책 변천 타임라인" />
</div>
</section>
<SectionShell
id="policy-history"
title="물 재이용 정책 변화 추이"
>
<ImgCard src={policyTimeline} alt="물 재이용 정책 변화 추이" />
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 5) 타 법령과의 관계도 */}
<section>
<SectionTitle>타법령과의 관계도</SectionTitle>
<div className="mt-8">
<ImgCard src={relNote} alt="관계도 설명" />
</div>
</section>
<SectionShell
id="law-relation"
title="타법령과의 관계 검토"
>
<ImgCard src={relNote} alt="타법령과의 관계 검토" />
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 6) 국내 연계 현황 */}
<section className="pb-[200px]">
<SectionTitle>국내 연계 현황</SectionTitle>
<div className="mt-8">
<SectionShell
id="domestic-linkage"
title="국내 연계 현황"
className="pb-[250px]"
>
<div className="mx-[20px] mb-8">
<p className="text-[24px] font-semibold mb-2">주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요</p>
<p className="text-[#4F4F4F]">국가 상위계획의 정책 방향이 기후위기 대응 등 수자원의 공급 안전성을 위한 물 재이용 활성화로 정책 실효성 제고 필요<br />
• 물관리 계획(국가수도기본계획, 국가 및 유역하수도종합계획, 하천유역수자원관리계획 등)과의 연계성 확보 → 통합적인 관점에서 물이용 체계 구축 필요
<p className="mb-2 text-[24px] font-semibold">
주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요
</p>
<p className="text-[#4F4F4F]">
국가 상위계획의 정책 방향이 기후위기 대응 등 수자원의 공급 안전성을 위한
물 재이용 활성화로 정책 실효성 제고 필요
<br />
• 물관리 계획(국가수도기본계획, 국가 및 유역하수도종합계획,
하천유역수자원관리계획 등)과의 연계성 확보 → 통합적인 관점에서
물이용 체계 구축 필요
</p>
</div>
<ImgCard src={linkage} alt="국내 연계 현황" />
</div>
</section>
</SectionShell>
</div>
</div>
);
}
}
+13 -6
View File
@@ -1,13 +1,20 @@
"use client";
import type { ReactNode } from "react";
import Lnb from "@/components/Lnb";
export default function IntroLayout({ children }: { children: ReactNode }) {
export default function ExampleLayout({ children }: { children: ReactNode }) {
return (
<div className="flex gap-0">
<div className="relative">
<Lnb />
<main className="flex-1 px-[100px] pt-[80px]">
{children}
</main>
<div className="flex max-w-[1400px] gap-[56px] py-[50px]">
<main className="min-w-0 flex-1">
{children}
</main>
{/* 우측 메뉴 영역 */}
{/* <BusinessSubNav /> */}
</div>
</div>
);
}
}
+86
View File
@@ -0,0 +1,86 @@
// DM 물 재이용 소개 페이지
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import SubPageHeader from "@/components/SubPageHeader";
import LawIntroSection from "./_components/LawIntroSection";
import RainIntroSection from "./_components/RainIntroSection";
import ReuseWaterIntroSection from "./_components/ReuseWaterIntroSection";
import TreatedIntroSection from "./_components/TreatedIntroSection";
const menus: BusinessSubNavMenu[] = [
{
key: "intro-law",
label: "물 재이용 법",
sections: [
{ id: "law-plan", label: "법률 제·개정 및 기본계획 수립" },
{ id: "plan-performance", label: "제2차 계획 대비 실적 현황" },
{ id: "domestic-change", label: "우리나라의 수자원 현황 및 물이용 여건" },
{ id: "policy-history", label: "물 재이용 정책 변화 추이" },
{ id: "law-relation", label: "타법령과의 관계 검토" },
{ id: "domestic-linkage", label: "주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요" },
],
},
{
key: "intro-rain",
label: "빗물 이용시설",
defaultOpen: false,
sections: [
{ id: "rain-intro", label: "빗물 재이용 소개" },
{
id: "rain-mandatory",
label: "빗물이용시설 설치 관리 법적 의무 대상",
},
{ id: "rain-design", label: "빗물이용시설 설계 방안" },
{
id: "rain-standards",
label: "빗물이용시설 시설기준 및 관리기준",
},
],
},
{
key: "intro-greywater",
label: "중수도",
defaultOpen: false,
sections: [
{ id: "greywater-intro", label: "중수 재이용 소개" },
{ id: "greywater-mandatory", label: "중수도 설치 의무 대상" },
{ id: "greywater-standard", label: "중수도 시설기준 및 관리기준" },
],
},
{
key: "intro-treated",
label: "하수처리수",
defaultOpen: false,
sections: [
{ id: "treatedwater-intro", label: "하수처리수 소개" },
{
id: "treatedwater-mandatory",
label: "하수처리수 재이용시설 의무 대상",
},
],
},
];
export default function IntroPage() {
return (
<div className="relative">
{/* 상단 헤더 */}
<SubPageHeader
title="물 재이용 소개"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 소개", href: "/intro" },
]}
/>
{/* 우측 앵커 네비 */}
<BusinessSubNav menus={menus} offsetTop={120} />
{/* 본문 */}
<div className="max-w-[1320px] pb-24">
<LawIntroSection />
<RainIntroSection />
<ReuseWaterIntroSection />
<TreatedIntroSection />
</div>
</div>
);
}
+75 -38
View File
@@ -1,8 +1,10 @@
// apps/dm/app/(landing)/reuse/intro/law/page.tsx
// DM 물 재이용 소개(빗물 이용시설) 페이지
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
import BusinessSubNav from "@/components/BusinessSubNav";
import rainreuseIntro from "@/public/images/landing/intro/rainreuse_intro.png";
import rainreuseMandatory from "@/public/images/landing/intro/rainreuse_mandatory.png";
import rainwaterDesign from "@/public/images/landing/intro/rainwater_design.png";
@@ -36,67 +38,102 @@ function ImgCard({ src, alt }: ImgCardProps) {
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
type SectionShellProps = {
id: string;
title: string;
children: React.ReactNode;
className?: string;
};
function SectionShell({
id,
title,
children,
className = "",
}: SectionShellProps) {
return (
<section
id={id}
className={["scroll-mt-[140px]", className].join(" ")}
>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function RainwaterReusePage() {
return (
<div className="bg-white">
<div className="relative bg-white">
<BusinessSubNav />
<SubPageHeader
title="빗물 이용시설"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 소개", href: "/reuse/intro/law" },
{ label: "빗물 이용시설" },
{ label: "Home", href: "/main" },
{ label: "물 재이용 소개", href: "/intro/law" },
{ label: "빗물 이용시설" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
<div className="pr-0">
<Hr className="my-7" />
{/* 1) 빗물 재이용 소개 */}
<section className="mt-6">
<SectionTitle>빗물 재이용 소개</SectionTitle>
<div className="mt-8">
<SectionShell
id="rain-intro"
title="빗물 재이용 소개"
className="mt-6"
>
<ImgCard src={rainreuseIntro} alt="빗물 재이용 소개" />
</div>
</section>
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 2) 빗물이용시설 설치 관리 법적 의무 대상 */}
<section className="mt-6">
<SectionTitle>빗물이용시설 설치 관리 법적 의무 대상</SectionTitle>
<div className="mt-8">
<ImgCard src={rainreuseMandatory} alt="법적 의무 대상 설명" />
</div>
</section>
<SectionShell
id="rain-mandatory"
title="빗물이용시설 설치 관리 법적 의무 대상"
>
<ImgCard
src={rainreuseMandatory}
alt="빗물이용시설 설치 관리 법적 의무 대상"
/>
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 3) 빗물이용시설 설계 방안 */}
<section className="mt-6">
<SectionTitle>빗물이용시설 설계 방안</SectionTitle>
<div className="mt-8">
<ImgCard src={rainwaterDesign} alt="빗물이용시설 설계 방안 설명" />
</div>
</section>
<SectionShell
id="rain-design"
title="빗물이용시설 설계 방안"
>
<ImgCard
src={rainwaterDesign}
alt="빗물이용시설 설계 방안"
/>
</SectionShell>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 4) 빗물이용시설 시설기준 및 관리기준 */}
<section className="pb-[200px]">
<SectionTitle>빗물이용시설 시설기준 및 관리기준</SectionTitle>
<div className="mt-8">
<ImgCard src={rainwaterStandards} alt="빗물이용시설 시설기준 및 관리기준 설명" />
</div>
</section>
<SectionShell
id="rain-standards"
title="빗물이용시설 시설기준 및 관리기준"
className="pb-[200px]"
>
<ImgCard
src={rainwaterStandards}
alt="빗물이용시설 시설기준 및 관리기준"
/>
</SectionShell>
</div>
</div>
);
}
}
@@ -0,0 +1,105 @@
// DM 물 재이용 소개(중수도) 페이지
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
import BusinessSubNav from "@/components/BusinessSubNav";
import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png";
import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png";
import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[140px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function ReusePage() {
return (
<div className="relative bg-white">
<BusinessSubNav />
<SubPageHeader
title="중수도"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 소개", href: "/intro/law" },
{ label: "중수도" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<div className="pr-0">
<Hr className="my-7" />
<SectionShell
id="reuse-intro"
title="중수 재이용 소개"
className="mt-6"
>
<ImgCard src={greywaterIntro} alt="중수 재이용 소개" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="reuse-mandatory"
title="중수도 설치 의무 대상"
>
<ImgCard src={greywaterMandatory} alt="중수도 설치 의무 대상" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="reuse-standard"
title="중수도 시설기준 및 관리기준"
className="pb-[200px]"
>
<ImgCard src={greywaterStandard} alt="중수도 시설기준 및 관리기준" />
</SectionShell>
</div>
</div>
);
}
@@ -1,80 +0,0 @@
// apps/dm/app/(landing)/reuse/intro/treated/page.tsx
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png";
import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type ImgCardProps = {
src: StaticImageData;
alt: string;
};
function ImgCard({ src, alt }: ImgCardProps) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
export default function TreatedWaterPage() {
return (
<div className="bg-white">
<SubPageHeader
title="하수처리수"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 소개", href: "/reuse/intro/law" },
{ label: "하수처리수" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
{/* 1) 하수처리수 */}
<section className="mt-6">
<SectionTitle>하수처리수</SectionTitle>
<div className="mt-8">
<ImgCard src={treatedwaterIntro} alt="하수처리수 설명" />
</div>
</section>
<Hr className="my-11" />
{/* 2) 하수처리수 재이용시설 의무 대상 */}
<section className="pb-[200px]">
<SectionTitle>하수처리수 재이용시설 의무 대상</SectionTitle>
<div className="mt-8">
<ImgCard src={treatedwaterMandatory} alt="하수처리수 재이용시설 의무 대상 설명" />
</div>
</section>
</div>
);
}
@@ -0,0 +1,98 @@
// DM 물 재이용 소개(하수처리수) 페이지
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
import BusinessSubNav from "@/components/BusinessSubNav";
import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png";
import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function ImgCard({ src, alt }: { src: StaticImageData; alt: string }) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="mx-auto block max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
function SectionShell({
id,
title,
children,
className = "",
}: {
id: string;
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<section id={id} className={`scroll-mt-[140px] ${className}`}>
<SectionTitle>{title}</SectionTitle>
<div className="mt-8">{children}</div>
</section>
);
}
export default function TreatedWaterPage() {
return (
<div className="relative bg-white">
<BusinessSubNav />
<SubPageHeader
title="하수처리수"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 소개", href: "/intro/law" },
{ label: "하수처리수" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<div className="pr-0">
<Hr className="my-7" />
<SectionShell
id="treated-intro"
title="하수처리수 개요"
className="mt-6"
>
<ImgCard src={treatedwaterIntro} alt="하수처리수 개요" />
</SectionShell>
<Hr className="my-11" />
<SectionShell
id="treated-mandatory"
title="하수처리수 재이용시설 의무 대상"
className="pb-[300px]"
>
<ImgCard
src={treatedwaterMandatory}
alt="하수처리수 재이용시설 의무 대상"
/>
</SectionShell>
</div>
</div>
);
}
+10 -10
View File
@@ -1,8 +1,3 @@
// app/(with-layout)/layout.tsx
/**
* 메인을 제외한 메뉴안 페이지의 layout
*/
import "../reuse.module.css";
import { StoreProvider } from "../store";
import TopNav from "@/components/TopNav";
@@ -10,10 +5,15 @@ import TopNav from "@/components/TopNav";
export default function WithLayout({ children }: { children: React.ReactNode }) {
return (
<StoreProvider>
<div className="pt-[83px]">{/* TopNav 높이만큼 */}
<TopNav />
<main className="ml-[260px]">{children}</main>{/* Lnb 너비만큼 */}
</div>
<TopNav />
<div className="pt-[83px] flex">
{/* 본문 */}
<main className="ml-[260px] w-full">
{children}
</main>
</div>
</StoreProvider>
);
}
}
@@ -0,0 +1,484 @@
/**
* 중수도
*/
"use client";
import React from "react";
import { useEffect } from "react";
// import SubPageHeader from "@/components/SubPageHeader";
import Image from "next/image";
import { useDmStore } from "@/app/store";
import { formatUsage } from "@/app/common/transValueFormat";
import type {
DmStatusLawYearsType,
DmStatusBildTpYearsType,
DmStatusUsgYearsType,
TableHeaderType,
} from "@/app/store/dm-status/type";
import { Col2Name } from "@/app/store/dm-status/type";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function GreywaterIntroSummary() {
const items = [
{
icon: "/images/landing/icon_recycle.png",
title: "지속적인 인프라 확산",
desc: (
<>
전국 중수도 설치 시설은 2018년 대비 약 45% 증가하여,
<br />
현재 1,000여 개소에서 물 재이용이 활발히 이뤄지고 있습니다.)
</>
),
},
{
icon: "/images/landing/icon_factory.png",
title: "조경용수의 핵심 자원",
desc: (
<>
전체 중수도 이용량의 약 96%가 공업용수로 활용되어,
<br />
산업 현장에서의 상수도 의존도를 낮추는 데 크게 기여하고 있습니다.
</>
),
},
{
icon: "/images/landing/icon_faucet.png",
title: "민간·공공의 협력",
desc: (
<>
공업용 외에도 청소·화장실용수로 꾸준히 사용되며
<br />
우리 생활 곳곳에서 물 자원 낭비를 막고 있습니다.
</>
),
},
];
return (
<div className="mb-[70px] px-[160px]">
<div className="grid grid-cols-[1fr_400px] items-center gap-[60px]">
<div>
<h3 className="text-[40px] font-extrabold leading-[1.35] tracking-[-0.06em] text-neutral-900">
건물 안에서 흐르는 또 하나의 자원,
<br />
<span className="inline-block bg-gradient-to-b from-[#6DBB4F] to-[#01ACE5] bg-clip-text text-transparent">
중수도
</span>
가 물 절약을 실천합니다.
</h3>
<p className="mt-5 text-[16px] leading-[1.8] text-neutral-600">
중수도는 개별 건축물이나 단지 내에서 사용한 물을 다시 처리하여
<br />
청소, 조경, 공업용수로 사용하고 있습니다.
</p>
<div className="mt-10 space-y-8">
{items.map((item) => (
<div key={item.title} className="flex gap-3 items-center">
<div className="mt-1 flex h-12 w-12 shrink-0 items-center justify-center">
<Image src={item.icon} alt="" width={48} height={48} />
</div>
<div>
<strong className="block text-[18px] font-extrabold text-neutral-900">
{item.title}
</strong>
<p className="mt-2 text-[16px] leading-[1.7] tracking-tighter text-neutral-600">
{item.desc}
</p>
</div>
</div>
))}
</div>
</div>
<div className="flex items-center justify-center">
<Image
src="/images/landing/greywater_status_img.png"
alt="중수도 재이용 개념도"
width={400}
height={343}
className="h-auto w-full max-w-[400px]"
priority
/>
</div>
</div>
</div>
);
}
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
const headerColors6 = [
"bg-[#01ACE5]",
"bg-[#17AFC7]",
"bg-[#2CB2A9]",
"bg-[#42B58B]",
"bg-[#57B86D]",
"bg-[#81C784]",
];
export default function GreywaterStatusSection() {
const getRswtrByLawYears = useDmStore((state) => state.getRswtrByLawYears);
const getRswtrByBildTpYears = useDmStore((state) => state.getRswtrByBildTpYears);
const getRswtrByUsgYears = useDmStore((state) => state.getRswtrByUsgYears);
const rswtrYears = useDmStore((state) => state.rswtr.rswtrYears);
const selectYear = rswtrYears.slice(-1)[0];
const rswtrByLawYearsHeader = useDmStore((state) => state.rswtr.rswtrByLawYearsHeader);
const rswtrByLawYears = useDmStore((state) => state.rswtr.rswtrByLawYears); // 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)
const rswtrByLawYearsFactory = useDmStore((state) => state.rswtr.rswtrByLawYearsFactory); // 공장 및 발전시설 내 중수도 설치 현황
const rswtrByBildTpYearsHeader = useDmStore((state) => state.rswtr.rswtrByBildTpYearsHeader);
const rswtrByBildTpYears = useDmStore((state) => state.rswtr.rswtrByBildTpYears);
const rswtrByUsgYearsHeader = useDmStore((state) => state.rswtr.rswtrByUsgYearsHeader);
const rswtrByUsgYears = useDmStore((state) => state.rswtr.rswtrByUsgYears);
// 출처 텍스트
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 하수도정보시스템 {selectYear} 하수도 통계
</p>
);
}
useEffect(() => {
void getRswtrByLawYears('nofactory'); // rswtrByLawYears: 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)
void getRswtrByLawYears('factory'); // rswtrByLawYearsFactory: 공장 및 발전시설 내 중수도 설치 현황
void getRswtrByBildTpYears();
void getRswtrByUsgYears();
}, [getRswtrByLawYears, getRswtrByBildTpYears, getRswtrByUsgYears]);
const renderPartHeaders = (headers: string[], headerColors: string[]) => {
return headers.map((header, index) => (
<th
key={header}
className={`${tableStyles.th} ${headerColors[index] ?? "bg-[#81C784]"} text-white`}
>
{header}
</th>
));
};
const renderCells = (
rowIdx: number,
row: DmStatusLawYearsType,
data: DmStatusLawYearsType[],
headers: TableHeaderType[]
) => {
const rowData = row as Record<string, string | number>;
const currentCol1 = String(rowData["COL1"] ?? "");
// 이전 값
const prevCol1 = rowIdx > 0 ? data[rowIdx - 1]?.COL1 : null;
// 현재 그룹의 rowspan 계산
const getRowSpan = () => {
let count = 0;
for (let i = rowIdx; i < data.length; i++) {
if (data[i]?.COL1 === currentCol1) count++;
else break;
}
return count;
};
return headers.map((header, index) => {
const value = rowData[header.id];
// COL1 처리 (rowspan 핵심)
if (header.id === "COL1") {
// 이전과 같으면 렌더링 안함
if (currentCol1 === prevCol1) return null;
return (
<td
key={`${rowIdx}-${header.id}`}
rowSpan={getRowSpan()}
className={`${tableStyles.td} !font-bold !text-neutral-900`}
>
{value}
</td>
);
} else if (header.id === "COL2") {
return (
<td
key={`${rowIdx}-${header.id}`}
className={`${tableStyles.td} !font-bold !text-neutral-900`}
dangerouslySetInnerHTML={{ __html: Col2Name[value as string] ?? "" }}
/>
);
}
return (
<td
key={`${rowIdx}-${header.id}`}
className={`${tableStyles.td} ${[1, 3, 5].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{String(rowData["COL2"]).indexOf("CNT") > -1
? Number(value).toLocaleString("ko-KR")
: formatUsage(value as string | number, 10000, 1) }
</td>
);
});
};
const renderBuildingUsageCells = (
row: DmStatusBildTpYearsType|DmStatusUsgYearsType,
headers: TableHeaderType[]
) => {
const rowData = row as Record<string, string | number>;
const firstHeaderId = headers[0]?.id;
const rowKey = firstHeaderId ? String(rowData[firstHeaderId] ?? "") : "";
return headers.map((header, index) => {
const value = rowData[header.id];
return (
<td
key={`${rowKey}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 3, 5].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{index === 0 ? value : formatUsage(value as string | number, 10000, 1)}
</td>
);
});
};
const title = "중수도(현황)";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-[70px] pb-[120px]">
{/* 1) 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외) */}
<section>
{/* 인트로 영역 */}
<GreywaterIntroSummary />
<div id="status-greywater-overall" className="mt-10 scroll-mt-[120px]">
<SectionTitle>전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
{renderPartHeaders(rswtrYears, headerColors6)}
</tr>
</thead>
<tbody>
{rswtrByLawYears.map((row, idx, arr) => (
<tr key={idx}>
{renderCells(idx, row, arr, rswtrByLawYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</div>
</section>
<Hr className="my-11" />
{/* 2) 공장 및 발전시설 내 중수도 설치 현황 */}
<section
id="status-greywater-factory"
className="scroll-mt-[120px]"
>
<SectionTitle>공장 및 발전시설 내 중수도 설치 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
{renderPartHeaders(rswtrYears, headerColors6)}
</tr>
</thead>
<tbody>
{rswtrByLawYearsFactory.map((row, idx, arr) => (
<tr key={idx}>
{renderCells(idx, row, arr, rswtrByLawYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 3) 중수도 건축물별 이용량 현황 */}
<section
id="status-greywater-building"
className="scroll-mt-[120px]"
>
<SectionTitle>중수도 건축물별 이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
이용량 (만m³/년)
</th>
</tr>
<tr>
{renderPartHeaders(rswtrYears, headerColors6)}
</tr>
</thead>
<tbody>
{rswtrByBildTpYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderBuildingUsageCells(row, rswtrByBildTpYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 4) 중수도 용도별 이용량 현황 */}
<section
id="status-greywater-usage"
className="scroll-mt-[120px]"
>
<SectionTitle>중수도 용도별 이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
이용량 (만m³/년)
</th>
</tr>
<tr>
{renderPartHeaders(rswtrYears, headerColors6)}
</tr>
</thead>
<tbody>
{rswtrByUsgYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderBuildingUsageCells(row, rswtrByUsgYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
@@ -0,0 +1,353 @@
"use client";
import { useEffect, useMemo } from "react";
// import SubPageHeader from "@/components/SubPageHeader";
import Image from "next/image";
import { useDmStore } from "@/app/store";
import { formatUsage } from "@/app/common/transValueFormat";
import type {
DmStatusBildTpYearsType,
DmStatusUsgBildTpType,
DmStatusUsgYearsType,
TableHeaderType,
} from "@/app/store/dm-status/type";
// 공통 섹션 타이틀 컴포넌트
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
/* 인트로 영역 */
function RainIntroSummary() {
const items = [
{
icon: "/images/landing/icon_chart.png",
title: "지속적인 이용량 증가",
desc: "2018년 약 797만m³이었던 빗물 이용량은 5년 사이 약 75% 증가하여 수자원 절약에 기여하고 있습니다.",
},
{
icon: "/images/landing/icon_hand.png",
title: "조경용수의 핵심 자원",
desc: "재이용되는 빗물의 약 97%가 조경용수로 활용되고 있으며 이는 공원, 아파트 단지 등의 녹지 관리에 중요한 역할을 합니다.",
},
{
icon: "/images/landing/icon_building.png",
title: "민간·공공의 협력",
desc: "골프장과 같은 대규모 스포츠 시설은 물론, 최근에는 공동주택·아파트 등 내 이용량이 급증하며 일상생활 속 빗물 활용이 확산되는 추세입니다.",
},
];
return (
<div className="mb-[70px] px-[160px]">
<div className="grid grid-cols-[1fr_400px] items-center gap-[60px]">
{/* 좌측 */}
<div>
<h3 className="text-[40px] font-extrabold leading-[1.35] tracking-[-0.04em] text-neutral-900">
기후위기 시대의 수자원 확보,
<br />
<span className="inline-block bg-gradient-to-b from-[#6DBB4F] to-[#01ACE5] bg-clip-text text-transparent">
빗물 재이용
</span>
이 앞장섭니다.
</h3>
<p className="mt-5 text-[16px] leading-[1.8] tracking-[-0.04em] text-neutral-600">
2023년 기준 전국 3,500여 개 시설에서
<br />
연간 약 1,394만m³의 빗물을 소중한 자원으로 다시 활용하고 있습니다.
</p>
<div className="mt-10 space-y-8">
{items.map((item) => (
<div key={item.title} className="flex gap-3 items-center">
<div className="flex h-12 w-12 shrink-0 items-center justify-center">
<Image
src={item.icon}
alt=""
width={48}
height={48}
className="object-contain"
/>
</div>
<div>
<strong className="block text-[18px] font-extrabold text-neutral-900">
{item.title}
</strong>
<p className="mt-2 text-[16px] leading-[1.7] text-neutral-600 tracking-tighter">
{item.desc}
</p>
</div>
</div>
))}
</div>
</div>
{/* 우측 이미지 */}
<div className="flex items-center justify-center">
<Image
src="/images/landing/rain_status_img.png"
alt="빗물 재이용 개념도"
width={400}
height={343}
className="h-auto w-full max-w-[400px] object-contain"
priority
/>
</div>
</div>
</div>
);
}
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
const headerColors6 = [
"bg-[#01ACE5]",
"bg-[#17AFC7]",
"bg-[#2CB2A9]",
"bg-[#42B58B]",
"bg-[#57B86D]",
"bg-[#81C784]",
];
const headerColors8 = [
"bg-[#01ACE5]",
"bg-[#10AED0]",
"bg-[#20B0BA]",
"bg-[#2FB2A5]",
"bg-[#3FB58F]",
"bg-[#4EB77A]",
"bg-[#5EB964]",
"bg-[#6DBB4F]",
];
export default function RainStatusSection() {
const getRainyByBildTpYears = useDmStore((state) => state.getRainyByBildTpYears);
const getRainyByUsgBildTp = useDmStore((state) => state.getRainyByUsgBildTp);
const getRainyByUsgYears = useDmStore((state) => state.getRainyByUsgYears);
const rainyYears = useDmStore((state) => state.rainy.rainyYears);
const selectYear = rainyYears.slice(-1)[0];
const rainyByBildTpYearsHeader = useDmStore((state) => state.rainy.rainyByBildTpYearsHeader);
const rainyByBildTpYears = useDmStore((state) => state.rainy.rainyByBildTpYears);
const rainyByUsgBildTpHeader = useDmStore((state) => state.rainy.rainyByUsgBildTpHeader);
const rainyByUsgBildTp = useDmStore((state) => state.rainy.rainyByUsgBildTp);
const rainyByUsgYearsHeader = useDmStore((state) => state.rainy.rainyByUsgYearsHeader);
const rainyByUsgYears = useDmStore((state) => state.rainy.rainyByUsgYears);
const partBildTpHeader = useMemo(
() => rainyByUsgBildTpHeader.slice(3).map((header) => header.title),
[rainyByUsgBildTpHeader]
);
// 출처 텍스트
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 하수도정보시스템 {selectYear} 하수도 통계
</p>
);
}
useEffect(() => {
void getRainyByBildTpYears();
void getRainyByUsgBildTp();
void getRainyByUsgYears();
}, [getRainyByBildTpYears, getRainyByUsgBildTp, getRainyByUsgYears]);
const renderPartHeaders = (headers: string[], headerColors: string[]) => {
return headers.map((header, index) => (
<th
key={header}
className={`${tableStyles.th} ${headerColors[index] ?? "bg-[#81C784]"} text-white`}
style={{ width: "140px" }}
>
{header}
</th>
));
};
const renderBuildingUsageCells = (
row: DmStatusBildTpYearsType | DmStatusUsgYearsType | DmStatusUsgYearsType,
headers: TableHeaderType[]
) => {
const rowData = row as Record<string, string | number>;
const firstHeaderId = headers[0]?.id;
const rowKey = firstHeaderId ? String(rowData[firstHeaderId] ?? "") : "";
return headers.map((header, index) => {
const value = rowData[header.id];
return (
<td
key={`${rowKey}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 3, 5].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{index === 0 ? value : formatUsage(value as string | number, 1000, 1)}
</td>
);
});
};
const renderRainyByUsgBildTpCells = (row: DmStatusUsgBildTpType) => {
return rainyByUsgBildTpHeader.map((header, index) => {
const value = row[header.id as keyof DmStatusUsgBildTpType];
const displayValue =
index === 0
? value
: header.id === "CNT"
? Number(value).toLocaleString("ko-KR")
: formatUsage(value as string | number, 1000, 1);
return (
<td
key={`${row.USG_NM}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 2].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{displayValue == 0.0 ? "-" : displayValue}
</td>
);
});
};
const title = "빗물이용시설(현황)";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-[70px] pb-[120px]">
{/* 1) 빗물이용시설의 건축물별 빗물이용량 현황 */}
<section>
{/* 인트로 영역 */}
<RainIntroSummary />
<div id="status-rain-building" className="scroll-mt-[120px]">
<SectionTitle>빗물이용시설의 건축물별 빗물이용량 현황</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>
구분
</th>
<th colSpan={rainyYears.length || 6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>
빗물이용량 (천m³/년)
</th>
</tr>
<tr>
{renderPartHeaders(rainyYears, headerColors6)}
</tr>
</thead>
<tbody>
{rainyByBildTpYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderBuildingUsageCells(row, rainyByBildTpYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</div>
</section>
<Hr className="my-11" />
{/* 2) 빗물이용시설의 용도별 분포 (xxxx년 기준) */}
<section
id="status-rain-purpose-distribution"
className="scroll-mt-[120px]"
>
<SectionTitle>빗물이용시설의 용도별 분포 ({selectYear}년 기준)</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>용도</th>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "84px" }}>개소수<br />(개소)</th>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "84px" }}>저류용량<br />(천m³)</th>
<th colSpan={8} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>빗물이용량 (천m³/년)</th>
</tr>
<tr>
{renderPartHeaders(partBildTpHeader, headerColors8)}
</tr>
</thead>
<tbody>
{rainyByUsgBildTp.map((row, idx) => (
<tr key={`${row.USG_NM}-${idx}`} className="bg-white">
{renderRainyByUsgBildTpCells(row)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 3) 빗물이용시설의 용도별 빗물이용량 현황 */}
<section
id="status-rain-purpose-usage"
className="scroll-mt-[120px]"
>
<SectionTitle>빗물이용시설의 용도별 빗물이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>용도</th>
<th colSpan={6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>빗물이용량 (천m³/년)</th>
</tr>
<tr>
{renderPartHeaders(rainyYears, headerColors6)}
</tr>
</thead>
<tbody>
{rainyByUsgYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderBuildingUsageCells(row, rainyByUsgYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
@@ -0,0 +1,591 @@
/**
* 하수처리수
*/
"use client";
import React from "react";
import Image from "next/image";
import { useEffect } from "react";
import { useDmStore } from "@/app/store";
import { formatUsage } from "@/app/common/transValueFormat";
import type {
DmStatusLawYearsType,
DmStatusBildTpYearsType,
DmStatusUsgYearsType,
DmStatusSidoUsgType,
TableHeaderType,
} from "@/app/store/dm-status/type";
import { Col2Name } from "@/app/store/dm-status/type";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function SewageIntroSummary() {
const items = [
{
icon: "/images/landing/icon_cycle_water.png",
title: "안정적인 재이용률 유지",
desc: (
<>
연간 약 1,136백만m³의 하수처리수가 재이용되고 있으며,
<br/>
약 15%에 달하는 높은 재이용률을 통해
<br/>
수자원 선순환 체계를 구축하고 있습니다.
</>
),
},
{
icon: "/images/landing/icon_water_flow.png",
title: "메마른 하천을 살리는 물",
desc: (
<>
재이용되는 물의 가장 많은 부분이 하천유지용수로 사용되어
<br/>
도심 속 하천의 생태계를 보호하고 쾌적한 수변 환경을 조성합니다.
</>
),
},
{
icon: "/images/landing/icon_location.png",
title: "지역별 균형 있는 활용",
desc: (
<>
전국적으로 고르게 시설이 확충되고 있으며, 특히 경기 지역을 중심으로
<br/>
대규모 공업용수 공급망을 구축하여
<br/>
지역 경제와 환경 보호를 동시에 실현하고 있습니다.
</>
),
},
];
return (
<div className="mb-[70px] px-[160px]">
<div className="grid grid-cols-[1fr_400px] items-center gap-0">
{/* 좌측 */}
<div>
<h3 className="text-[40px] font-extrabold leading-[1.35] tracking-[-0.14em] text-neutral-900">
버려지는 하수에서 찾는 새로운 가치,
<br />
<span className="inline-block bg-gradient-to-b from-[#6DBB4F] to-[#01ACE5] bg-clip-text text-transparent">
하수처리수 재이용
</span>
이 도시를 숨쉬게 합니다.
</h3>
<p className="mt-5 text-[16px] leading-[1.8] text-neutral-600">
하수처리장에서 정화된 물은 하천의 수량을 조절하고
<br />
산업 현장에 용수를 공급하는 소중한 자원입니다.
</p>
<div className="mt-10 space-y-8">
{items.map((item) => (
<div key={item.title} className="flex gap-4 items-center">
<div className="mt-1 flex h-12 w-12 shrink-0 items-center justify-center">
<Image
src={item.icon}
alt=""
width={48}
height={48}
className="object-contain"
/>
</div>
<div>
<strong className="block text-[18px] font-extrabold text-neutral-900">
{item.title}
</strong>
<p className="mt-2 text-[16px] leading-[1.7] text-neutral-600">
{item.desc}
</p>
</div>
</div>
))}
</div>
</div>
{/* 우측 이미지 */}
<div className="flex items-center justify-center">
<Image
src="/images/landing/sewage_status_img.png"
alt="하수처리수 재이용 개념도"
width={420}
height={360}
className="h-auto w-full max-w-[420px] object-contain"
priority
/>
</div>
</div>
</div>
);
}
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
const headerColors6 = [
"bg-[#01ACE5]",
"bg-[#17AFC7]",
"bg-[#2CB2A9]",
"bg-[#42B58B]",
"bg-[#57B86D]",
"bg-[#81C784]",
];
export default function SewageStatusSection() {
const getPbnstByLawYears = useDmStore((state) => state.getPbnstByLawYears);
const getPbnstByUsgYears = useDmStore((state) => state.getPbnstByUsgYears);
const getPbnstByCpcYears = useDmStore((state) => state.getPbnstByCpcYears);
const getPbnstBySido = useDmStore((state) => state.getPbnstBySido);
const pbnstYears = useDmStore((state) => state.pbnst.pbnstYears);
const selectYear = pbnstYears.slice(-1)[0];
const pbnstByLawYearsHeader = useDmStore((state) => state.pbnst.pbnstByLawYearsHeader);
const pbnstByLawYears = useDmStore((state) => state.pbnst.pbnstByLawYears);
const pbnstByUsgYearsHeader = useDmStore((state) => state.pbnst.pbnstByUsgYearsHeader);
const pbnstByUsgYears = useDmStore((state) => state.pbnst.pbnstByUsgYears);
const pbnstByCpcYearsHeader = useDmStore((state) => state.pbnst.pbnstByCpcYearsHeader);
const pbnstByCpcYears = useDmStore((state) => state.pbnst.pbnstByCpcYears);
const pbnstBySidoHeader = useDmStore((state) => state.pbnst.pbnstBySidoHeader);
const pbnstBySido = useDmStore((state) => state.pbnst.pbnstBySido);
// 출처 텍스트
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 하수도정보시스템 {selectYear} 하수도 통계
</p>
);
}
useEffect(() => {
void getPbnstByLawYears();
void getPbnstByUsgYears();
void getPbnstByCpcYears();
void getPbnstBySido();
}, [getPbnstByLawYears, getPbnstByUsgYears, getPbnstByCpcYears, getPbnstBySido]);
const renderPartHeaders = (headers: string[], headerColors: string[]) => {
return headers.map((header, index) => (
<th
key={header}
className={`${tableStyles.th} ${headerColors[index] ?? "bg-[#81C784]"} text-white`}
>
{header}
</th>
));
};
const renderCells = (
rowIdx: number,
row: DmStatusLawYearsType,
data: DmStatusLawYearsType[],
headers: TableHeaderType[],
unitSize: number = 1, // 단위 변경 1000 인 경우, 100,000 -> 100 으로 변경
) => {
const rowData = row as Record<string, string | number>;
const currentCol1 = String(rowData["COL1"] ?? "");
// 이전 값
const prevCol1 = rowIdx > 0 ? data[rowIdx - 1]?.COL1 : null;
// 현재 그룹의 rowspan 계산
const getRowSpan = () => {
let count = 0;
for (let i = rowIdx; i < data.length; i++) {
if (data[i]?.COL1 === currentCol1) count++;
else break;
}
return count;
};
return headers.map((header, index) => {
const value = rowData[header.id];
// COL1 처리 (rowspan 핵심)
if (header.id === "COL1") {
// 이전과 같으면 렌더링 안함
if (currentCol1 === prevCol1) return null;
return (
<td
key={`${rowIdx}-${header.id}`}
rowSpan={getRowSpan()}
className={`${tableStyles.td} !font-bold !text-neutral-900`}
>
{value}
</td>
);
} else if (header.id === "COL2") {
return (
<td
key={`${rowIdx}-${header.id}`}
className={`${tableStyles.td} !font-bold !text-neutral-900`}
dangerouslySetInnerHTML={{ __html: Col2Name[value as string] ?? "" }}
/>
);
}
return (
<td
key={`${rowIdx}-${header.id}`}
className={`${tableStyles.td} ${[0, 2, 4].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{(String(rowData["COL2"]).indexOf("CNT") > -1)
? Number(value).toLocaleString("ko-KR")
: ((String(rowData["COL2"]).indexOf("REUSERT") > -1)
? formatUsage(value as string | number, 1, 1)
: formatUsage(value as string | number, unitSize, 1))
}
</td>
);
});
};
const renderPbnstUsgCells = (
rowIdx: number,
row: DmStatusBildTpYearsType | DmStatusUsgYearsType,
headers: TableHeaderType[]
) => {
const rowData = row as Record<string, string | number>;
const isLastInGroup = rowIdx === 8 || rowIdx === 17;
const getType = (colNm:string) => {
const last3 = colNm.slice(-3); // 끝 3자리
const prefix = last3[0]; // S 또는 D
if (prefix === 'S') {
const num = parseInt(last3.slice(1), 10); // 숫자
if (num < 8) return 'red-item';
if (num >= 8) return 'blue-item';
}
return ''; // 조건 안 맞으면 빈값 (필요시 기본값)
};
return headers.map((header, index) => {
const value = rowData[header.id];
const colNm = String(rowData["COL_NM"] ?? "");
const type = getType(colNm);
const isSubItem = type === "red-item" || type === "blue-item";
if (index === 0) {
console.log(header.id, value);
return (
<td key={`${rowIdx}-${header.id}`} className={`${tableStyles.td} relative`}>
{isSubItem ? (
<>
<div
className={`absolute left-[1px] w-[16px] bg-white z-10 top-[-2px] ${
isLastInGroup ? "bottom-[0px]" : "bottom-[-2px]"
}`}
/>
<div
className={`absolute left-[16px] w-[4px] z-20 ${
type === "red-item" ? "bg-[#FF8B8B]" : "bg-[#4ECBFF]"
} top-[-2px] ${
isLastInGroup ? "bottom-[0px]" : "bottom-[-2px]"
}`}
/>
<span className="text-[#4f4f4f] font-bold block w-full text-center relative z-30 translate-x-[8px]">
{value}
</span>
</>
) : (
<span className="font-bold text-neutral-900 block w-full text-center">
{value}
</span>
)}
</td>
);
}
return (
<td
key={`${rowIdx}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[0, 2, 4].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{formatUsage(value as string | number, 1000, 1)}
</td>
);
});
};
const renderSidoUsgCells = (
rowIdx: number,
row: DmStatusSidoUsgType,
headers: TableHeaderType[]
) => {
const rowData = row as Record<string, string | number>;
return headers.map((header, index) => {
let value = rowData[header.id];
value = (index === 0||index ===9) ? value : formatUsage(value as string | number, 1000, 1);
value = value === "0.0"? "-" : value;
return (
<td
key={`${rowIdx}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 9].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{value}
</td>
);
});
};
const title = "하수처리수 재이용시설(현황)";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
</h2>
</div>
<div className="mt-[70px] pb-[120px]">
{/* 1) 연도별 하수처리수 재이용 현황 */}
<section>
{/* 인트로 */}
<SewageIntroSummary />
<div id="status-treated-year" className="scroll-mt-[120px]">
<SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "180px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
{renderPartHeaders(pbnstYears, headerColors6)}
</tr>
</thead>
<tbody>
{pbnstByLawYears.map((row, idx, arr) => (
<tr key={idx}>
{renderCells(idx, row, arr, pbnstByLawYearsHeader, 1000)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</div>
</section>
<Hr className="my-11" />
{/* 2) 용도별 하수처리수 재이용 현황 */}
<section id="status-treated-usage" className="scroll-mt-[120px]">
<SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<div className="mb-2 text-right text-[13px] text-neutral-600">
(단위: 백만m³/일)
</div>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "180px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th className={`${tableStyles.th} ${tableStyles.headerBg}`}>
구분
</th>
{renderPartHeaders(pbnstYears, headerColors6)}
</tr>
</thead>
<tbody>
{pbnstByUsgYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderPbnstUsgCells(idx, row, pbnstByUsgYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 3) 하수처리시설 설치 및 이용 현황 */}
<section id="status-treated-facility" className="scroll-mt-[120px]">
<SectionTitle>하수처리시설 설치 및 이용 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "130px" }} />
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th
colSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
>
구분
</th>
{renderPartHeaders(pbnstYears, headerColors6)}
</tr>
</thead>
<tbody>
{pbnstByCpcYears.map((row, idx, arr) => (
<tr key={idx}>
{renderCells(idx, row, arr, pbnstByCpcYearsHeader, 10000)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
{/* 4) 시도별 하수처리수 재이용 현황 */}
<section
id="status-treated-region"
className="scroll-mt-[120px]"
>
<SectionTitle>시도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<div className="mb-2 text-right text-[13px] text-neutral-600">
(단위: 백만m³/일)
</div>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: "160px" }} />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`}> 시도 </th>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
연간<br />하수<br />처리량
</th>
<th colSpan={7} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`} >
재이용현황
</th>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
재이용률<br />(%)
</th>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
비고
</th>
</tr>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
계
</th>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
장내
</th>
<th colSpan={5} className={`${tableStyles.th} ${tableStyles.headerBg} py-1`} >
장외
</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white py-2 text-[16px]`}>
소계
</th>
<th className={`${tableStyles.th} bg-[#1FB1C2] text-white py-2 text-[13px]`}>
하천유지용수
</th>
<th className={`${tableStyles.th} bg-[#3EB59F] text-white py-2 text-[16px]`}>
공업용수
</th>
<th className={`${tableStyles.th} bg-[#5DB97C] text-white py-2 text-[16px]`}>
농업용수
</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white py-2 text-[16px]`}>
기타용수
</th>
</tr>
</thead>
<tbody>
{pbnstBySido.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderSidoUsgCells(idx, row, pbnstBySidoHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
@@ -1,13 +1,9 @@
// apps/dm/app/(landing)/reuse/cases/greywater/page.tsx
"use client";
import Image, { type StaticImageData } from "next/image";
import SubPageHeader from "@/components/SubPageHeader";
import greywaterOverall from "@/public/images/landing/cases/greywater_overall.png";
import greywaterFactory from "@/public/images/landing/cases/greywater_factory.png";
import greywaterBuilding from "@/public/images/landing/cases/greywater_building.png";
import greywaterUsage from "@/public/images/landing/cases/greywater_usage.png";
import React from "react";
// 공통 섹션 타이틀 컴포넌트
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
@@ -17,86 +13,351 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type ImgCardProps = {
src: StaticImageData;
alt: string;
};
function ImgCard({ src, alt }: ImgCardProps) {
// 출처 텍스트
function SourceText() {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 환경부(2024), 「2023 하수도 통계」
</p>
);
}
/**
* 공통 테이블 스타일
* - table-fixed: 열 넓이를 고정하고 균등 분배하기 위해 추가
* - 폰트 사이즈: 16px로 수정
*/
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
export default function GreywaterPage() {
return (
<div className="bg-white">
<div className="bg-white">
<SubPageHeader
title="중수도(현황)"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 현황", href: "/reuse/cases/rain" },
{ label: "중수도(현황)" },
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 현황", href: "/reuse/cases/rain" },
{ label: "중수도(현황)" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
<div className="pr-0">
<Hr className="my-7" />
{/* 1) 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외) */}
<section className="mt-6">
<section className="mt-10">
<SectionTitle>전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterOverall} alt="전체 중수도 설치 및 이용 현황 설명" />
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
{/* colgroup으로 열 너비를 최상단에서 완벽히 제어합니다 */}
<colgroup>
<col style={{ width: '130px' }} />
<col style={{ width: '160px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th colSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "576", "633", "682", "740", "819", "842"],
["건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", "1,494.2", "1,514.4", "1,524.9", "989.1", "1,104.3", "1,015.4"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "39.6", "40.0", "51.1", "47.1", "48.9", "50.7"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "2,224.0", "1,166.0", "4,377.3", "3,196.5", "2,752.5", "898.0"],
]
},
{
group: "법적 대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "196", "214", "253", "273", "287", "325"],
["건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", "649.7", "668.6", "720.9", "743.2", "760.4", "822.0"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "20.0", "20.5", "21.1", "21.1", "21.2", "24.7"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "614.7", "491.9", "518.1", "362.5", "349.6", "654.1"],
]
},
{
group: "법적 미대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "380", "419", "429", "467", "532", "517"],
["건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", "844.5", "845.8", "804.0", "245.8", "255.1", "282.3"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "19.6", "19.5", "30.0", "26.0", "27.7", "26.0"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "1,609.3", "674.1", "3,859.2", "2,834.0", "2,402.9", "243.9"],
]
}
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${isFirstCol ? "!font-bold !text-neutral-900" : ""} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 2) 중수공장 및 발전시설 내 중수도 설치 및 이용 현황 */}
{/* 2) 공장 및 발전시설 내 중수도 설치 및 이용 현황 */}
<section>
<SectionTitle>공장 및 발전시설 내 중수도 설치 및 이용 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterFactory} alt="공장 및 발전시설 내 중수도 설치 및 이용 현황 설명" />
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '130px' }} />
<col style={{ width: '160px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th colSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "125", "127", "139", "146", "151", "176"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "141.3", "141.0", "146.7", "189.0", "190.3", "166.5"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "33,712", "29,300", "33,110", "22,284", "21,593", "21,349"],
]
},
{
group: "법적 대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "59", "63", "78", "78", "82", "86"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "108.6", "109.7", "118.1", "159.5", "142.6", "119.4"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "24,847", "24,021", "28,055", "17,959", "12,239", "12,297"],
]
},
{
group: "법적 미대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "66", "64", "61", "68", "69", "90"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "32.7", "31.4", "28.7", "29.5", "47.7", "47.1"],
["사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", "8,864.9", "5,278.8", "5,054.7", "4,324.6", "9,353.3", "9,052.4"],
]
}
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${isFirstCol ? "!font-bold !text-neutral-900" : ""} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 3) 중수도 건축물별 이용량 현황 */}
<section className="pb-16">
<SectionTitle>중수도 건축물별 이용량 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterBuilding} alt="중수도 건축물별 이용량 현황 설명" />
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '160px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th colSpan={6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>이용량 (천m³/년)</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
["공장·발전시설", "33,712.3", "29,300.6", "33,109.9", "22,284.1", "21,593.3", "21,349.6"],
["교정시설", "1.3", "-", "0.4", "0.1", "0.1", "0.1"],
["대규모점포", "393.0", "241.5", "259.3", "211.5", "369.2", "119.2"],
["방송국·전신국", "9.7", "1.1", "12.7", "7.2", "7.4", "2.3"],
["숙박·목욕장", "269.6", "234.4", "250.3", "128.1", "125.8", "104.9"],
["업무시설", "338.2", "211.4", "268.0", "138.2", "116.4", "61.4"],
["운수·물류시설", "759.2", "34.2", "459.8", "259.8", "252.8", "410.4"],
["기타", "453.1", "443.3", "3,126.8", "2,451.5", "1,880.8", "199.7"],
["합계", "35,936.3", "30,466.6", "37,487.2", "25,480.6", "24,345.8", "22,247.6"],
].map((row, idx) => (
<tr key={idx} className={idx === 8 ? "bg-white font-bold" : "bg-white"}>
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${i === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 3, 5].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 4) 중수도 용도별 이용량 현황 */}
<section className="pb-[200px]">
<SectionTitle>중수도 용도별 이용량 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={greywaterUsage} alt="중수도 용도별 이용량 현황 설명" />
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '160px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th colSpan={6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>이용량 (천m³/년)</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
["공업용수", "33,082", "28,719", "32,312", "21,707", "20,991", "20,470"],
["세척·살수용수", "860.2", "146.5", "133.8", "100.7", "95.1", "149.9"],
["조경용수", "313.9", "326.4", "307.6", "67.3", "63.2", "65.9"],
["청소·화장실용수", "1,650.7", "1,272", "1,345.7", "1,340", "1,435.8", "1,459.7"],
["친수용수", "28.6", "0.5", "2,807.9", "2,258.1", "1,643.6", "9.1"],
["미기재", "0.2", "2.0", "580.1", "7.3", "116.2", "92.4"],
["합계", "35,935.6", "30,466.4", "37,487.1", "25,480.4", "24,344.9", "22,247"],
].map((row, idx) => (
<tr key={idx} className={idx === 6 ? "bg-white font-bold" : "bg-white"}>
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${i === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 3, 5].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</section>
</div>
</div>
);
}
}
+13 -4
View File
@@ -1,11 +1,20 @@
"use client";
import type { ReactNode } from "react";
import Lnb from "@/components/Lnb";
export default function CasesLayout({ children }: { children: ReactNode }) {
export default function ExampleLayout({ children }: { children: ReactNode }) {
return (
<div className="flex gap-0">
<div className="relative">
<Lnb />
<main className="flex-1 px-[100px] pt-[80px]">{children}</main>
<div className="flex max-w-[1400px] gap-[56px] py-[50px]">
<main className="min-w-0 flex-1">
{children}
</main>
{/* 우측 메뉴 영역 */}
{/* <BusinessSubNav /> */}
</div>
</div>
);
}
}
+96
View File
@@ -0,0 +1,96 @@
// DM 물 재이용 현황 페이지
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import SubPageHeader from "@/components/SubPageHeader";
import RainStatusSection from "./_components/RainStatusSection";
import GreywaterStatusSection from "./_components/GreywaterStatusSection";
import SewageStatusSection from "./_components/SewageStatusSection";
const menus: BusinessSubNavMenu[] = [
{
key: "status-rain",
label: "빗물이용시설(현황)",
sections: [
{
id: "status-rain-building",
label: "빗물이용시설의 건축물별 빗물이용량 현황",
},
{
id: "status-rain-purpose-distribution",
label: "빗물이용시설의 용도별 분포",
},
{
id: "status-rain-purpose-usage",
label: "빗물이용시설의 용도별 빗물이용량 현황",
},
],
},
{
key: "status-greywater",
label: "중수도(현황)",
defaultOpen: false,
sections: [
{
id: "status-greywater-overall",
label: "전체 중수도 설치 및 이용 현황",
},
{
id: "status-greywater-factory",
label: "공장 및 발전시설 내 중수도 설치 현황",
},
{
id: "status-greywater-building",
label: "중수도 건축물별 이용량 현황",
},
{
id: "status-greywater-usage",
label: "중수도 용도별 이용량 현황",
},
],
},
{
key: "status-sewage",
label: "하수처리수 재이용시설(현황)",
defaultOpen: false,
sections: [
{
id: "status-treated-year",
label: "연도별 하수처리수 재이용 현황",
},
{
id: "status-treated-usage",
label: "연도별 하수처리수 재이용 현황",
},
{
id: "status-treated-facility",
label: "하수처리시설 설치 및 이용 현황",
},
{
id: "status-treated-region",
label: "시도별 하수처리수 재이용 현황",
},
],
},
];
export default function StatusPage() {
return (
<div className="relative">
{/* 상단 헤더 */}
<SubPageHeader
title="물 재이용 현황"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "물 재이용 현황", href: "/status" },
]}
/>
{/* 우측 앵커 네비 */}
<BusinessSubNav menus={menus} offsetTop={120} />
<div className="max-w-[1320px] pb-24">
<RainStatusSection />
<GreywaterStatusSection />
<SewageStatusSection />
</div>
</div>
);
}
+223 -54
View File
@@ -1,12 +1,17 @@
// apps/dm/app/(landing)/reuse/cases/rain/page.tsx
"use client";
import Image, { type StaticImageData } from "next/image";
import { useEffect, useMemo } from "react";
import SubPageHeader from "@/components/SubPageHeader";
import rainwaterBuilding from "@/public/images/landing/cases/rainwater_building.png";
import rainwaterUsage from "@/public/images/landing/cases/rainwater_usage.png";
import rainwaterUsageAmount from "@/public/images/landing/cases/rainwater_usage_amount.png";
import { useDmStore } from "@/app/store";
import { formatUsage } from "@/app/common/transValueFormat";
import type {
DmStatusBildTpYearsType,
DmStatusUsgBildTpType,
DmStatusUsgYearsType,
TableHeaderType,
} from "@/app/store/dm-status/type";
// 공통 섹션 타이틀 컴포넌트
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
@@ -16,76 +21,240 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type ImgCardProps = {
src: StaticImageData;
alt: string;
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
function ImgCard({ src, alt }: ImgCardProps) {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
);
}
const headerColors6 = [
"bg-[#01ACE5]",
"bg-[#17AFC7]",
"bg-[#2CB2A9]",
"bg-[#42B58B]",
"bg-[#57B86D]",
"bg-[#81C784]",
];
const headerColors8 = [
"bg-[#01ACE5]",
"bg-[#10AED0]",
"bg-[#20B0BA]",
"bg-[#2FB2A5]",
"bg-[#3FB58F]",
"bg-[#4EB77A]",
"bg-[#5EB964]",
"bg-[#6DBB4F]",
];
export default function RainwaterStatusPage() {
const getRainyByBildTpYears = useDmStore((state) => state.getRainyByBildTpYears);
const getRainyByUsgBildTp = useDmStore((state) => state.getRainyByUsgBildTp);
const getRainyByUsgYears = useDmStore((state) => state.getRainyByUsgYears);
const rainyYears = useDmStore((state) => state.rainy.rainyYears);
const selectYear = rainyYears.slice(-1)[0];
const rainyByBildTpYearsHeader = useDmStore((state) => state.rainy.rainyByBildTpYearsHeader);
const rainyByBildTpYears = useDmStore((state) => state.rainy.rainyByBildTpYears);
const rainyByUsgBildTpHeader = useDmStore((state) => state.rainy.rainyByUsgBildTpHeader);
const rainyByUsgBildTp = useDmStore((state) => state.rainy.rainyByUsgBildTp);
const rainyByUsgYearsHeader = useDmStore((state) => state.rainy.rainyByUsgYearsHeader);
const rainyByUsgYears = useDmStore((state) => state.rainy.rainyByUsgYears);
const partBildTpHeader = useMemo(
() => rainyByUsgBildTpHeader.slice(3).map((header) => header.title),
[rainyByUsgBildTpHeader]
);
// 출처 텍스트
function SourceText() {
return (
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 환경부(2024), 「{selectYear} 하수도 통계」
</p>
);
}
useEffect(() => {
void getRainyByBildTpYears();
void getRainyByUsgBildTp();
void getRainyByUsgYears();
}, [getRainyByBildTpYears, getRainyByUsgBildTp, getRainyByUsgYears]);
const renderPartHeaders = (headers: string[], headerColors: string[]) => {
return headers.map((header, index) => (
<th
key={header}
className={`${tableStyles.th} ${headerColors[index] ?? "bg-[#81C784]"} text-white`}
style={{ width: "140px" }}
>
{header}
</th>
));
};
const renderBuildingUsageCells = (
row: DmStatusBildTpYearsType | DmStatusUsgYearsType | DmStatusUsgYearsType,
headers: TableHeaderType[]
) => {
const rowData = row as Record<string, string | number>;
const firstHeaderId = headers[0]?.id;
const rowKey = firstHeaderId ? String(rowData[firstHeaderId] ?? "") : "";
return headers.map((header, index) => {
const value = rowData[header.id];
return (
<td
key={`${rowKey}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 3, 5].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{index === 0 ? value : formatUsage(value as string | number, 1000, 1)}
</td>
);
});
};
const renderRainyByUsgBildTpCells = (row: DmStatusUsgBildTpType) => {
return rainyByUsgBildTpHeader.map((header, index) => {
const value = row[header.id as keyof DmStatusUsgBildTpType];
const displayValue =
index === 0
? value
: header.id === "CNT"
? Number(value).toLocaleString("ko-KR")
: formatUsage(value as string | number, 1000, 1);
return (
<td
key={`${row.USG_NM}-${header.id}`}
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 2].includes(index) ? "bg-[#EAF5F5]" : ""}`}
>
{displayValue == 0.0 ? "-" : displayValue}
</td>
);
});
};
export default function GreywaterPage() {
return (
<div className="bg-white">
<div className="bg-white">
<SubPageHeader
title="빗물이용시설(현황)"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 현황", href: "/reuse/cases/rain" },
{ label: "빗물이용시설(현황)" },
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 현황", href: "/reuse/cases/rain" },
{ label: "빗물이용시설(현황)" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
<div className="pr-[100px]">
<Hr className="my-7" />
{/* 1) 빗물이용시설의 건축물별 빗물이용량 현황 */}
<section className="mt-6">
<SectionTitle>빗물이용시설의 건축물별 빗물이용량 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={rainwaterBuilding} alt="빗물이용시설의 건축물별 빗물이용량 현황 설명" />
</div>
</section>
{/* 1) 빗물이용시설의 건축물별 빗물이용량 현황 */}
<section id="status-rain-building" className="mt-10 scroll-mt-[120px]">
<SectionTitle>빗물이용시설의 건축물별 빗물이용량 현황</SectionTitle>
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>
구분
</th>
<th colSpan={rainyYears.length || 6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>
빗물이용량 (천m³/년)
</th>
</tr>
<tr>
{renderPartHeaders(rainyYears, headerColors6)}
</tr>
</thead>
<tbody>
{rainyByBildTpYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderBuildingUsageCells(row, rainyByBildTpYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 2) 빗물이용시설의 용도별 분포 (2023년 기준) */}
<section>
<SectionTitle>빗물이용시설의 용도별 분포 (2023년 기준)</SectionTitle>
<div className="mt-8">
<ImgCard src={rainwaterUsage} alt="빗물이용시설의 용도별 분포 설명" />
</div>
</section>
{/* 2) 빗물이용시설의 용도별 분포 (2023년 기준) */}
<section id="status-rain-purpose-distribution" className="scroll-mt-[120px]">
<SectionTitle>빗물이용시설의 용도별 분포 ({selectYear}년 기준)</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>용도</th>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "84px" }}>개소수<br />(개소)</th>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "84px" }}>저류용량<br />(천m³)</th>
<th colSpan={8} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>빗물이용량 (천m³/년)</th>
</tr>
<tr>
{renderPartHeaders(partBildTpHeader, headerColors8)}
</tr>
</thead>
<tbody>
{rainyByUsgBildTp.map((row, idx) => (
<tr key={`${row.USG_NM}-${idx}`} className="bg-white">
{renderRainyByUsgBildTpCells(row)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 3) 빗물이용시설의 용도별 빗물이용량 현황 */}
<section className="pb-[200px]">
<SectionTitle>빗물이용시설의 용도별 빗물이용량 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={rainwaterUsageAmount} alt="빗물이용시설의 용도별 빗물이용량 현황 설명" />
</div>
</section>
{/* 3) 빗물이용시설의 용도별 빗물이용량 현황 */}
<section id="status-rain-purpose-usage" className="pb-[200px] scroll-mt-[120px]">
<SectionTitle>빗물이용시설의 용도별 빗물이용량 현황</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<thead>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>용도</th>
<th colSpan={6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>빗물이용량 (천m³/년)</th>
</tr>
<tr>
{renderPartHeaders(rainyYears, headerColors6)}
</tr>
</thead>
<tbody>
{rainyByUsgYears.map((row, idx) => (
<tr key={idx} className="bg-white">
{renderBuildingUsageCells(row, rainyByUsgYearsHeader)}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
+358 -42
View File
@@ -1,13 +1,9 @@
// apps/dm/app/(landing)/reuse/cases/treated/page.tsx
"use client";
import Image, { type StaticImageData } from "next/image";
import React from "react";
import SubPageHeader from "@/components/SubPageHeader";
import treatedWaterYear from "@/public/images/landing/cases/treated_water_year.png";
import treatedWaterUsage from "@/public/images/landing/cases/treated_water_usage.png";
import treatedWaterFacility from "@/public/images/landing/cases/treated_water_facility.png";
import treatedWaterRegion from "@/public/images/landing/cases/treated_water_region.png";
// 공통 섹션 타이틀 컴포넌트
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
@@ -17,86 +13,406 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
);
}
type HrProps = {
className?: string;
};
function Hr({ className = "" }: HrProps) {
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type ImgCardProps = {
src: StaticImageData;
alt: string;
};
function ImgCard({ src, alt }: ImgCardProps) {
// 출처 텍스트
function SourceText() {
return (
<div className="w-full overflow-x-auto">
<Image
src={src}
alt={alt}
className="block mx-auto max-w-none"
style={{ width: "auto", height: "auto" }}
/>
</div>
<p className="mt-7 text-center text-[13px] text-neutral-500">
자료: 환경부(2024), 「2023 하수도 통계」
</p>
);
}
export default function GreywaterPage() {
/**
* 공통 테이블 스타일
* - table-fixed: 열 넓이를 고정하고 균등 분배하기 위해 추가
* - 폰트 사이즈: 16px로 수정
*/
const tableStyles = {
container: "w-[1000px] mx-auto overflow-hidden",
table: "w-full table-fixed border-collapse text-center text-[16px]",
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
headerBg: "bg-[#E3E7EA]",
};
// export default function TreatedWaterPage() {
// return (
// <section id={id} className={`scroll-mt-[140px] ${className}`}>
// <SectionTitle>{title}</SectionTitle>
// <div className="mt-8">{children}</div>
// </section>
// );
// }
export default function TreatedStatusPage() {
return (
<div className="bg-white">
<div className="relative bg-white">
{/* <BusinessSubNav /> */}
<SubPageHeader
title="하수처리수 재이용시설(현황)"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "물 재이용 현황", href: "/reuse/cases/rain" },
{ label: "하수처리수 재이용시설(현황)" },
{ label: "Home", href: "/main" },
{ label: "물 재이용 현황", href: "/status/rain" },
{ label: "하수처리수 재이용시설(현황)" },
]}
hideFooterLine
hideGuideText
wrapClassName="!pb-0"
/>
<Hr className="my-7" />
<div className="pr-0">
<Hr className="my-7" />
{/* 1) 연도별 하수처리수 재이용 현황 */}
<section className="mt-6">
<section className="mt-10">
<SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={treatedWaterYear} alt="연도별 하수처리수 재이용 현황 설명" />
<div className="mt-10">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '130px' }} />
<col style={{ width: '180px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th colSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
["개소수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "671", "681", "698", "704", "714", "717"],
["하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>", "7,163.7", "7,140.4", "7,375.6", "7,335.8", "7,378.9", "7,537.2"],
["재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>", "1,112.7", "1,148.7", "1,148.7", "1,127.0", "1,136.8", "1,137.5"],
["재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>", "17.4", "16.5", "15.5", "14.1", "14.3", "14.4"],
]
},
{
group: "법적 대상",
rows: [
["개소수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "68", "82", "94", "97", "101", "118"],
["하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>", "761.2", "839.0", "935.3", "1,158.7", "1,220.6", "1,473.6"],
["재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>", "162.2", "190.6", "218.5", "244.6", "247.2", "324.9"],
["재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>", "24.7", "25.4", "27.8", "25.7", "24.2", "24.1"],
]
},
{
group: "법적 미대상",
rows: [
["개소수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "603", "599", "604", "607", "613", "599"],
["하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>", "6,402.5", "6,301.4", "6,440.3", "6,177.1", "6,158.3", "6,063.5"],
["재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>", "950.6", "958.1", "926.2", "882.4", "889.5", "812.7"],
["재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>", "16.5", "15.3", "13.6", "12.2", "12.6", "12.5"],
]
}
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${isFirstCol ? "!font-bold !text-neutral-900" : ""} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 2) 용도별 하수처리수 재이용 현황 */}
<section>
<SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={treatedWaterUsage} alt="용도별 하수처리수 재이용 현황 설명" />
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '180px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
{ label: "총 재이용량", type: "total", values: ["1,112.7", "1,148.7", "1,144.7", "1,127.0", "1,136.8", "1,137.5"] },
{ label: "장내 재이용", type: "category", values: ["520.6", "519.8", "513.9", "520.3", "502.2", "490.0"] },
{ label: "세척수", type: "red-item", values: ["177.9", "174.4", "169.4", "180.9", "196.8", "191.2"] },
{ label: "냉각수", type: "red-item", values: ["45.5", "46.8", "50.4", "40.6", "38.3", "41.7"] },
{ label: "청소수", type: "red-item", values: ["17.6", "21.2", "21.2", "21.7", "20.8", "20.5"] },
{ label: "조경용수", type: "red-item", values: ["0.1", "0.1", "-", "-", "-", "-"] },
{ label: "회생용수", type: "red-item", values: ["15.8", "16.7", "17.8", "15.7", "14.4", "14.0"] },
{ label: "장내급수도", type: "red-item", values: ["2.4", "-", "-", "-", "-", "-"] },
{ label: "장내기타용수", type: "red-item", values: ["261.3", "260.6", "255.0", "261.1", "231.8", "222.6"] },
{ label: "장외 재이용", type: "category", values: ["592.1", "628.9", "630.8", "606.7", "634.6", "647.5"] },
{ label: "청소·화장실용수", type: "blue-item", values: ["0.6", "0.4", "0.4", "0.4", "0.4", "0.4"] },
{ label: "세척·살수용수", type: "blue-item", values: ["5.6", "9.9", "2.9", "2.1", "3.0", "3.6"] },
{ label: "조경용수", type: "blue-item", values: ["10.3", "16.4", "5.9", "4.5", "8.9", "9.5"] },
{ label: "친수용수", type: "blue-item", values: ["7.8", "3.1", "3.1", "3.0", "10.9", "10.5"] },
{ label: "하천유지용수", type: "blue-item", values: ["480.5", "482.5", "496.2", "442.7", "471.7", "484.0"] },
{ label: "농업용수", type: "blue-item", values: ["12.2", "11.9", "13.4", "10.7", "10.6", "11.4"] },
{ label: "지하수 충전", type: "blue-item", values: ["0.0", "0.0", "0.0", "13.4", "0.0", "0.0"] },
{ label: "공업용수", type: "blue-item", values: ["75.2", "104.6", "108.9", "129.9", "129.0", "128.1"] },
].map((row, idx) => {
const isSubItem = row.type === "red-item" || row.type === "blue-item";
const isLastInGroup = idx === 8 || idx === 17;
return (
<tr key={idx} className="bg-white">
<td className={`${tableStyles.td} relative`}>
{isSubItem ? (
<>
<div className={`absolute left-[1px] w-[16px] bg-white z-10 top-[-2px] ${isLastInGroup ? 'bottom-[0px]' : 'bottom-[-2px]'}`} />
<div className={`absolute left-[16px] w-[4px] z-20 ${row.type === 'red-item' ? 'bg-[#FF8B8B]' : 'bg-[#4ECBFF]'} top-[-2px] ${isLastInGroup ? 'bottom-[0px]' : 'bottom-[-2px]'}`} />
<span className="text-[#4f4f4f] font-bold block w-full text-center relative z-30 translate-x-[8px]">{row.label}</span>
</>
) : (
<span className="font-bold text-neutral-900 block w-full text-center">{row.label}</span>
)}
</td>
{row.values.map((val, i) => (
<td key={i} className={`${tableStyles.td} ${[0, 2, 4].includes(i) ? "bg-[#EAF5F5]" : ""}`}>
{val}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 3) 하수처리시설 설치 및 이용 현황 */}
<section>
<SectionTitle>하수처리시설 설치 및 이용 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={treatedWaterFacility} alt="하수처리시설 설치 및 이용 현황 설명" />
<div className={tableStyles.container}>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '130px' }} />
<col style={{ width: '160px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th colSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>구분</th>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white`}>2018</th>
<th className={`${tableStyles.th} bg-[#14AFCD] text-white`}>2019</th>
<th className={`${tableStyles.th} bg-[#28B2B4] text-white`}>2020</th>
<th className={`${tableStyles.th} bg-[#3CB59A] text-white`}>2021</th>
<th className={`${tableStyles.th} bg-[#50B880] text-white`}>2022</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white`}>2023</th>
</tr>
</thead>
<tbody>
{[
{
group: "전체",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "671", "681", "698", "704", "714", "717"],
["시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "25,846", "26,067", "26,244", "26,492", "26,586", "26,766"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "1,112.7", "1,148.7", "1,144.7", "1,127.0", "1,136.8", "1,137.5"],
]
},
{
group: "법적 대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "68", "82", "94", "97", "101", "118"],
["시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "3,088.9", "3,359.2", "3,612.6", "4,412.2", "4,692.3", "5,503.8"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "162.2", "190.6", "218.5", "244.6", "247.2", "324.9"],
]
},
{
group: "법적 미대상",
rows: [
["시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>", "603", "599", "604", "607", "613", "599"],
["시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "22,757", "22,708", "22,631", "22,080", "21,894", "21,262"],
["처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", "950.6", "958.1", "926.2", "882.4", "889.5", "812.7"],
]
}
].map((section, sIdx) => (
<React.Fragment key={sIdx}>
{section.rows.map((row, rIdx) => (
<tr key={`${sIdx}-${rIdx}`} className="bg-white">
{rIdx === 0 && (
<td
rowSpan={section.rows.length}
className={`${tableStyles.td} ${tableStyles.headerBg} !text-[18px] !font-bold !text-neutral-900`}
>
{section.group}
</td>
)}
{row.map((val, i) => {
const isFirstCol = i === 0;
const isHighlightedCol = [1, 3, 5].includes(i);
return (
<td
key={i}
className={`${tableStyles.td} ${isFirstCol ? "!font-bold !text-neutral-900" : ""} ${isHighlightedCol ? "bg-[#EAF5F5]" : ""}`}
dangerouslySetInnerHTML={{ __html: val }}
/>
);
})}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
<Hr className="my-11" />
<Hr className="my-11" />
{/* 4) 시도별 하수처리수 재이용 현황 */}
{/* 4) 시도별 하수처리수 재이용 현황 */}
<section className="pb-[200px]">
<SectionTitle>시도별 하수처리수 재이용 현황</SectionTitle>
<div className="mt-8">
<ImgCard src={treatedWaterRegion} alt="시도별 하수처리수 재이용 현황 설명" />
<div className={tableStyles.container}>
<div className="text-right text-[13px] mb-2 text-neutral-600">(단위: 백만m³/일)</div>
<table className={tableStyles.table}>
<colgroup>
<col style={{ width: '160px' }} />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`}>시도</th>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`}>연간<br/>하수<br/>처리량</th>
<th colSpan={7} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>재이용현황</th>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`}>재이용률<br/>(%)</th>
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`}>비고</th>
</tr>
<tr>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>계</th>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`}>장내</th>
<th colSpan={5} className={`${tableStyles.th} ${tableStyles.headerBg} py-1`}>장외</th>
</tr>
<tr>
{/* 💡 하천유지용수는 14px, 나머지 하위 헤더는 16px로 적용 */}
<th className={`${tableStyles.th} bg-[#01ACE5] text-white py-2 text-[16px]`}>소계</th>
<th className={`${tableStyles.th} bg-[#1FB1C2] text-white py-2 text-[13px]`}>하천유지용수</th>
<th className={`${tableStyles.th} bg-[#3EB59F] text-white py-2 text-[16px]`}>공업용수</th>
<th className={`${tableStyles.th} bg-[#5DB97C] text-white py-2 text-[16px]`}>농업용수</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white py-2 text-[16px]`}>기타용수</th>
</tr>
</thead>
<tbody>
{[
["합계", "7,537.2", "1,137.5", "490.0", "647.5", "484.0", "128.1", "11.4", "24.0", "15.1%", ""],
["서울특별시", "1,567.0", "114.9", "59.3", "55.6", "55.1", "-", "-", "0.5", "7.3%", ""],
["부산광역시", "491.5", "146.2", "120.7", "25.5", "25.3", "-", "-", "0.2", "29.7%", ""],
["대구광역시", "432.7", "102.4", "38.2", "64.2", "64.1", "-", "-", "0.1", "23.7%", ""],
["인천광역시", "302.3", "57.7", "11.6", "46.1", "29.2", "12.1", "0.5", "4.3", "19.1%", ""],
["광주광역시", "211.1", "28.7", "6.5", "22.3", "22.2", "-", "-", "0.1", "13.6%", ""],
["대전광역시", "244.6", "11.9", "11.9", "0.68", "0.0", "-", "-", "0.1", "4.9%", ""],
["울산광역시", "235.1", "36.1", "6.1", "29.9", "29.7", "0.2", "-", "-", "15.3%", ""],
["세종특별자치시", "38.5", "2.5", "2.0", "0.5", "0.4", "-", "0.2", "-", "6.6%", ""],
["경기도", "1,926.8", "313.3", "122.2", "191.0", "153.0", "33.3", "0.4", "4.3", "16.3%", ""],
["강원도", "230.4", "19.6", "15.4", "4.2", "2.1", "-", "-", "2.1", "8.5%", ""],
["충청북도", "190.9", "15.6", "6.3", "9.2", "7.4", "0.6", "-", "1.2", "8.2%", ""],
["충청남도", "241.5", "41.4", "9.8", "31.5", "17.7", "8.6", "4.5", "0.8", "17.1%", ""],
["전라북도", "292.4", "42.7", "7.5", "35.1", "33.5", "0.2", "1.0", "0.5", "14.6%", ""],
["전라남도", "254.0", "23.6", "12.3", "11.3", "8.7", "0.0", "1.0", "1.6", "9.3%", ""],
["경상북도", "378.4", "129.1", "33.7", "95.3", "20.8", "72.5", "0.8", "1.3", "34.1%", ""],
["경상남도", "411.3", "50.1", "25.0", "25.0", "14.7", "0.5", "3.0", "6.9", "12.2%", ""],
["제주특별자치도", "88.6", "1.7", "1.5", "0.12", "-", "-", "0.1", "-", "1.9%", ""],
].map((row, idx) => (
<tr key={idx} className="bg-white">
{/* 💡 연간하수처리량(index 1), 장내(index 3), 재이용률(index 9) 컬러 하이라이트 적용 */}
{row.map((val, i) => (
<td
key={i}
className={`${tableStyles.td} ${i === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 3, 9].includes(i) ? "bg-[#EAF5F5]" : ""}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<SourceText />
</div>
</section>
</div>
</div>
);
}
}
+31 -1
View File
@@ -23,4 +23,34 @@ export default function TransValueFormat({value='', format=''}: CommonProps) {
: String(value ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</>
: <>{value}</>
)
}
}
/**
* 천,만 등의 단위 변경 && 반올림 및 소숫점 자리수 지정
* @param value
* @param unitSize 단위 변경 1000 인 경우, 100,000 -> 100 으로 변경
* @param decimalPlaces 어느 자리수에서 반올림할지 지정 1이면 소수 두번째 자리에서 반올림
* @returns
*/
export const formatUsage = (
value: string | number,
unitSize: number = 1,
decimalPlaces: number = 0
) => {
if (value === null || value === undefined || value === "") return "";
const numeric =
typeof value === "number" ? value : Number(String(value).replaceAll(",", ""));
if (Number.isNaN(numeric)) {
return String(value);
}
const scaledValue = numeric / unitSize;
const roundedValue =
Math.round(scaledValue * 10 ** decimalPlaces) / 10 ** decimalPlaces;
return roundedValue.toLocaleString("ko-KR", {
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
});
};
+1 -1
View File
@@ -16,7 +16,7 @@ export default function RootLayout({
<title>물재이용 대민</title>
</head>
<body>
<div className="min-w-[1800px] overflow-x-auto">
<div className="min-w-[1400px] overflow-x-auto">
{children}
</div>
</body>
+1 -1
View File
@@ -28,7 +28,7 @@ export default function Cases() {
<div className="mt-14">
<a
href="/reuse/cases/rain"
href="/status"
className="
inline-flex items-center justify-center gap-3
w-[260px] h-[52px]
+1 -1
View File
@@ -11,7 +11,7 @@ export default function Hero() {
<div className="absolute inset-0 z-10 flex items-center justify-center">
<div className="flex flex-col items-center justify-items-center gap-[40px] mx-auto max-w-[1520px] px-6 text-center text-white">
<h1 className="text-5xl font-bold tracking-tight">물 재이용</h1>
<p className="text-xl font-medium leading-6 text-white/85 max-w-[720px] mx-auto">
<p className="text-2xl font-medium leading-8 text-white/85 max-w-[720px] mx-auto">
빗물, 오수, 하수처리수, 폐수처리수, 발전소 온배수를 재이용처리하여<br />
생활, 공업, 농업, 조경, 하천유지 등의 용도로 재이용합니다.
</p>
+5 -5
View File
@@ -20,25 +20,25 @@ const INTRO_CARDS: IntroCardItem[] = [
title: "물 재이용 법",
desc: "물의 재이용 촉진 및 지원에 관한 법률을 시행하고, 일부 법률은 개정되었습니다.",
image: card01,
href: "/reuse/intro/law",
href: "/intro#law-plan",
},
{
title: "친환경 수자원 빗물",
desc: "가뭄을 극복하고 홍수를 예방하기 위해 비가 내릴 때 빗물을 모아두었다가 사용합니다.",
image: card02,
href: "/reuse/intro/rain",
href: "/intro#rain-intro",
},
{
title: "알뜰하게 재이용되는 중수",
desc: "한번 사용한 물을 중수도 시설을 통해 재생하여 개별적 또는 지역적으로 재이용할 수 있습니다.",
image: card03,
href: "/reuse/intro/greywater",
href: "/intro#greywater-intro",
},
{
title: "하수처리수 재이용",
desc: "사용하고 버려진 공공하수처리장으로 모여진 물을 용도에 맞는 처리 후 재이용 할 수 있습니다.",
image: card04,
href: "/reuse/intro/treated",
href: "/intro#treatedwater-intro",
},
];
@@ -79,7 +79,7 @@ export default function IntroCards() {
<h3 className="text-lg font-semibold tracking-tight">
{card.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-white/90 line-clamp-3">
<p className="mt-2 text-sm leading-relaxed text-white/60 line-clamp-3">
{card.desc}
</p>
</div>
+13 -2
View File
@@ -1,8 +1,11 @@
// apps/dm/app/(landing)/reuse/page.tsx
/**
* 대민 메인 페이지(첫페이지)
*/
"use client";
import { useMemo } from "react";
import { useEffect, useMemo } from "react";
import TopNav from "@/components/TopNav";
import { removeCookie } from "@/src/shared/lib";
import SideIndicator from "./SideIndicator";
import { useScrollSpy } from "./_hooks/useScrollSpy";
@@ -11,6 +14,9 @@ import IntroCards from "./_sections/IntroCards";
import Cases from "./_sections/Cases";
import ProcessFlow from "./_sections/ProcessFlow";
const CLAIM_NAME = "Authorization";
const AUTH_USER_ID = "token-id";
const LANDING_SECTIONS = [
{ id: "main", label: "Main" },
{
@@ -34,6 +40,11 @@ export default function ReuseLandingPage() {
const sectionIds = useMemo(() => LANDING_SECTIONS.map((s) => s.id), []);
const activeId = useScrollSpy(sectionIds);
useEffect(() => {
removeCookie(CLAIM_NAME);
removeCookie(AUTH_USER_ID);
}, []);
return (
<main className="bg-white">
+1 -1
View File
@@ -496,7 +496,7 @@ export const CommonReducer:(set:any)=>CommonAction=(set: any) => {
}
})
)},
fetchAnonymousMenu: async (siteId = REUSE_SITE_ID) => fetchAnonymousMenu(siteId)
fetchAnonymousMenu: async (siteId = REUSE_SITE_ID) => fetchAnonymousMenu(siteId),
}
}
export type CommonStore = CommonType & CommonAction;
+262
View File
@@ -0,0 +1,262 @@
import { fetchType, getFetch } from "@/components/ui/api";
import type {
TableHeaderType, DmStatusBildTpYearsType, DmStatusUsgBildTpType, DmStatusUsgYearsType,
DmStatusLawYearsType, DmStatusSidoUsgType,
} from "./type";
import { rainyByBildTpYearsHeader, rainyByUsgBildTpHeader, rainyByUsgYearsHeader } from "./type";
import { rswtrByLawYearsHeader, rswtrByBildTpYearsHeader, rswtrByUsgYearsHeader } from "./type";
import { pbnstByLawYearsHeader, pbnstByUsgYearsHeader, pbnstByCpcYearsHeader, pbnstBySidoHeader } from "./type";
export type DmStatusActions = {
getRainyByBildTpYears: () => Promise<void>; // 1.대민 빗물재이용현황 건축물유형별 최근 6년간
getRainyByUsgBildTp: () => Promise<void>; // 2.대민 빗물재이용현황 이용용도별 건축물별
getRainyByUsgYears: () => Promise<void>; // 3.대민 빗물재이용현황 이용용도별 최근 6년간
getRswtrByLawYears: (bildTpCond: string) => Promise<void>; // 4.대민 중수도재이용현황 법적대상별 최근 6년간
getRswtrByBildTpYears: () => Promise<void>; // 5.대민 중수도재이용현황 건축물별 최근 6년간
getRswtrByUsgYears: () => Promise<void>; // 6.대민 중수도재이용현황 이용용도별 최근 6년간
getPbnstByLawYears: () => Promise<void>; // 7.대민 하수재이용현황 법적대상별 최근 6년간
getPbnstByUsgYears: () => Promise<void>; // 8.대민 하수재이용현황 이용용도별 최근 6년간
getPbnstByCpcYears: () => Promise<void>; // 9.대민 하수재이용현황 용량 최근 6년간
getPbnstBySido: () => Promise<void>; // 10.대민 하수재이용현황 시도별
};
export type DmStatusType = {
rainy: {
rainyYears: string[];
rainyByBildTpYearsHeader: TableHeaderType[];
rainyByUsgBildTpHeader: TableHeaderType[];
rainyByUsgYearsHeader: TableHeaderType[];
rainyByBildTpYears: DmStatusBildTpYearsType[];
rainyByUsgBildTp: DmStatusUsgBildTpType[];
rainyByUsgYears: DmStatusUsgYearsType[];
},
rswtr: {
rswtrYears: string[];
rswtrByLawYearsHeader: TableHeaderType[];
rswtrByBildTpYearsHeader: TableHeaderType[];
rswtrByUsgYearsHeader: TableHeaderType[];
rswtrByLawYearsFactory: DmStatusLawYearsType[];
rswtrByLawYears: DmStatusLawYearsType[];
rswtrByBildTpYears: DmStatusBildTpYearsType[];
rswtrByUsgYears: DmStatusUsgYearsType[];
},
pbnst: {
pbnstYears: string[];
pbnstByLawYearsHeader: TableHeaderType[];
pbnstByUsgYearsHeader: TableHeaderType[];
pbnstByCpcYearsHeader: TableHeaderType[];
pbnstBySidoHeader: TableHeaderType[];
pbnstByLawYears: DmStatusLawYearsType[];
pbnstByUsgYears: DmStatusUsgYearsType[];
pbnstByCpcYears: DmStatusLawYearsType[];
pbnstBySido: DmStatusSidoUsgType[];
}
};
export const dmStatusState: DmStatusType = {
rainy: {
rainyYears: ["2019", "2020", "2021", "2022", "2023", "2024"],
rainyByBildTpYearsHeader: rainyByBildTpYearsHeader,
rainyByUsgBildTpHeader: rainyByUsgBildTpHeader,
rainyByUsgYearsHeader: rainyByUsgYearsHeader,
rainyByBildTpYears: [],
rainyByUsgBildTp: [],
rainyByUsgYears: [],
},
rswtr: {
rswtrYears: ["2019", "2020", "2021", "2022", "2023", "2024"],
rswtrByLawYearsHeader: rswtrByLawYearsHeader,
rswtrByBildTpYearsHeader: rswtrByBildTpYearsHeader,
rswtrByUsgYearsHeader: rswtrByUsgYearsHeader,
rswtrByLawYearsFactory: [], // 공장 및 발전시설 내 중수도 설치 현황
rswtrByLawYears: [], // 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)
rswtrByBildTpYears: [],
rswtrByUsgYears: [],
},
pbnst: {
pbnstYears: ["2019", "2020", "2021", "2022", "2023", "2024"],
pbnstByLawYearsHeader: pbnstByLawYearsHeader,
pbnstByUsgYearsHeader: pbnstByUsgYearsHeader,
pbnstByCpcYearsHeader: pbnstByCpcYearsHeader,
pbnstBySidoHeader: pbnstBySidoHeader,
pbnstByLawYears: [],
pbnstByUsgYears: [],
pbnstByCpcYears: [],
pbnstBySido: [],
}
};
export type DmStatusStore = DmStatusType & DmStatusActions;
export const DmStatusReducer = (set: any): DmStatusActions => {
return {
// 1. 대민 빗물재이용현황 건축물유형별 최근 6년간
getRainyByBildTpYears: async () => {
const res = await getFetch(fetchType.WR, "/dmStatus/rainyByBildTpYears");
const rainyByBildTpYears = Array.isArray(res?.data) ? res.data : [];
const rainyYears = (rainyByBildTpYears[0]?.YEAR ?? "")
.split(",")
.map((year) => year.trim())
.filter(Boolean);
set((state: DmStatusType) => ({
...state,
rainy: {
...state.rainy,
rainyByBildTpYears,
rainyYears,
}
}));
},
// 2.대민 빗물재이용현황 이용용도별 건축물별
getRainyByUsgBildTp: async () => {
const res = await getFetch(fetchType.WR, "/dmStatus/rainyByUsgBildTp");
const rainyByUsgBildTp = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
rainy: {
...state.rainy,
rainyByUsgBildTp,
}
}));
},
// 3.대민 빗물재이용현황 이용용도별 최근 6년간
getRainyByUsgYears: async () => {
const res = await getFetch(fetchType.WR, "/dmStatus/rainyByUsgYears");
const rainyByUsgYears = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
rainy: {
...state.rainy,
rainyByUsgYears,
}
}));
},
// 4.대민 중수도재이용현황 법적대상별 최근 6년간
getRswtrByLawYears: async (bildTpCond: string) => {
const res = await getFetch(fetchType.WR,`/dmStatus/rswtrByLawYears/${bildTpCond}`);
const rswtrByLawYears = Array.isArray(res?.data) ? res.data : [];
const maxYear = Number(rswtrByLawYears[0]?.MAXYR);
const rswtrYears = Number.isFinite(maxYear)
? Array.from({ length: 6 }, (_, index) => String(maxYear - 5 + index))
: dmStatusState.rswtr.rswtrYears;
if (bildTpCond === "factory") {
set((state: DmStatusType) => ({
...state,
rswtr: {
...state.rswtr,
rswtrYears,
rswtrByLawYearsFactory: rswtrByLawYears,
}
}));
} else {
set((state: DmStatusType) => ({
...state,
rswtr: {
...state.rswtr,
rswtrYears,
rswtrByLawYears,
}
}));
}
},
// 5.대민 중수도재이용현황 건축물별 최근 6년간
getRswtrByBildTpYears: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/rswtrByBildTpYears");
const rswtrByBildTpYears = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
rswtr: {
...state.rswtr,
rswtrByBildTpYears,
}
}));
},
// 6.대민 중수도재이용현황 이용용도별 최근 6년간
getRswtrByUsgYears: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/rswtrByUsgYears");
const rswtrByUsgYears = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
rswtr: {
...state.rswtr,
rswtrByUsgYears,
}
}));
},
// 7.대민 하수재이용현황 법적대상별 최근 6년간
getPbnstByLawYears: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/pbnstByLawYears");
const pbnstByLawYears = Array.isArray(res?.data) ? res.data : [];
const maxYear = Number(pbnstByLawYears[0]?.MAXYR);
const pbnstYears = Number.isFinite(maxYear)
? Array.from({ length: 6 }, (_, index) => String(maxYear - 5 + index))
: dmStatusState.rswtr.rswtrYears;
set((state: DmStatusType) => ({
...state,
pbnst: {
...state.pbnst,
pbnstYears,
pbnstByLawYears,
}
}));
},
// 8.대민 하수재이용현황 이용용도별 최근 6년간
getPbnstByUsgYears: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/pbnstByUsgYears");
const pbnstByUsgYears = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
pbnst: {
...state.pbnst,
pbnstByUsgYears,
}
}));
},
// 9.대민 하수재이용현황 용량 최근 6년간
getPbnstByCpcYears: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/pbnstByCpcYears");
const pbnstByCpcYears = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
pbnst: {
...state.pbnst,
pbnstByCpcYears,
}
}));
},
// 10.대민 하수재이용현황 시도별
getPbnstBySido: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/pbnstBySido");
const pbnstBySido = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
pbnst: {
...state.pbnst,
pbnstBySido,
}
}));
},
};
};
+285
View File
@@ -0,0 +1,285 @@
export type TableHeaderType = {
id: string;
title: string;
upName?: string;
upSequnce?: number;
align?: string;
row?: number;
rowSpan?: number;
colSpan?: number;
widthPercent?: number;
}
export type DmStatusBildTpYearsType = {
BILD_TP: string; // 건물구분
YEAR?: string; // 가로 연도 목록
Y1: string | number;
Y2: string | number;
Y3: string | number;
Y4: string | number;
Y5: string | number;
Y6: string | number;
MAXYR?: string; // 최종 확정 연도
};
export type DmStatusUsgBildTpType = {
USG_NM: string;
CNT: string | number;
USEQTY: string | number;
SGBS_CPCTY: string | number;
PUB_FCT: string | number;
COMM_HOUSE: string | number;
MEGA_STORE: string | number;
GYM_FCT: string | number;
SCHOOL: string | number;
GOLF: string | number;
ETC: string | number;
};
export type DmStatusUsgYearsType = {
USG_NM: string; // 용도
YEAR?: string; // 가로 연도 목록
Y1: string | number;
Y2: string | number;
Y3: string | number;
Y4: string | number;
Y5: string | number;
Y6: string | number;
COL_NM?: string; // 컬럼명
MAXYR?: string; // 최종 확정 연도
};
export type DmStatusLawYearsType = {
COL1: string; // 전체, 법적 대상, 법적 미대상
COL2: string; // 구분
MAXYR?: string; // 최종 확정 연도
Y1: string | number;
Y2: string | number;
Y3: string | number;
Y4: string | number;
Y5: string | number;
Y6: string | number;
};
export type DmStatusSidoUsgType = {
ADST_CD: string; // 시도 코드
SIDONM: string; // 시도명
TPUT_TOTAL: string; // 연간하수처리량(천톤/년)
USE_AMNT_TOTAL: number; // 재이용현황
USE_AMNT_D01: number;
USE_AMNT_D02: number;
USE_AMNT_S11: number; // 청소·화장실용수
USE_AMNT_S12: number; // 세척·살수용수
USE_AMNT_S13: number; // 조경용수
USE_AMNT_S14: number; // 친수용수
USE_AMNT_S15: number; // 하천유지용수
USE_AMNT_S16: number; // 농업용수
USE_AMNT_S17: number; // 지하수충전
USE_AMNT_S18: number; // 공업용수
USE_AMNT_ETC: number; // 기타 (청소·화장실용수+세척·살수용수+조경용수+친수용수+지하수충전)
REUSE_RT: number;
};
// 빗물이용시설의 건축물별 빗물이용량 현황
export const rainyByBildTpYearsHeader: TableHeaderType[] = [
{ id: "BILD_TP", title: "구분", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
export const rainyByBildTpYearsInit: DmStatusBildTpYearsType[] = [
{ BILD_TP: "골프장", Y1: "6,771.8", Y2: "6,417.4", Y3: "8,371.6", Y4: "10,215.4", Y5: "11,360", Y6: "10,191.6" },
{ BILD_TP: "공공시설", Y1: "364", Y2: "325.2", Y3: "201.9", Y4: "244.6", Y5: "209.9", Y6: "299.6" },
{ BILD_TP: "공동주택", Y1: "291", Y2: "840.5", Y3: "804.2", Y4: "2,393.0", Y5: "2,386", Y6: "2,428.4" },
{ BILD_TP: "대규모점포", Y1: "169.9", Y2: "214.4", Y3: "58.4", Y4: "427.2", Y5: "371", Y6: "293.3" },
{ BILD_TP: "체육시설", Y1: "52.6", Y2: "44.7", Y3: "48.6", Y4: "75.3", Y5: "71.8", Y6: "77.6" },
{ BILD_TP: "학교", Y1: "33", Y2: "25.5", Y3: "22", Y4: "155.8", Y5: "165.4", Y6: "32.6" },
{ BILD_TP: "기타", Y1: "311.6", Y2: "901.2", Y3: "344.5", Y4: "537.8", Y5: "564", Y6: "621.2" },
];
// 빗물이용시설의 용도별 분포
export const rainyByUsgBildTpHeader: TableHeaderType[] = [
{ id: "USG_NM", title: "용도", row: 0, widthPercent: 15 },
{ id: "CNT", title: "개소수", row: 1, widthPercent: 5 },
{ id: "SGBS_CPCTY", title: "저류용량", row: 1, widthPercent: 5 },
{ id: "USEQTY", title: "총사용량", row: 1, widthPercent: 5 },
{ id: "PUB_FCT", title: "공공시설", row: 1, widthPercent: 5 },
{ id: "COMM_HOUSE", title: "공동주택", row: 0, widthPercent: 7 },
{ id: "MEGA_STORE", title: "대규모점포", row: 0, widthPercent: 10 },
{ id: "GYM_FCT", title: "체육시설", row: 1, widthPercent: 5 },
{ id: "SCHOOL", title: "학교", row: 1, widthPercent: 5 },
{ id: "GOLF", title: "골프장", row: 1, widthPercent: 5 },
{ id: "ETC", title: "기타", row: 1, widthPercent: 5 },
];
export const rainyByUsgBildTpInit: DmStatusUsgBildTpType[] = [
{ USG_NM: "청소·환경정화용수", CNT: "195", SGBS_CPCTY: "32.7", USEQTY: "108.8", PUB_FCT: "24.9", COMM_HOUSE: "5.8", MEGA_STORE: "2.6", GYM_FCT: "4.4", SCHOOL: "2.9", GOLF: "-", ETC: "68.1" },
{ USG_NM: "세척·살수용수", CNT: "12", SGBS_CPCTY: "1.4", USEQTY: "37", PUB_FCT: "35", COMM_HOUSE: "-", MEGA_STORE: "-", GYM_FCT: "1.9", SCHOOL: "-", GOLF: "-", ETC: "0.2" },
{ USG_NM: "조경용수", CNT: "3,165", SGBS_CPCTY: "7,440.8", USEQTY: "13,554.1", PUB_FCT: "168.1", COMM_HOUSE: "2,421.9", MEGA_STORE: "290.7", GYM_FCT: "66.2", SCHOOL: "29", GOLF: "10,044", ETC: "534.3" },
{ USG_NM: "냉·난방수", CNT: "8", SGBS_CPCTY: "1.6", USEQTY: "0.1", PUB_FCT: "-", COMM_HOUSE: "-", MEGA_STORE: "-", GYM_FCT: "-", SCHOOL: "0.1", GOLF: "-", ETC: "-" },
{ USG_NM: "공업용수", CNT: "6", SGBS_CPCTY: "14.9", USEQTY: "53.2", PUB_FCT: "52.8", COMM_HOUSE: "-", MEGA_STORE: "-", GYM_FCT: "-", SCHOOL: "-", GOLF: "-", ETC: "0.4" },
{ USG_NM: "농업용수", CNT: "6.0", SGBS_CPCTY: "0.5", USEQTY: "0.6", PUB_FCT: "-", COMM_HOUSE: "-", MEGA_STORE: "-", GYM_FCT: "-", SCHOOL: "-", GOLF: "-", ETC: "0.6" },
{ USG_NM: "기타", CNT: "48", SGBS_CPCTY: "306.1", USEQTY: "155.6", PUB_FCT: "2.8", COMM_HOUSE: "-", MEGA_STORE: "-", GYM_FCT: "0.6", SCHOOL: "0.5", GOLF: "147.5", ETC: "4.2" },
{ USG_NM: "미기재", CNT: "103", SGBS_CPCTY: "22.9", USEQTY: "34.8", PUB_FCT: "16.0", COMM_HOUSE: "0.6", MEGA_STORE: "-", GYM_FCT: "4.6", SCHOOL: "0.2", GOLF: "-", ETC: "13.5" },
{ USG_NM: "합계", CNT: "3,543", SGBS_CPCTY: "7,820.8", USEQTY: "13,944.7", PUB_FCT: "299.6", COMM_HOUSE: "2,428.4", MEGA_STORE: "293.3", GYM_FCT: "77.6", SCHOOL: "32.6", GOLF: "10,191.6", ETC: "621.2" },
];
// 빗물이용시설의 용도별 빗물이용량 현황
export const rainyByUsgYearsHeader: TableHeaderType[] = [
{ id: "USG_NM", title: "용도", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
export const rainyByUsgYearsInit: DmStatusUsgYearsType[] = [
{ USG_NM: "청소·화장실용수", Y1: "126.5", Y2: "134.1", Y3: "96.5", Y4: "153.9", Y5: "140.5", Y6: "108.8" },
{ USG_NM: "세척·살수용수", Y1: "14.8", Y2: "14.6", Y3: "9.5", Y4: "49.8", Y5: "49.7", Y6: "37.0" },
{ USG_NM: "조경용수", Y1: "7,629.8", Y2: "8,496.2", Y3: "9,415.4", Y4: "13,360.4", Y5: "14,466.3", Y6: "13,554.1" },
{ USG_NM: "친수용수", Y1: "1.0", Y2: "0.1", Y3: "0.1", Y4: "0.1", Y5: "0.1", Y6: "0.1" },
{ USG_NM: "공업용수", Y1: "152.0", Y2: "77.0", Y3: "77.0", Y4: "75.4", Y5: "0.4", Y6: "53.2" },
{ USG_NM: "농업용수", Y1: "4.4", Y2: "4.3", Y3: "4.3", Y4: "4.3", Y5: "0.6", Y6: "0.6" },
{ USG_NM: "기타", Y1: "37.7", Y2: "42.6", Y3: "230.7", Y4: "394.3", Y5: "463.2", Y6: "155.6" },
];
// 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)
export const rswtrByLawYearsHeader: TableHeaderType[] = [
{ id: "COL1", title: "구분", row: 0, widthPercent: 15 },
{ id: "COL2", title: "구분", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
export const rswtrByLawYearsInit: DmStatusLawYearsType[] = [
{ COL1: "전체", COL2: "CNT", Y1: "576", Y2: "633", Y3: "682", Y4: "740", Y5: "819", Y6: "842" },
{ COL1: "전체", COL2: "건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", Y1: "1,494.2", Y2: "1,514.4", Y3: "1,524.9", Y4: "989.1", Y5: "1,104.3", Y6: "1,015.4" },
{ COL1: "전체", COL2: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", Y1: "39.6", Y2: "40.0", Y3: "51.1", Y4: "47.1", Y5: "48.9", Y6: "50.7" },
{ COL1: "전체", COL2: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", Y1: "2,223.4", Y2: "1,165.2", Y3: "6,713.0", Y4: "4,681.7", Y5: "3,918.2", Y6: "898.4" },
{ COL1: "법적 대상", COL2: "CNT", Y1: "146", Y2: "154", Y3: "156", Y4: "170", Y5: "166", Y6: "169" },
{ COL1: "법적 대상", COL2: "건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", Y1: "674.9", Y2: "682.4", Y3: "736.1", Y4: "421.1", Y5: "434.9", Y6: "465.5" },
{ COL1: "법적 대상", COL2: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", Y1: "20.0", Y2: "20.1", Y3: "31.2", Y4: "22.8", Y5: "21.2", Y6: "24.7" },
{ COL1: "법적 대상", COL2: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", Y1: "1,819.4", Y2: "712.8", Y3: "6,479.9", Y4: "4,416.8", Y5: "3,663.1", Y6: "616.1" },
{ COL1: "법적 미대상", COL2: "CNT", Y1: "430", Y2: "479", Y3: "526", Y4: "570", Y5: "653", Y6: "673" },
{ COL1: "법적 미대상", COL2: "건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>", Y1: "819.3", Y2: "832.0", Y3: "788.8", Y4: "568.0", Y5: "669.4", Y6: "549.9" },
{ COL1: "법적 미대상", COL2: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", Y1: "19.6", Y2: "19.5", Y3: "19.9", Y4: "24.3", Y5: "27.7", Y6: "26.0" },
{ COL1: "법적 미대상", COL2: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", Y1: "404.0", Y2: "452.4", Y3: "233.1", Y4: "264.9", Y5: "255.1", Y6: "282.3" },
];
export const rswtrByLawYearsFactoryInit: DmStatusLawYearsType[] = [
{ COL1: "전체", COL2: "CNT", Y1: "125", Y2: "127", Y3: "139", Y4: "146", Y5: "151", Y6: "176" },
{ COL1: "전체", COL2: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", Y1: "141.3", Y2: "141.0", Y3: "146.7", Y4: "189.0", Y5: "190.3", Y6: "166.5" },
{ COL1: "전체", COL2: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", Y1: "33,712", Y2: "29,300", Y3: "33,110", Y4: "22,284", Y5: "21,593", Y6: "21,349" },
{ COL1: "법적 대상", COL2: "CNT", Y1: "59", Y2: "63", Y3: "78", Y4: "78", Y5: "82", Y6: "86" },
{ COL1: "법적 대상", COL2: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", Y1: "108.6", Y2: "109.7", Y3: "118.1", Y4: "159.5", Y5: "142.6", Y6: "119.4" },
{ COL1: "법적 대상", COL2: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", Y1: "24,847", Y2: "24,021", Y3: "28,055", Y4: "17,959", Y5: "12,241", Y6: "12,296" },
{ COL1: "법적 비대상", COL2: "CNT", Y1: "66", Y2: "64", Y3: "61", Y4: "68", Y5: "69", Y6: "90" },
{ COL1: "법적 비대상", COL2: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>", Y1: "32.7", Y2: "31.4", Y3: "28.7", Y4: "29.5", Y5: "47.7", Y6: "47.1" },
{ COL1: "법적 비대상", COL2: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>", Y1: "8,864.9", Y2: "5,278.8", Y3: "5,054.7", Y4: "4,324.6", Y5: "9,353.3", Y6: "9,052.4" },
];
export const Col2Name: Record<string, string> =
{ CNT: "시설수 <span class='text-[13px] font-normal text-[#767676]'>(개소)</span>",
TOTAR: "건축연면적 <span class='text-[13px] font-normal text-[#767676]'>(만m²)</span>",
PROCCPCTY: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
USECPCTY: "사용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/년)</span>",
TPUTTOTAL: "하수처리량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
USEAMNT: "재이용량 <span class='text-[13px] font-normal text-[#767676]'>(백만m³/년)</span>",
REUSERT: "재이용률 <span class='text-[13px] font-normal text-[#767676]'>(%)</span>",
FCT_CPC: "시설용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
TOT_TPUT_AVE: "처리용량 <span class='text-[13px] font-normal text-[#767676]'>(만m³/일)</span>",
};
export const rswtrByBildTpYearsHeader: TableHeaderType[] = [
{ id: "BILD_TP", title: "구분", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
export const rswtrByBildTpYearsInit: DmStatusBildTpYearsType[] = [
{ BILD_TP: "공동주택", Y1: "804.0", Y2: "245.8", Y3: "255.1", Y4: "282.3", Y5: "255.1", Y6: "282.3" },
{ BILD_TP: "판매시설", Y1: "1,447.5", Y2: "477.1", Y3: "802.1", Y4: "713.0", Y5: "972.3", Y6: "742.4" },
{ BILD_TP: "숙박시설", Y1: "458.5", Y2: "0.4", Y3: "177.0", Y4: "102.0", Y5: "123.3", Y6: "140.9" },
{ BILD_TP: "업무시설", Y1: "20.7", Y2: "0.3", Y3: "14.3", Y4: "44.9", Y5: "31.5", Y6: "37.4" },
{ BILD_TP: "의료시설", Y1: "97.7", Y2: "7.2", Y3: "63.7", Y4: "55.7", Y5: "85.1", Y6: "70.4" },
{ BILD_TP: "교육연구시설", Y1: "536.6", Y2: "1.5", Y3: "1,558.4", Y4: "772.2", Y5: "315.1", Y6: "66.7" },
{ BILD_TP: "운수·물류시설", Y1: "759.2", Y2: "34.2", Y3: "459.8", Y4: "259.8", Y5: "252.8", Y6: "410.4" },
{ BILD_TP: "기타", Y1: "453.1", Y2: "443.3", Y3: "3,126.8", Y4: "2,451.5", Y5: "1,880.8", Y6: "199.7" },
{ BILD_TP: "합계", Y1: "35,936.3", Y2: "30,466.6", Y3: "37,487.2", Y4: "25,480.6", Y5: "24,345.8", Y6: "22,247.6" },
];
export const rswtrByUsgYearsHeader: TableHeaderType[] = [
{ id: "USE_NM", title: "용도", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
// 연도별 하수처리수 재이용 현황
export const pbnstByLawYearsHeader: TableHeaderType[] = [
{ id: "COL1", title: "구분", row: 0, widthPercent: 15 },
{ id: "COL2", title: "구분", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
// 용도별 하수처리수 재이용 현황
export const pbnstByUsgYearsHeader: TableHeaderType[] = [
{ id: "USG_NM", title: "용도", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
// 하수처리시설 설치 및 이용 현황
export const pbnstByCpcYearsHeader: TableHeaderType[] = [
{ id: "COL1", title: "구분", row: 0, widthPercent: 15 },
{ id: "COL2", title: "구분", row: 0, widthPercent: 15 },
{ id: "Y1", title: "2019", row: 1, widthPercent: 5 },
{ id: "Y2", title: "2020", row: 1, widthPercent: 5 },
{ id: "Y3", title: "2021", row: 1, widthPercent: 5 },
{ id: "Y4", title: "2022", row: 0, widthPercent: 7 },
{ id: "Y5", title: "2023", row: 0, widthPercent: 10 },
{ id: "Y6", title: "2024", row: 1, widthPercent: 5 },
];
// 시도별 하수처리수 재이용 현황
export const pbnstBySidoHeader: TableHeaderType[] = [
{ id: "SIDONM", title: "시도", row: 0, widthPercent: 15 },
{ id: "TPUT_TOTAL", title: "연간하수처리량", row: 1, widthPercent: 5 },
{ id: "USE_AMNT_TOTAL", title: "재이용현황계", row: 1, widthPercent: 5 },
{ id: "USE_AMNT_D01", title: "재이용현황 장내", row: 1, widthPercent: 5 },
{ id: "USE_AMNT_D02", title: "재이용현황 장외소계", row: 0, widthPercent: 7 },
{ id: "USE_AMNT_S15", title: "장외 하천유지용수", row: 0, widthPercent: 10 },
{ id: "USE_AMNT_S16", title: "장외 농업용수", row: 1, widthPercent: 5 },
{ id: "USE_AMNT_S18", title: "장외 공업용수", row: 1, widthPercent: 5 },
{ id: "USE_AMNT_ETC", title: "장외 기타용수", row: 1, widthPercent: 5 },
{ id: "REUSE_RT", title: "재이용률", row: 1, widthPercent: 5 },
{ id: "REMARK", title: "비고", row: 1, widthPercent: 5 },
];
+3 -1
View File
@@ -537,6 +537,8 @@ const getSdmInfoStatusList = async (dmdInfoId: string) => {
// 유사사례 조회
const fetchGetSimilarCaseList = async (part: string, value: number | string) => {
try {
// part : 'S' 요구수량 , 'U' 사용용도
// default : part 'S' , value 10000 즉, part와 value값이 없으면 backend에서 default값으로 조회함.
const url = '/reuseFc/pubFcRuseCase/dm/' + part + '/' + (value ? value : 0);
const res = await getFetch(fetchType.WR, url);
return res?.data || [];
@@ -759,7 +761,7 @@ export const SdmReducer = (set: any): SdmActions => {
}
}))
},
// 용도 코드 조회 (WR018 : 하수재이용장외용도)
getUseTypesData: async () => {
//http://dev.onasys.co.kr:3000/server/cms-service/api/v1/lwpo-cds?lwpoCdNm=&sort=regDt,desc&upCd=WR018&page=0&size=10
+2 -2
View File
@@ -257,9 +257,9 @@ export function extractSearchInputFromRoadAddr(roadAddr: string): string {
* @returns 최대 5개의 "도로명 건물본번" 자동완성 문자열 목록
*/
export async function fetchPostcodeSuggestions(input: string): Promise<string[]> {
const normalized = input.replace(/\s+/g, " ").trim();
const normalized = input.replace(/\s+/g, " ").trim(); // space가 여러개일 때, 하나만 남김
const parts = normalized.split(" ");
const tail = parts.at(-1) ?? "";
const tail = parts.at(-1) ?? ""; // 마지막 요소
const roadNm =
/^\d+(?:-\d+)?$/.test(tail) && parts.length > 1
? parts.slice(0, -1).join(" ").trim()
+1 -2
View File
@@ -18,8 +18,7 @@ import {
dmStatusState,
type DmStatusStore,
type DmStatusType,
} from "./wrStatus";
} from "./dm-status";
export type DmStore = CommonStore & SdmStore & DmMapStore & DmStatusStore;
export type DmState = CommonType & SdmType & DmMapType & DmStatusType;
+242
View File
@@ -0,0 +1,242 @@
"use client";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
export type AnchorSection = {
id: string;
label: string;
};
export type BusinessSubNavMenu = {
key: string;
label: string;
href?: string;
current?: boolean;
defaultOpen?: boolean;
sections?: AnchorSection[];
};
type BusinessSubNavProps = {
menus?: BusinessSubNavMenu[];
offsetTop?: number;
className?: string;
};
export default function BusinessSubNav({
menus = [],
offsetTop = 120,
className = "",
}: BusinessSubNavProps) {
const [activeId, setActiveId] = useState("");
const [openMenus, setOpenMenus] = useState<Record<string, boolean>>({});
const allSections = useMemo(
() => menus.flatMap((menu) => menu.sections ?? []),
[menus]
);
useEffect(() => {
const initialOpenState: Record<string, boolean> = {};
menus.forEach((menu) => {
initialOpenState[menu.key] = menu.defaultOpen ?? menu.current ?? false;
});
setOpenMenus(initialOpenState);
}, [menus]);
useEffect(() => {
if (!allSections.length) return;
const observer = new IntersectionObserver(
(entries) => {
const visible = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
if (visible[0]) {
setActiveId(visible[0].target.id);
}
},
{
rootMargin: `-${offsetTop}px 0px -55% 0px`,
threshold: [0.2, 0.35, 0.5, 0.7],
}
);
const targets = allSections
.map((section) => document.getElementById(section.id))
.filter(Boolean) as HTMLElement[];
targets.forEach((el) => observer.observe(el));
const hash = window.location.hash.replace("#", "");
const initialActive =
allSections.find((section) => section.id === hash)?.id ??
allSections[0]?.id ??
"";
setActiveId(initialActive);
return () => observer.disconnect();
}, [allSections, offsetTop]);
useEffect(() => {
if (!activeId) return;
const parentMenu = menus.find((menu) =>
(menu.sections ?? []).some((section) => section.id === activeId)
);
if (!parentMenu) return;
setOpenMenus(() => {
const nextOpenState: Record<string, boolean> = {};
menus.forEach((menu) => {
nextOpenState[menu.key] = menu.key === parentMenu.key;
});
return nextOpenState;
});
}, [activeId, menus]);
const handleMove = (id: string) => {
setActiveId(id);
const target = document.getElementById(id);
if (!target) return;
window.history.replaceState(null, "", `#${id}`);
const top = target.getBoundingClientRect().top + window.scrollY - offsetTop;
window.scrollTo({
top,
behavior: "smooth",
});
};
const toggleMenu = (key: string) => {
setOpenMenus((prev) => ({
...prev,
[key]: !prev[key],
}));
};
if (!menus.length) return null;
return (
<aside className={`fixed right-2 top-[110px] z-50 w-[260px] ${className}`}>
<div className="overflow-hidden bg-white">
{menus.map((menu) => {
const isOpen = openMenus[menu.key] ?? false;
const hasChildren = (menu.sections?.length ?? 0) > 0;
const isCurrentGroup = (menu.sections ?? []).some(
(section) => section.id === activeId
);
return (
<div
key={menu.key}
>
<div className="flex items-center justify-between px-5 py-2">
{menu.href ? (
<Link
href={menu.href}
className={[
"text-[16px] font-medium tracking-[-0.02em]",
isCurrentGroup || menu.current
? "text-sky-500"
: "text-[#767676]",
].join(" ")}
>
{menu.label}
</Link>
) : (
<button
type="button"
onClick={() => {
if (hasChildren) toggleMenu(menu.key);
}}
className={[
"text-left text-[16px] font-medium tracking-[-0.02em]",
isCurrentGroup || menu.current
? "text-sky-500"
: "text-[#767676]",
].join(" ")}
>
{menu.label}
</button>
)}
{hasChildren && (
<button
type="button"
onClick={() => toggleMenu(menu.key)}
className={[
"ml-2 shrink-0",
isCurrentGroup || menu.current
? "text-sky-500"
: "text-[#767676]",
].join(" ")}
aria-label={`${menu.label} 드롭다운 토글`}
>
{isOpen ? (
<ChevronUp className="h-5 w-5" />
) : (
<ChevronDown className="h-5 w-5" />
)}
</button>
)}
</div>
{hasChildren && isOpen && (
<div className="relative px-5 pb-5">
<div className="absolute left-[31px] top-[6px] bottom-[14px] w-[2px] bg-[#CDD5DB]" />
<ul className="space-y-4 pl-2">
{menu.sections!.map((section) => {
const active = activeId === section.id;
return (
<li key={section.id}>
<button
type="button"
onClick={() => handleMove(section.id)}
className="flex items-start gap-3 text-left"
>
<span
className={[
"relative z-[1] mt-[6px] -ml-[2px] h-[11px] w-[11px] shrink-0 rounded-full border",
active
? "border-sky-500 bg-sky-500"
: "border-[#BFC7CF] bg-white",
].join(" ")}
/>
<span
className={[
"break-keep text-[14px] leading-[1.45] transition-colors",
active
? "text-[#4A4A4A]"
: "text-[#D1D7DE] hover:text-[#7B8794]",
].join(" ")}
>
{section.label}
</span>
</button>
</li>
);
})}
</ul>
</div>
)}
</div>
);
})}
</div>
</aside>
);
}
+96 -113
View File
@@ -1,4 +1,3 @@
// apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx
"use client";
import Image from "next/image";
@@ -9,167 +8,151 @@ type Slide = { src: string; alt?: string };
type Props = {
slides: Slide[];
className?: string;
maxWidthClassName?: string; // 예: "max-w-[760px]"
maxWidthClassName?: string;
autoPlayMs?: number;
imageHeightClassName?: string;
};
const PER_VIEW = 2;
export default function CaseImageSlider({
slides,
className = "",
maxWidthClassName = "max-w-[760px]",
maxWidthClassName = "max-w-[700px]",
autoPlayMs = 4500,
imageHeightClassName = "h-[340px]",
}: Props) {
const safeSlides = useMemo(() => slides.filter(Boolean), [slides]);
const [idx, setIdx] = useState(0);
const [page, setPage] = useState(0);
const [dragging, setDragging] = useState(false);
const [isHover, setIsHover] = useState(false);
const startX = useRef(0);
const deltaX = useRef(0);
const total = safeSlides.length;
const canSlide = total > 1;
const pageCount = Math.ceil(safeSlides.length / PER_VIEW);
const canSlide = pageCount > 1;
const go = (dir: number) => {
const goTo = (nextPage: number) => {
if (!canSlide) return;
setIdx((prev) => {
const next = prev + dir;
if (next < 0) return total - 1;
if (next >= total) return 0;
return next;
});
if (nextPage < 0) setPage(pageCount - 1);
else if (nextPage >= pageCount) setPage(0);
else setPage(nextPage);
};
// ← → 키보드 지원(선택)
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (!canSlide) return;
if (e.key === "ArrowLeft") go(-1);
if (e.key === "ArrowRight") go(1);
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [canSlide, total]);
const goNext = () => goTo(page + 1);
const goPrev = () => goTo(page - 1);
// 드래그(마우스/터치)로 넘기기
const onPointerDown = (e: React.PointerEvent) => {
// ✅ 자동롤링 (hover 시 정지 포함)
useEffect(() => {
if (!canSlide || dragging || isHover) return;
const timer = window.setInterval(() => {
setPage((prev) => (prev + 1 >= pageCount ? 0 : prev + 1));
}, autoPlayMs);
return () => window.clearInterval(timer);
}, [canSlide, dragging, isHover, pageCount, autoPlayMs]);
// ✅ swipe
const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (!canSlide) return;
setDragging(true);
startX.current = e.clientX;
deltaX.current = 0;
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
e.currentTarget.setPointerCapture(e.pointerId);
};
const onPointerMove = (e: React.PointerEvent) => {
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (!dragging) return;
deltaX.current = e.clientX - startX.current;
};
const onPointerUp = () => {
if (!dragging) return;
setDragging(false);
const threshold = 60; // 넘김 감도
if (deltaX.current > threshold) go(-1);
else if (deltaX.current < -threshold) go(1);
const threshold = 50;
if (deltaX.current > threshold) goPrev();
if (deltaX.current < -threshold) goNext();
deltaX.current = 0;
};
if (!total) return null;
if (!safeSlides.length) return null;
return (
<section className={className}>
<div className={`mx-auto ${maxWidthClassName}`}>
<div className="relative">
{/* 좌측 버튼 */}
<button
type="button"
onClick={() => go(-1)}
className="absolute left-[-60px] top-1/2 -translate-y-1/2 z-10
w-[80px] h-[80px] rounded-full border border-black/20 bg-white
grid place-items-center shadow-sm hover:bg-black/[0.03]"
>
<Image
src="/images/landing/icon_left_arrow.png"
alt="이전"
width={12}
height={22}
className="w-[12px] h-[22px]"
/>
</button>
{/* 우측 버튼 */}
<button
type="button"
onClick={() => go(1)}
className="absolute right-[-60px] top-1/2 -translate-y-1/2 z-10
w-[80px] h-[80px] rounded-full border border-black/20 bg-white
grid place-items-center shadow-sm hover:bg-black/[0.03]"
>
<Image
src="/images/landing/icon_right_arrow.png"
alt="다음"
width={12}
height={22}
className="w-[12px] h-[22px]"
/>
</button>
{/* 뷰포트 */}
<div
className="overflow-hidden select-none touch-pan-y cursor-grab active:cursor-grabbing"
onMouseEnter={() => setIsHover(true)} // ✅ hover 시작 → 정지
onMouseLeave={() => setIsHover(false)} // ✅ hover 끝 → 재시작
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
onPointerCancel={onPointerUp}
>
<div
className={[
"overflow-hidden",
"select-none touch-pan-y",
"cursor-grab active:cursor-grabbing",
"w-[583px] h-[400px] mx-auto",
].join(" ")}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
onPointerCancel={onPointerUp}
className="flex transition-transform duration-500 ease-out"
style={{
transform: `translateX(-${page * 100}%)`,
}}
>
<div
className="flex transition-transform duration-300 ease-out"
style={{ transform: `translateX(-${idx * 100}%)` }}
>
{safeSlides.map((s, i) => (
<div key={`${s.src}-${i}`} className="min-w-full">
<Image
src={s.src}
alt={s.alt ?? `slide-${i + 1}`}
width={583}
height={400}
className="w-full h-full object-contain"
style={{ width: "100%", height: "100%" }}
draggable={false}
/>
{Array.from({ length: pageCount }).map((_, pageIndex) => {
const pageSlides = safeSlides.slice(
pageIndex * PER_VIEW,
pageIndex * PER_VIEW + PER_VIEW
);
return (
<div
key={pageIndex}
className="grid min-w-full grid-cols-2 gap-5"
>
{pageSlides.map((slide, i) => (
<div
key={`${slide.src}-${i}`}
className={`relative ${imageHeightClassName} overflow-hidden rounded-[20px] bg-neutral-100`}
>
<Image
src={slide.src}
alt={slide.alt ?? `slide-${pageIndex * PER_VIEW + i + 1}`}
fill
className="object-cover"
sizes="(max-width: 768px) 50vw, 340px"
draggable={false}
/>
</div>
))}
</div>
))}
</div>
);
})}
</div>
</div>
{/* 도트 */}
{canSlide && (
<div className="mt-3 flex items-center justify-center gap-2">
{safeSlides.map((_, i) => {
const active = i === idx;
return (
<button
key={i}
type="button"
onClick={() => setIdx(i)}
aria-label={`${i + 1}번 슬라이드`}
className={[
"h-2 w-2 rounded-full",
active ? "bg-black/70" : "bg-black/20 hover:bg-black/35",
].join(" ")}
/>
);
})}
<div className="mt-5 flex items-center justify-center gap-[6px]">
{Array.from({ length: pageCount }).map((_, i) => (
<button
key={i}
type="button"
onClick={() => setPage(i)}
className={[
"h-[8px] w-[8px] rounded-full transition-all",
page === i ? "bg-[#18BCEB]" : "bg-[#D8DDE3]",
].join(" ")}
/>
))}
</div>
)}
</div>
</section>
);
}
}
+100 -70
View File
@@ -22,7 +22,7 @@ import Image from "next/image";
* - BUGFIX 2) 슬라이드 중(busy) clipWidth 변화로 트랙 스냅(-W) 덮어쓰지 않게 분리
*/
type TabKey = "rain" | "reuse" | "sewage" | "global";
type TabKey = "rain" | "greywater" | "sewage" | "global";
type CaseItem = {
id: string;
@@ -34,7 +34,7 @@ type CaseItem = {
const TABS: { key: TabKey; label: string }[] = [
{ key: "rain", label: "빗물 재이용 사례" },
{ key: "reuse", label: "중수 재이용 사례" },
{ key: "greywater", label: "중수 재이용 사례" },
{ key: "sewage", label: "하수 재이용 사례" },
{ key: "global", label: "해외 재이용 사례" },
];
@@ -43,113 +43,143 @@ const TABS: { key: TabKey; label: string }[] = [
const MOCK: Record<TabKey, CaseItem[]> = {
rain: [
{
id: "rain-0",
title: "경기도 수원시 빗물이용과 중수도",
desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…",
imageSrc: "/images/landing/rain_01.png",
href: "/reuse/business/rain/suwon",
id: "rain-1",
title: "경기도 수원시 빗물 재이용",
desc: "월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물공급기를 설치하여 축서기 도시 열섬효과 및 도로 비산먼지 저감",
imageSrc: "/images/landing/business/rain/suwon_01.png",
href: "/example#example-rain-suwon",
},
{
id: "rain-2",
title: "제주특별자치도 빗물 재이용",
desc: "강우시 비닐하우스에서 발생하는 대체수자원인 빗물을 순환 활용하고자 하는 사업으로, 지하수 사용 저감과 집중호우시 도로와 농경지 침수피해 예방",
imageSrc: "/images/landing/business/rain/jeju_01.png",
href: "/example#example-rain-jeju",
},
],
greywater: [
{
id: "greywater-1",
title: "경기도 안산시 중수 재이용",
desc: "수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현을 위해 안산시 중수 재이용 사업 추진, 공공기관 및 산업체에 중수 공급",
imageSrc: "/images/landing/business/greywater/no_img.jpg",
href: "/example#example-greywater-ansan",
},
{
id: "greywater-2",
title: "경기도 용인시 중수 재이용",
desc: "물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용과 지속가능성 도모를 위해 용인시 중수 재이용 사업 추진, 공공기관 및 산업체에 중수 공급",
imageSrc: "/images/landing/business/greywater/no_img.jpg",
href: "/example#example-greywater-yongin",
},
{
id: "greywater-3",
title: "경기도 평택시 중수 재이용",
desc: "수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현을 위해 평택시 중수 재이용 사업 추진, 공공기관 및 산업체에 중수 공급",
imageSrc: "/images/landing/business/greywater/no_img.jpg",
href: "/example#example-greywater-pyeongtaek",
},
// {
// id: "rain-2",
// title: "경기도 수원시 빗물이용과 중수도",
// desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…",
// imageSrc: "/images/landing/rain_01.png",
// href: "#",
// },
],
reuse: Array.from({ length: 6 }).map((_, i) => ({
id: `reuse-${i}`,
title: `중수 재이용 사례 ${i + 1}`,
desc: "설명 문구가 들어갑니다. (최대 2~3줄) …",
imageSrc: "/images/landing/sample_case.png",
href: "#",
})),
sewage: [
{
id: "sewage-1",
title: "경기도 파주시 하수 재이용",
desc: "운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에...",
imageSrc: "/images/landing/sewage_01.png",
href: "/reuse/business/sewage/paju",
title: "구리시 하수 재이용",
desc: "고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화",
imageSrc: "/images/landing/business/sewage/guri_01.png",
href: "/example#example-sewage-guri",
},
{
id: "sewage-2",
title: "충남보령 하수재이용",
desc: "보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업...",
imageSrc: "/images/landing/sewage_02.png",
href: "/reuse/business/sewage/bolyeong",
title: "부천시 하수재이용",
desc: "굴포하수처리시설 하수처리수 재이용수 생산시설 설치 운영을 통해 여월천 등 부천 관내 하천의 건천화 방지, 하천 생태계 보존 및 주민들에게 친수 공간 제공",
imageSrc: "/images/landing/business/sewage/bucheon_01.png",
href: "/example#example-sewage-bucheon",
},
{
id: "sewage-3",
title: "여수시 하수처리수 재이용",
desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로...",
imageSrc: "/images/landing/sewage_03.png",
href: "/reuse/business/sewage/yeosu",
title: "수원시 하수처리수 재이용",
desc: "황구지천 공공하수처리시설 하수처리수 재이용사업으로 황구 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전",
imageSrc: "/images/landing/business/sewage/suwon_01.png",
href: "/example#example-sewage-suwon",
},
{
id: "sewage-4",
title: "청주시 하수 재이용 시설",
desc: "청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO)...",
imageSrc: "/images/landing/sewage_04.png",
href: "/reuse/business/sewage/cheongju",
title: "파주시 하수 재이용 시설",
desc: "운정 및 금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주LCD산업단지 내 3개 업체에 공업용수를 공급(LG디스플레이, 희성전자, 엔비텍)",
imageSrc: "/images/landing/business/sewage/paju_01.png",
href: "/example#example-sewage-paju",
},
{
id: "sewage-5",
title: "수원시 황구지천 하천유지용수",
desc: "황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로...",
imageSrc: "/images/landing/sewage_05.png",
href: "/reuse/business/sewage/hwanggu",
title: "포항시 하수 재이용 시설",
desc: "포항시 내 공단 개발 확대로 지속적이고 안정적인 공업용수 공급 추진(포스코, 동국산업 등), 포항하수처리장 하수처리수를 재처리하여 공업용수로 공급",
imageSrc: "/images/landing/business/sewage/pohang_01.png",
href: "/example#example-sewage-pohang",
},
{
id: "sewage-6",
title: "구리시 도로자동청소시스템",
desc: "고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용...",
imageSrc: "/images/landing/sewage_06.png",
href: "/reuse/business/sewage/guri",
title: "울산광역시 하수 재이용 시설",
desc: "18년 갈수기시 낙동강 수질 악화로 울산석유화학단지 용수 공급 부족을 해소하기 위해 미포산단 내 한화케미칼 등 8개사 공업용수 공급",
imageSrc: "/images/landing/business/sewage/ulsan_01.png",
href: "/example#example-sewage-ulsan",
},
// ...Array.from({ length: 6 }).map((_, i) => ({
// id: `sewage-x-${i}`,
// title: `하수 재이용 사례 ${i + 4}`,
// desc: "설명 문구가 들어갑니다. (최대 2~3줄) …",
// imageSrc: "/images/landing/sample_case.png",
// href: "#",
// })),
{
id: "sewage-7",
title: "여수시 하수 재이용 시설",
desc: "수요자가 원하는 양질의 공업용수를 저렴하게 공급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선을 위한 여수국가산단 내 공업용수 공급(GS칼텍스, YNCC 등)",
imageSrc: "/images/landing/business/sewage/yeosu_01.png",
href: "/example#example-sewage-yeosu",
},
{
id: "sewage-8",
title: "보령시 하수 재이용 시설",
desc: "한국중부발전(주)의 보령 및 신보령화력발전소에 10,000㎥/일의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진, 하수처리수 재이용을 통한 수자원 절약 및 지역 물 부족 해소",
imageSrc: "/images/landing/business/sewage/boryeong_01.png",
href: "/example#example-sewage-boryeong",
},
{
id: "sewage-9",
title: "아산시 하수 재이용 시설",
desc: "신도시 개발에 따른 인구 급증으로 발생하는 하수를 재처리하여 탕정산업단지(삼성디스플레이)에 공업용수를 안정적으로 공급함으로써 지역기반 및 활성화를 도모",
imageSrc: "/images/landing/business/sewage/asan_01.png",
href: "/example#example-sewage-asan",
}
],
global: [
{
id: "global-1",
title: "해외 재이용 사례 개요",
desc: "2024년 하수도 통계 기준 15.6%이며, 제2차 물 재이용 기본 계획을 고려한 2030년 목표는 17%, 우리나라의 하수 처리 재이용률은 15% 수준...",
imageSrc: "/images/landing/overview_01.png",
href: "reuse/business/globalCase/overview",
desc: "전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐. 이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편",
imageSrc: "/images/landing/business/globalCase/overview_01.png",
href: "/example#example-global-overview",
},
{
id: "global-2",
title: "[대만] 국가 주도의 공업용수 공급",
desc: "대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑), 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능...",
imageSrc: "/images/landing/taiwan_01.png",
href: "/reuse/business/globalCase/taiwan",
desc: "수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결. 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등",
imageSrc: "/images/landing/business/globalCase/taiwan_01.png",
href: "/example#example-global-taiwan",
},
{
id: "global-3",
title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급",
desc: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망, 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급...",
imageSrc: "/images/landing/australia_01.png",
href: "/reuse/business/globalCase/australia",
title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화",
desc: "2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설. NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용",
imageSrc: "/images/landing/business/globalCase/singapore_01.png",
href: "/example#example-global-singapore",
},
{
id: "global-4",
title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화",
desc: "2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설, NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용...",
imageSrc: "/images/landing/singapore_01.png",
href: "/reuse/business/globalCase/singapore",
title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급",
desc: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망. 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급",
imageSrc: "/images/landing/business/globalCase/australia_01.png",
href: "/example#example-global-australia",
},
{
id: "global-5",
title: "[EU] 법률을 제정, EU국가에서 물 재이용 유도",
desc: "유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741), 2023년부터 모든 EU국가에서 시행...",
imageSrc: "/images/landing/eu_01.png",
href: "/reuse/business/globalCase/eu",
desc: "유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741), 2023년부터 모든 EU국가에서 시행",
imageSrc: "/images/landing/business/globalCase/eu_01.png",
href: "/example#example-global-eu",
},
],
};
@@ -249,7 +279,7 @@ export default function CaseTabsCarousel() {
// 탭별 마지막 그룹 인덱스 기억
const tabIndexRef = useRef<Record<TabKey, number>>({
rain: 0,
reuse: 0,
greywater: 0,
sewage: 0,
global: 0,
});
+33 -159
View File
@@ -1,181 +1,55 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import {
fetchAnonymousMenu,
findActiveTopMenu,
hasAnyChild,
isMenuActive,
type MenuItem,
} from "@/app/store/common";
export type LnbItem = {
export type LnbMenu = {
label: string;
href?: string;
children?: LnbItem[];
href: string;
};
function findOpenMenuCode(items: MenuItem[], pathname: string): number | null {
const matched = items.find((item) => {
if (isMenuActive(pathname, item)) return true;
return (item.children ?? []).some((child) => isMenuActive(pathname, child));
});
type Props = {
menus?: LnbMenu[];
className?: string;
};
return matched?.menuCd ?? null;
}
const defaultMenus: LnbMenu[] = [
{ label: "수요처", href: "/apply/dmdco" },
{ label: "수요정보등록", href: "/apply/dmdinfo" },
{ label: "수요신청현황", href: "/apply/status" },
];
export default function Lnb() {
export default function Lnb({ menus, className = "" }: Props) {
const pathname = usePathname();
const [menus, setMenus] = useState<MenuItem[]>([]);
const [openMenuCode, setOpenMenuCode] = useState<number | null>(null);
useEffect(() => {
let isMounted = true;
(async () => {
try {
const loaded = await fetchAnonymousMenu();
if (!isMounted) return;
console.log("Fetched LNB menus:", loaded);
setMenus(loaded);
} catch (error) {
console.error("Failed to load DN LNB menu", error);
if (!isMounted) return;
setMenus([]);
}
})();
return () => {
isMounted = false;
};
}, []);
const currentTopMenu = useMemo(
() => findActiveTopMenu(menus, pathname),
[menus, pathname],
);
const secondLevelMenus = useMemo(
() => currentTopMenu?.children ?? [],
[currentTopMenu],
);
// console.log("LNB render", { pathname, currentTopMenu, secondLevelMenus, menus });
useEffect(() => {
const defaultOpen = findOpenMenuCode(secondLevelMenus, pathname);
setOpenMenuCode(defaultOpen);
}, [pathname, secondLevelMenus]);
const navMenus = menus ?? defaultMenus;
return (
<aside
className={[
"fixed left-0 top-[83px]", // ✅ 화면 고정 + TopNav 아래로
"w-[260px] min-w-[260px] shrink-0",
"h-[calc(100vh-83px)]", // ✅ 남은 높이만큼
"overflow-y-auto", // ✅ LNB 내부 스크롤
"bg-white z-50", // ✅ 겹침 방지
].join(" ")}
>
{/* 타이틀 */}
<div className="flex h-[120px] items-center justify-center bg-gradient-to-r from-cyan-500 to-green-500 text-[40px] font-bold text-white">
{currentTopMenu?.menuKornNm?.trim().split(/\s+/).pop() ?? ""}
</div>
<aside className={`fixed right-2 top-[110px] z-50 w-[260px] ${className}`}>
<ul className="space-y-1">
{navMenus.map((menu) => {
const isActive =
pathname === menu.href ||
pathname.startsWith(`${menu.href}/`);
{/* 메뉴 */}
<ul>
{secondLevelMenus.map((item) => {
const hasChildren = hasAnyChild(item);
const active = isMenuActive(pathname, item);
if (!hasChildren) {
return (
<li key={item.menuCd}>
<li key={menu.href}>
<Link
href={item.urlPath || "#"}
className={[
"block border-b border-slate-300 px-5 py-4 text-center text-base font-semibold transition-colors",
active
? "cursor-default bg-white text-sky-500"
: "bg-[#F0F0F0] text-[#767676] hover:bg-white hover:text-sky-500",
].join(" ")}
href={menu.href}
className={`
block text-[16px] font-medium transition px-5 py-2
${
isActive
? "text-[#00AEEF]"
: "text-[#767676] hover:text-[#00AEEF]"
}
`}
>
{item.menuKornNm}
{menu.label}
</Link>
</li>
);
}
const isOpen = openMenuCode === item.menuCd;
const childMenus = item.children ?? [];
return (
<li key={item.menuCd}>
<button
type="button"
onClick={() =>
setOpenMenuCode((prev) => (prev === item.menuCd ? null : item.menuCd))
}
className={[
"flex w-full items-center justify-between border-b border-slate-300 px-5 py-6 text-base font-semibold",
active ? "bg-slate-100 text-slate-900" : "bg-slate-100 text-slate-500",
].join(" ")}
aria-expanded={isOpen}
>
<span className="w-full text-center">{item.menuKornNm}</span>
<span
aria-hidden
className={[
"ml-3 shrink-0 transition-transform duration-200",
isOpen ? "rotate-180" : "rotate-0",
].join(" ")}
>
<Image
src="/images/landing/drop_arrow.svg"
alt="menu toggle icon"
width={28}
height={28}
className="h-7 w-7"
/>
</span>
</button>
<div
className={[
"grid transition-[grid-template-rows] duration-200 ease-out",
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
].join(" ")}
>
<div className="overflow-hidden bg-white">
<ul>
{childMenus.map((child) => {
const childActive = isMenuActive(pathname, child);
return (
<li key={child.menuCd}>
<Link
href={child.urlPath || "#"}
className={[
"block border-b border-slate-200 bg-slate-50 px-5 py-4 pl-[60px] text-[18px] font-medium transition-colors",
childActive
? "cursor-default bg-white text-sky-500"
: "text-slate-500 hover:bg-white hover:text-sky-500",
].join(" ")}
>
<span className="mr-2 text-current">-</span>
{child.menuKornNm}
</Link>
</li>
);
})}
</ul>
</div>
</div>
</li>
);
})}
</ul>
})}
</ul>
</aside>
);
}
}
+16 -6
View File
@@ -1,3 +1,6 @@
/**
* 로그인 modal
*/
"use client";
import { useEffect, useId, useState } from "react";
@@ -50,6 +53,16 @@ export default function LoginModal({
);
const [submitting, setSubmitting] = useState(false);
const resetForm = () => {
setBizNo("");
setWorkerNo("");
setNoWorker(true);
setPw("");
setErrors({});
setTouched({});
setSubmitting(false);
};
useEffect(() => {
if (!open) return;
const onKeyDown = (e: KeyboardEvent) => {
@@ -61,13 +74,10 @@ export default function LoginModal({
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden"; // 모달 오픈 시 배경 스크롤 방지
// 모달 오픈 시 에러/터치 초기화
setErrors({});
setTouched({});
return () => {
window.removeEventListener("keydown", onKeyDown);
document.body.style.overflow = prev;
resetForm();
};
}, [open, onClose]);
@@ -151,7 +161,7 @@ export default function LoginModal({
const parsed = JSON.parse(sessionUser) as { dmdCoNm?: string };
onSuccess?.({ userName: parsed.dmdCoNm ?? "수요처" });
onClose();
router.refresh();
router.push("/apply/status");
}
} finally {
setSubmitting(false);
@@ -342,7 +352,7 @@ export default function LoginModal({
<button
type="button"
onClick={() => router.push(REGISTER_HREF)}
onClick={() => {onClose(); router.push(REGISTER_HREF);}}
className="shrink-0 rounded-[4px] h-[48px] px-10 bg-[#004A94] text-white text-[18px] hover:opacity-95"
>
수요처등록
+19 -8
View File
@@ -30,14 +30,16 @@ function useDebouncedValue<T>(value: T, delay = 250) {
export default function PostcodeModal({ open, onClose, onSelect }: Props) {
const [mounted, setMounted] = useState(false);
const [q, setQ] = useState("");
const [q, setQ] = useState(""); // 주소 입력값
const dq = useDebouncedValue(q, 250);
const [items, setItems] = useState<AddrItem[]>([]);
const [suggestions, setSuggestions] = useState<string[]>([]);
const [items, setItems] = useState<AddrItem[]>([]); // 검색 결과
const [suggestions, setSuggestions] = useState<string[]>([]); // 추천 주소 목록
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [searchMessage, setSearchMessage] = useState("");
const [searchMessage, setSearchMessage] = useState(""); // 검색 실패/형식 오류/보조 안내 문구를 담는 상태값
const [isInputFocused, setIsInputFocused] = useState(false);
const [searchedInput, setSearchedInput] = useState(""); // 주소 검색 입력란 저장
const inputRef = useRef<HTMLInputElement | null>(null);
@@ -51,6 +53,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
setTotal(0);
setLoading(false);
setSearchMessage("");
setIsInputFocused(false);
}, [open]);
useEffect(() => {
@@ -59,7 +62,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
const prevOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
setTimeout(() => inputRef.current?.focus(), 50);
setTimeout(() => inputRef.current?.focus(), 50); // 주소입력란에 focus()
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
@@ -131,6 +134,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
setItems(result.items);
setTotal(result.totalCount);
setSearchedInput(keyword);
} catch {
if (!alive) return;
setItems([]);
@@ -149,7 +153,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
};
}, [dq, open]);
const showSuggest = open && q.trim().length >= 1 && suggestions.length > 0;
const showSuggest = open && isInputFocused && q.trim().length >= 1 && suggestions.length > 0 && dq.trim() !== searchedInput;
if (!mounted || !open) return null;
@@ -190,6 +194,10 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
ref={inputRef}
value={q}
onChange={(e) => setQ(e.target.value)}
onFocus={() => setIsInputFocused(true)}
onBlur={() => {
setTimeout(() => setIsInputFocused(false), 100);
}}
placeholder="예) 테헤란로 152-1"
className={[
"w-full rounded-md border px-4 py-3 pr-12 text-[14px] outline-none",
@@ -226,8 +234,9 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
key={s}
type="button"
className="flex w-full items-center justify-between px-4 py-3 text-left text-[14px] hover:bg-slate-50"
onMouseDown={(e) => e.preventDefault()}
onClick={() => {
// ✅ 추천 클릭 → 입력값 채우고 드롭다운 닫기 + 포커스 유지
// 추천 클릭 → 입력값 초기화 드롭다운 닫기 + 포커스 유지
setQ(s);
setSuggestions([]);
requestAnimationFrame(() => inputRef.current?.focus());
@@ -263,7 +272,9 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {
<button
key={`${it.zipNo}-${it.roadAddr}`}
type="button"
onClick={() => onSelect(it)}
onClick={() => {setQ("");
setSuggestions([]);
onSelect(it)}}
className={[
"w-full rounded-lg border p-4 text-left transition",
"border-slate-200 hover:border-sky-300 hover:bg-sky-50/30",
+9 -15
View File
@@ -16,27 +16,21 @@ type Props = {
};
export default function SubPageHeader({
title,
// title,
crumbs,
note = "* 는 필수 값입니다.",
// note = "* 는 필수 값입니다.",
// underlineWidthClass = "w-40",
className = "",
wrapClassName = "",
// hideFooterLine = false,
hideGuideText = false,
// hideGuideText = false,
}: Props) {
return (
<section className={`text-black ${className}`}>
<section className={`text-black pr-[100px] ${className}`}>
<div className={`pb-5 ${wrapClassName}`}>
<div className="flex items-start justify-between gap-6">
{/* 좌측 타이틀 */}
<div className="inline-block">
<h1 className="relative z-10 px-2 text-[28px] font-extrabold tracking-tight text-slate-900 leading-none">{title}</h1>
<div className="relative top-[-7px] -z-1 h-4 bg-gradient-to-r from-sky-300 to-green-300" />
</div>
<div className="flex flex-col gap-5">
{/* 우측 브레드크럼 */}
<nav className="pt-2 text-sm text-black/70" aria-label="breadcrumb">
<nav className="text-sm text-black/70" aria-label="breadcrumb">
{crumbs.map((c, i) => (
<span key={`${c.label}-${i}`}>
{c.href ? (
@@ -49,15 +43,15 @@ export default function SubPageHeader({
{i < crumbs.length - 1 && <span className="mx-2">›</span>}
</span>
))}
</nav>
</nav>
</div>
{/* 하단 라인 + 안내문 */}
{!hideGuideText && (
{/* {!hideGuideText && (
<div className="mt-6 border-t border-black/10 pt-4 text-sm text-black/70">
<span className="text-red-400">*</span>
{note.replace("*", "")}
</div>
)}
)} */}
</div>
</section>
);
+4 -4
View File
@@ -12,10 +12,10 @@ import { useDmStore } from "@/app/store";
const LS_USER_KEY = "user";
const DEFAULT_NAV_ITEMS: Pick<MenuItem, "menuCd" | "menuKornNm" | "urlPath" | "newWndwYn">[] = [
{ menuCd: 1, menuKornNm: "물 재이용 소개", urlPath: "/reuse/intro/law", newWndwYn: "N" },
{ menuCd: 2, menuKornNm: "물 재이용 현황", urlPath: "/reuse/cases/rain", newWndwYn: "N" },
{ menuCd: 3, menuKornNm: "물 재이용 사례", urlPath: "/reuse/business/rain/suwon", newWndwYn: "N" },
{ menuCd: 4, menuKornNm: "수요신청", urlPath: "/reuse/apply/place", newWndwYn: "N" },
{ menuCd: 1, menuKornNm: "물 재이용 소개", urlPath: "/intro", newWndwYn: "N" },
{ menuCd: 2, menuKornNm: "물 재이용 현황", urlPath: "/status", newWndwYn: "N" },
{ menuCd: 3, menuKornNm: "물 재이용 사례", urlPath: "/example", newWndwYn: "N" },
{ menuCd: 4, menuKornNm: "수요신청", urlPath: "/apply/dmdco", newWndwYn: "N" },
];
export default function TopNav() {
@@ -94,7 +94,7 @@ export default function SearchInputText({
value={textValue || ""}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
className="w-full h-8 text-xs pl-2 pr-2 text-gray-600 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={`${label}을(를) 입력하세요`}
onClick={() => {
setTextValue("");
@@ -109,10 +109,10 @@ export default function SearchInputText({
searchList.map((item, index) => (
<div
key={index}
className={`w-full cursor-pointer px-3 py-2 text-left text-xs ${
className={`w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl ${
highlightIndex === index
? "bg-blue-50"
: "hover:bg-gray-50"
? "bg-blue-100"
: "hover:bg-gray-100"
}`}
onClick={() => handleSuggestionClick(item.bscFctNm)}
role="button"
@@ -127,7 +127,7 @@ export default function SearchInputText({
</div>
))
) : (
<div className="text-xs w-full px-3 py-2 hover:bg-gray-50 cursor-pointer text-gray-500">
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
검색 결과가 없습니다.
</div>
)}
@@ -62,51 +62,50 @@ export function SearchBar({
);
return (
<div className="flex justify-center w-full px-4 py-2">
<div className="flex items-center w-auto shadow-md px-4 py-2 space-x-2 bg-white/95 backdrop-blur-sm rounded-lg text-sm border border-gray-200">
<div className="flex justify-center w-full px-4 py-3">
<div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
<SelectBox
labelClass="w-[50px] text-xs font-medium text-gray-700"
className="w-[110px]"
labelClass="w-[40px]"
className="w-[200px]"
label="시도"
selectClass="rounded bg-white text-xs h-8"
selectClass="rounded-lg bg-white"
selectArray={Common.selectSidoData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue || Common.selectSidoData[0]?.val || ""}
/>
<SelectBox
labelClass="w-[70px] text-xs font-medium text-gray-700"
className="w-[110px]"
labelClass="w-[65px]"
className="w-[200px]"
label="시군구"
selectClass="rounded bg-white text-xs h-8"
selectClass="rounded-lg bg-white"
selectArray={filteredSigunData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<Label className="text-gray-700 text-xs font-medium pl-2 pr-1">하수재이용시설</Label>
<div className="w-[180px]">
<SearchInputText
label="시설명 입력"
initValue={undefined}
setValue={(searchNm?: string) => {
let sggOrCity = "";
if (searchNm) {
if (sigunValue === "0" || sigunValue === "") {
sggOrCity = sidoValue;
} else {
sggOrCity = sigunValue;
}
findRuseFclt(sggOrCity, searchNm);
<Label className="text-gray-600">하수재이용시설</Label>
<SearchInputText
label="시설명"
initValue={undefined}
setValue={(searchNm?: string) => {
let sggOrCity = "";
if (searchNm) {
if (sigunValue === "0" || sigunValue === "") {
sggOrCity = sidoValue;
} else {
sggOrCity = sigunValue;
}
}}
setRtnData={(rtnData: any) => {
setHasuPk(rtnData);
}}
searchList={map.ruseFcltList || []}
/>
</div>
findRuseFclt(sggOrCity, searchNm);
}
}}
setRtnData={(rtnData: any) => {
setHasuPk(rtnData);
}}
searchList={map.ruseFcltList || []}
/>
<Button
color="sky"
className="ml-2 text-white !h-8 !py-0 !px-3 !text-xs font-semibold rounded"
className="ml-2 text-white"
size="sm"
onClick={() => {
onApply();
}}
+1 -1
View File
@@ -8,7 +8,7 @@ const serverApiUrl = process.env.NEXT_PUBLIC_CS_SERVER_API_URL;
const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
const GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL || "";
const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
// const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || "";
const AdressKey = process.env.NEXT_PRIVATE_WR_ADDRESS_KEY || "";
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 364 KiB

Some files were not shown because too many files have changed in this diff Show More