dm: 물재이용 현황 - 빗물

This commit is contained in:
2026-04-29 21:54:08 +09:00
parent 73e1499583
commit 9b4d373f7d
23 changed files with 3848 additions and 345 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>
);
}
@@ -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,6 +1,7 @@
"use client";
import { ReactNode, useEffect, useMemo, useState } from "react";
import type { ReactNode } from "react";
import { useEffect, useMemo, useState } from "react";
import { usePathname } from "next/navigation";
import Lnb from "../_components/Lnb";
import BusinessSubNav from "../_components/BusinessSubNav";
@@ -67,4 +68,4 @@ export default function BusinessLayout({ children }: { children: ReactNode }) {
</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,16 @@
"use client";
import React from "react";
import { useEffect, useMemo } from "react";
// import SubPageHeader from "@/components/SubPageHeader";
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 }) {
@@ -17,15 +27,6 @@ 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]",
@@ -34,7 +35,112 @@ const tableStyles = {
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">
자료: 환경부(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>
);
});
};
const title = "빗물이용시설(현황)";
return (
@@ -57,80 +163,21 @@ export default function RainStatusSection() {
<table className={tableStyles.table}>
<thead>
<tr>
<th
rowSpan={2}
className={`${tableStyles.th} ${tableStyles.headerBg}`}
style={{ width: "160px" }}
>
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>
구분
</th>
<th
colSpan={6}
className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}
>
<th colSpan={rainyYears.length || 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>
{renderPartHeaders(rainyYears, headerColors6)}
</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) => (
{rainyByBildTpYears.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>
))}
{renderBuildingUsageCells(row, rainyByBildTpYearsHeader)}
</tr>
))}
</tbody>
@@ -155,119 +202,19 @@ export default function RainStatusSection() {
<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>
<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>
{renderPartHeaders(partBildTpHeader, headerColors8)}
</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>
);
})}
{rainyByUsgBildTp.map((row, idx) => (
<tr key={`${row.USG_NM}-${idx}`} className="bg-white">
{renderRainyByUsgBildTpCells(row)}
</tr>
))}
</tbody>
@@ -292,80 +239,17 @@ export default function RainStatusSection() {
<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>
<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>
{renderPartHeaders(rainyYears, headerColors6)}
</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) => (
{rainyByUsgYears.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>
))}
{renderBuildingUsageCells(row, rainyByUsgYearsHeader)}
</tr>
))}
</tbody>
+130 -88
View File
@@ -1,6 +1,15 @@
"use client";
import { useEffect, useMemo } from "react";
import SubPageHeader from "@/components/SubPageHeader";
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 }) {
@@ -17,14 +26,7 @@ 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",
@@ -34,7 +36,114 @@ const tableStyles = {
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 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>
);
});
};
return (
<div className="bg-white">
<SubPageHeader
@@ -63,38 +172,18 @@ export default function RainwaterStatusPage() {
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} style={{ width: "160px" }}>
구분
</th>
<th colSpan={6} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>
<th colSpan={rainyYears.length || 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>
{renderPartHeaders(rainyYears, headerColors6)}
</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) => (
{rainyByBildTpYears.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>
))}
{renderBuildingUsageCells(row, rainyByBildTpYearsHeader)}
</tr>
))}
</tbody>
@@ -108,7 +197,7 @@ export default function RainwaterStatusPage() {
{/* 2) 빗물이용시설의 용도별 분포 (2023년 기준) */}
<section id="status-rain-purpose-distribution" className="scroll-mt-[120px]">
<SectionTitle>빗물이용시설의 용도별 분포 (2023년 기준)</SectionTitle>
<SectionTitle>빗물이용시설의 용도별 분포 ({selectYear}년 기준)</SectionTitle>
<div className="mt-8">
<div className={tableStyles.container}>
<table className={tableStyles.table}>
@@ -120,40 +209,13 @@ export default function RainwaterStatusPage() {
<th colSpan={8} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`}>빗물이용량 (천m³/년)</th>
</tr>
<tr>
<th className={`${tableStyles.th} bg-[#01ACE5] text-white py-2 text-[13px]`} style={{ width: "84px" }}>총사용량</th>
<th className={`${tableStyles.th} bg-[#10AED0] text-white py-2 text-[13px]`} style={{ width: "84px" }}>공공시설</th>
<th className={`${tableStyles.th} bg-[#20B0BA] text-white py-2 text-[13px]`} style={{ width: "84px" }}>공동주택</th>
<th className={`${tableStyles.th} bg-[#2FB2A5] text-white py-2 text-[13px]`} style={{ width: "84px" }}>대규모점포</th>
<th className={`${tableStyles.th} bg-[#3FB58F] text-white py-2 text-[13px]`} style={{ width: "84px" }}>체육시설</th>
<th className={`${tableStyles.th} bg-[#4EB77A] text-white py-2 text-[13px]`} style={{ width: "84px" }}>학교</th>
<th className={`${tableStyles.th} bg-[#5EB964] text-white py-2 text-[13px]`} style={{ width: "84px" }}>골프장</th>
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white py-2 text-[13px]`} style={{ width: "84px" }}>기타</th>
{renderPartHeaders(partBildTpHeader, headerColors8)}
</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>
);
})}
{rainyByUsgBildTp.map((row, idx) => (
<tr key={`${row.USG_NM}-${idx}`} className="bg-white">
{renderRainyByUsgBildTpCells(row)}
</tr>
))}
</tbody>
@@ -177,33 +239,13 @@ export default function RainwaterStatusPage() {
<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>
{renderPartHeaders(rainyYears, headerColors6)}
</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) => (
{rainyByUsgYears.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>
))}
{renderBuildingUsageCells(row, rainyByUsgYearsHeader)}
</tr>
))}
</tbody>
@@ -215,4 +257,4 @@ export default function RainwaterStatusPage() {
</div>
</div>
);
}
}
+23 -1
View File
@@ -23,4 +23,26 @@ export default function TransValueFormat({value='', format=''}: CommonProps) {
: String(value ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</>
: <>{value}</>
)
}
}
export const formatUsage = (
value: string | number,
unitSize: number = 1,
decimalPlaces: number = 0
) => {
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,
});
};
+243
View File
@@ -0,0 +1,243 @@
import { fetchType, getFetch } from "@/components/ui/api";
import type {
TableHeaderType, DmStatusBildTpYearsType, DmStatusUsgBildTpType, DmStatusUsgYearsType,
DmStatusLawYearsType, DmStatusSidoYearsType,
} from "./type";
import { rainyByBildTpYearsHeader, rainyByUsgBildTpHeader, rainyByUsgYearsHeader } from "./type";
import { rswtrByLawYearsHeader, rswtrByBildTpYearsHeader, rswtrByUsgYearsHeader } from "./type";
import { pbnstByLawYearsHeader, pbnstByUsgYearsHeader, pbnstByCpcYearsHeader, pbnstBySidoYearsHeader } 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년간
getPbnstBySidoYears: () => 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[];
rswtrByLawYears: DmStatusLawYearsType[];
rswtrByBildTpYears: DmStatusBildTpYearsType[];
rswtrByUsgYears: DmStatusUsgYearsType[];
},
pbnst: {
pbnstYears: string[];
pbnstByLawYearsHeader: TableHeaderType[];
pbnstByUsgYearsHeader: TableHeaderType[];
pbnstByCpcYearsHeader: TableHeaderType[];
pbnstBySidoYearsHeader: TableHeaderType[];
pbnstByLawYears: DmStatusBildTpYearsType[];
pbnstByUsgYears: DmStatusBildTpYearsType[];
pbnstByCpcYears: DmStatusBildTpYearsType[];
pbnstBySidoYears: DmStatusBildTpYearsType[];
}
};
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,
rswtrByLawYears: [],
rswtrByBildTpYears: [],
rswtrByUsgYears: [],
},
pbnst: {
pbnstYears: ["2019", "2020", "2021", "2022", "2023", "2024"],
pbnstByLawYearsHeader: pbnstByLawYearsHeader,
pbnstByUsgYearsHeader: pbnstByUsgYearsHeader,
pbnstByCpcYearsHeader: pbnstByCpcYearsHeader,
pbnstBySidoYearsHeader: pbnstBySidoYearsHeader,
pbnstByLawYears: [],
pbnstByUsgYears: [],
pbnstByCpcYears: [],
pbnstBySidoYears: [],
}
};
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;
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 : [];
set((state: DmStatusType) => ({
...state,
pbnst: {
...state.pbnst,
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.대민 하수재이용현황 시도별
getPbnstBySidoYears: async () => {
const res = await getFetch(fetchType.WR,"/dmStatus/pbnstBySidoYears");
const pbnstBySidoYears = Array.isArray(res?.data) ? res.data : [];
set((state: DmStatusType) => ({
...state,
pbnst: {
...state.pbnst,
pbnstBySidoYears,
}
}));
},
};
};
+215
View File
@@ -0,0 +1,215 @@
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;
};
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;
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 DmStatusSidoYearsType = {
COL: string; // 컬럼 구분
MAXYR?: string; // 최종 확정 연도
Y1: string | number;
Y2: string | number;
Y3: string | number;
Y4: string | number;
Y5: string | number;
Y6: string | 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: "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 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 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: "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 pbnstByUsgYearsHeader: 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 pbnstByCpcYearsHeader: 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 pbnstBySidoYearsHeader: 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 },
];
+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;