592 lines
20 KiB
TypeScript
592 lines
20 KiB
TypeScript
/**
|
|
* 하수처리수
|
|
*/
|
|
"use client";
|
|
|
|
import React from "react";
|
|
import Image from "next/image";
|
|
import { useEffect } from "react";
|
|
import { useDmStore } from "@/app/store";
|
|
import { formatUsage } from "@/app/common/transValueFormat";
|
|
import type {
|
|
DmStatusLawYearsType,
|
|
DmStatusBildTpYearsType,
|
|
DmStatusUsgYearsType,
|
|
DmStatusSidoUsgType,
|
|
TableHeaderType,
|
|
} from "@/app/store/dm-status/type";
|
|
import { Col2Name } from "@/app/store/dm-status/type";
|
|
|
|
function SectionTitle({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
<span className="h-5 w-[3px] bg-sky-500" />
|
|
<h3 className="text-[24px] font-bold text-neutral-900">{children}</h3>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Hr({ className = "" }: { className?: string }) {
|
|
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
|
|
}
|
|
|
|
function SewageIntroSummary() {
|
|
const items = [
|
|
{
|
|
icon: "/images/landing/icon_cycle_water.png",
|
|
title: "안정적인 재이용률 유지",
|
|
desc: (
|
|
<>
|
|
연간 약 1,136백만m³의 하수처리수가 재이용되고 있으며,
|
|
<br/>
|
|
약 15%에 달하는 높은 재이용률을 통해
|
|
<br/>
|
|
수자원 선순환 체계를 구축하고 있습니다.
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
icon: "/images/landing/icon_water_flow.png",
|
|
title: "메마른 하천을 살리는 물",
|
|
desc: (
|
|
<>
|
|
재이용되는 물의 가장 많은 부분이 하천유지용수로 사용되어
|
|
<br/>
|
|
도심 속 하천의 생태계를 보호하고 쾌적한 수변 환경을 조성합니다.
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
icon: "/images/landing/icon_location.png",
|
|
title: "지역별 균형 있는 활용",
|
|
desc: (
|
|
<>
|
|
전국적으로 고르게 시설이 확충되고 있으며, 특히 경기 지역을 중심으로
|
|
<br/>
|
|
대규모 공업용수 공급망을 구축하여
|
|
<br/>
|
|
지역 경제와 환경 보호를 동시에 실현하고 있습니다.
|
|
</>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="mb-[70px] px-[160px]">
|
|
<div className="grid grid-cols-[1fr_400px] items-center gap-0">
|
|
{/* 좌측 */}
|
|
<div>
|
|
<h3 className="text-[40px] font-extrabold leading-[1.35] tracking-[-0.14em] text-neutral-900">
|
|
버려지는 하수에서 찾는 새로운 가치,
|
|
<br />
|
|
<span className="inline-block bg-gradient-to-b from-[#6DBB4F] to-[#01ACE5] bg-clip-text text-transparent">
|
|
하수처리수 재이용
|
|
</span>
|
|
이 도시를 숨쉬게 합니다.
|
|
</h3>
|
|
|
|
<p className="mt-5 text-[16px] leading-[1.8] text-neutral-600">
|
|
하수처리장에서 정화된 물은 하천의 수량을 조절하고
|
|
<br />
|
|
산업 현장에 용수를 공급하는 소중한 자원입니다.
|
|
</p>
|
|
|
|
<div className="mt-10 space-y-8">
|
|
{items.map((item) => (
|
|
<div key={item.title} className="flex gap-4 items-center">
|
|
<div className="mt-1 flex h-12 w-12 shrink-0 items-center justify-center">
|
|
<Image
|
|
src={item.icon}
|
|
alt=""
|
|
width={48}
|
|
height={48}
|
|
className="object-contain"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<strong className="block text-[18px] font-extrabold text-neutral-900">
|
|
{item.title}
|
|
</strong>
|
|
<p className="mt-2 text-[16px] leading-[1.7] text-neutral-600">
|
|
{item.desc}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 우측 이미지 */}
|
|
<div className="flex items-center justify-center">
|
|
<Image
|
|
src="/images/landing/sewage_status_img.png"
|
|
alt="하수처리수 재이용 개념도"
|
|
width={420}
|
|
height={360}
|
|
className="h-auto w-full max-w-[420px] object-contain"
|
|
priority
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const tableStyles = {
|
|
container: "w-[1000px] mx-auto overflow-hidden",
|
|
table: "w-full table-fixed border-collapse text-center text-[16px]",
|
|
th: "border border-[#858F98] py-3 font-bold text-neutral-800",
|
|
td: "border border-[#858F98] py-3 text-neutral-700 font-medium break-keep",
|
|
headerBg: "bg-[#E3E7EA]",
|
|
};
|
|
|
|
const headerColors6 = [
|
|
"bg-[#01ACE5]",
|
|
"bg-[#17AFC7]",
|
|
"bg-[#2CB2A9]",
|
|
"bg-[#42B58B]",
|
|
"bg-[#57B86D]",
|
|
"bg-[#81C784]",
|
|
];
|
|
|
|
export default function SewageStatusSection() {
|
|
const getPbnstByLawYears = useDmStore((state) => state.getPbnstByLawYears);
|
|
const getPbnstByUsgYears = useDmStore((state) => state.getPbnstByUsgYears);
|
|
const getPbnstByCpcYears = useDmStore((state) => state.getPbnstByCpcYears);
|
|
const getPbnstBySido = useDmStore((state) => state.getPbnstBySido);
|
|
const pbnstYears = useDmStore((state) => state.pbnst.pbnstYears);
|
|
const selectYear = pbnstYears.slice(-1)[0];
|
|
const pbnstByLawYearsHeader = useDmStore((state) => state.pbnst.pbnstByLawYearsHeader);
|
|
const pbnstByLawYears = useDmStore((state) => state.pbnst.pbnstByLawYears);
|
|
const pbnstByUsgYearsHeader = useDmStore((state) => state.pbnst.pbnstByUsgYearsHeader);
|
|
const pbnstByUsgYears = useDmStore((state) => state.pbnst.pbnstByUsgYears);
|
|
const pbnstByCpcYearsHeader = useDmStore((state) => state.pbnst.pbnstByCpcYearsHeader);
|
|
const pbnstByCpcYears = useDmStore((state) => state.pbnst.pbnstByCpcYears);
|
|
const pbnstBySidoHeader = useDmStore((state) => state.pbnst.pbnstBySidoHeader);
|
|
const pbnstBySido = useDmStore((state) => state.pbnst.pbnstBySido);
|
|
|
|
// 출처 텍스트
|
|
function SourceText() {
|
|
return (
|
|
<p className="mt-7 text-center text-[13px] text-neutral-500">
|
|
자료: 하수도정보시스템 {selectYear} 하수도 통계
|
|
</p>
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
void getPbnstByLawYears();
|
|
void getPbnstByUsgYears();
|
|
void getPbnstByCpcYears();
|
|
void getPbnstBySido();
|
|
}, [getPbnstByLawYears, getPbnstByUsgYears, getPbnstByCpcYears, getPbnstBySido]);
|
|
|
|
const renderPartHeaders = (headers: string[], headerColors: string[]) => {
|
|
return headers.map((header, index) => (
|
|
<th
|
|
key={header}
|
|
className={`${tableStyles.th} ${headerColors[index] ?? "bg-[#81C784]"} text-white`}
|
|
>
|
|
{header}
|
|
</th>
|
|
));
|
|
};
|
|
|
|
const renderCells = (
|
|
rowIdx: number,
|
|
row: DmStatusLawYearsType,
|
|
data: DmStatusLawYearsType[],
|
|
headers: TableHeaderType[],
|
|
unitSize: number = 1, // 단위 변경 1000 인 경우, 100,000 -> 100 으로 변경
|
|
) => {
|
|
const rowData = row as Record<string, string | number>;
|
|
const currentCol1 = String(rowData["COL1"] ?? "");
|
|
|
|
// 이전 값
|
|
const prevCol1 = rowIdx > 0 ? data[rowIdx - 1]?.COL1 : null;
|
|
|
|
// 현재 그룹의 rowspan 계산
|
|
const getRowSpan = () => {
|
|
let count = 0;
|
|
for (let i = rowIdx; i < data.length; i++) {
|
|
if (data[i]?.COL1 === currentCol1) count++;
|
|
else break;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
return headers.map((header, index) => {
|
|
const value = rowData[header.id];
|
|
|
|
// COL1 처리 (rowspan 핵심)
|
|
if (header.id === "COL1") {
|
|
// 이전과 같으면 렌더링 안함
|
|
if (currentCol1 === prevCol1) return null;
|
|
|
|
return (
|
|
<td
|
|
key={`${rowIdx}-${header.id}`}
|
|
rowSpan={getRowSpan()}
|
|
className={`${tableStyles.td} !font-bold !text-neutral-900`}
|
|
>
|
|
{value}
|
|
</td>
|
|
);
|
|
} else if (header.id === "COL2") {
|
|
return (
|
|
<td
|
|
key={`${rowIdx}-${header.id}`}
|
|
className={`${tableStyles.td} !font-bold !text-neutral-900`}
|
|
dangerouslySetInnerHTML={{ __html: Col2Name[value as string] ?? "" }}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<td
|
|
key={`${rowIdx}-${header.id}`}
|
|
className={`${tableStyles.td} ${[0, 2, 4].includes(index) ? "bg-[#EAF5F5]" : ""}`}
|
|
>
|
|
{(String(rowData["COL2"]).indexOf("CNT") > -1)
|
|
? Number(value).toLocaleString("ko-KR")
|
|
: ((String(rowData["COL2"]).indexOf("REUSERT") > -1)
|
|
? formatUsage(value as string | number, 1, 1)
|
|
: formatUsage(value as string | number, unitSize, 1))
|
|
}
|
|
</td>
|
|
);
|
|
});
|
|
};
|
|
|
|
const renderPbnstUsgCells = (
|
|
rowIdx: number,
|
|
row: DmStatusBildTpYearsType | DmStatusUsgYearsType,
|
|
headers: TableHeaderType[]
|
|
) => {
|
|
const rowData = row as Record<string, string | number>;
|
|
const isLastInGroup = rowIdx === 8 || rowIdx === 17;
|
|
|
|
const getType = (colNm:string) => {
|
|
const last3 = colNm.slice(-3); // 끝 3자리
|
|
const prefix = last3[0]; // S 또는 D
|
|
|
|
if (prefix === 'S') {
|
|
const num = parseInt(last3.slice(1), 10); // 숫자
|
|
if (num < 8) return 'red-item';
|
|
if (num >= 8) return 'blue-item';
|
|
}
|
|
|
|
return ''; // 조건 안 맞으면 빈값 (필요시 기본값)
|
|
};
|
|
|
|
return headers.map((header, index) => {
|
|
const value = rowData[header.id];
|
|
const colNm = String(rowData["COL_NM"] ?? "");
|
|
const type = getType(colNm);
|
|
const isSubItem = type === "red-item" || type === "blue-item";
|
|
|
|
if (index === 0) {
|
|
// console.log(header.id, value);
|
|
return (
|
|
<td key={`${rowIdx}-${header.id}`} className={`${tableStyles.td} relative`}>
|
|
{isSubItem ? (
|
|
<>
|
|
<div
|
|
className={`absolute left-[1px] w-[16px] bg-white z-10 top-[-2px] ${
|
|
isLastInGroup ? "bottom-[0px]" : "bottom-[-2px]"
|
|
}`}
|
|
/>
|
|
<div
|
|
className={`absolute left-[16px] w-[4px] z-20 ${
|
|
type === "red-item" ? "bg-[#FF8B8B]" : "bg-[#4ECBFF]"
|
|
} top-[-2px] ${
|
|
isLastInGroup ? "bottom-[0px]" : "bottom-[-2px]"
|
|
}`}
|
|
/>
|
|
<span className="text-[#4f4f4f] font-bold block w-full text-center relative z-30 translate-x-[8px]">
|
|
{value}
|
|
</span>
|
|
</>
|
|
) : (
|
|
<span className="font-bold text-neutral-900 block w-full text-center">
|
|
{value}
|
|
</span>
|
|
)}
|
|
</td>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<td
|
|
key={`${rowIdx}-${header.id}`}
|
|
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[0, 2, 4].includes(index) ? "bg-[#EAF5F5]" : ""}`}
|
|
>
|
|
{formatUsage(value as string | number, 1000, 1)}
|
|
</td>
|
|
);
|
|
|
|
});
|
|
};
|
|
|
|
const renderSidoUsgCells = (
|
|
rowIdx: number,
|
|
row: DmStatusSidoUsgType,
|
|
headers: TableHeaderType[]
|
|
) => {
|
|
const rowData = row as Record<string, string | number>;
|
|
|
|
return headers.map((header, index) => {
|
|
let value = rowData[header.id];
|
|
value = (index === 0||index ===9) ? value : formatUsage(value as string | number, 1000, 1);
|
|
value = value === "0.0"? "-" : value;
|
|
|
|
return (
|
|
<td
|
|
key={`${rowIdx}-${header.id}`}
|
|
className={`${tableStyles.td} ${index === 0 ? "!font-bold !text-neutral-900" : ""} ${[1, 9].includes(index) ? "bg-[#EAF5F5]" : ""}`}
|
|
>
|
|
{value}
|
|
</td>
|
|
);
|
|
});
|
|
};
|
|
|
|
const title = "하수처리수 재이용시설(현황)";
|
|
|
|
return (
|
|
<div className="bg-white">
|
|
{/* 상단 타이틀 */}
|
|
<div className="inline-block">
|
|
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
|
|
<span className="relative z-10">{title}</span>
|
|
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
|
|
</h2>
|
|
</div>
|
|
|
|
<div className="mt-[70px] pb-[120px]">
|
|
{/* 1) 연도별 하수처리수 재이용 현황 */}
|
|
<section>
|
|
{/* 인트로 */}
|
|
<SewageIntroSummary />
|
|
|
|
<div id="status-treated-year" className="scroll-mt-[120px]">
|
|
<SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle>
|
|
<div className="mt-10">
|
|
<div className={tableStyles.container}>
|
|
<table className={tableStyles.table}>
|
|
<colgroup>
|
|
<col style={{ width: "130px" }} />
|
|
<col style={{ width: "180px" }} />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th
|
|
colSpan={2}
|
|
className={`${tableStyles.th} ${tableStyles.headerBg}`}
|
|
>
|
|
구분
|
|
</th>
|
|
{renderPartHeaders(pbnstYears, headerColors6)}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{pbnstByLawYears.map((row, idx, arr) => (
|
|
<tr key={idx}>
|
|
{renderCells(idx, row, arr, pbnstByLawYearsHeader, 1000)}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<SourceText />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<Hr className="my-11" />
|
|
|
|
{/* 2) 용도별 하수처리수 재이용 현황 */}
|
|
<section id="status-treated-usage" className="scroll-mt-[120px]">
|
|
<SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle>
|
|
|
|
<div className="mt-8">
|
|
<div className={tableStyles.container}>
|
|
<div className="mb-2 text-right text-[13px] text-neutral-600">
|
|
(단위: 백만m³/일)
|
|
</div>
|
|
<table className={tableStyles.table}>
|
|
<colgroup>
|
|
<col style={{ width: "180px" }} />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th className={`${tableStyles.th} ${tableStyles.headerBg}`}>
|
|
구분
|
|
</th>
|
|
{renderPartHeaders(pbnstYears, headerColors6)}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{pbnstByUsgYears.map((row, idx) => (
|
|
<tr key={idx} className="bg-white">
|
|
{renderPbnstUsgCells(idx, row, pbnstByUsgYearsHeader)}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<SourceText />
|
|
</div>
|
|
</section>
|
|
|
|
<Hr className="my-11" />
|
|
|
|
{/* 3) 하수처리시설 설치 및 이용 현황 */}
|
|
<section id="status-treated-facility" className="scroll-mt-[120px]">
|
|
<SectionTitle>하수처리시설 설치 및 이용 현황</SectionTitle>
|
|
|
|
<div className="mt-8">
|
|
<div className={tableStyles.container}>
|
|
<table className={tableStyles.table}>
|
|
<colgroup>
|
|
<col style={{ width: "130px" }} />
|
|
<col style={{ width: "160px" }} />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th
|
|
colSpan={2}
|
|
className={`${tableStyles.th} ${tableStyles.headerBg}`}
|
|
>
|
|
구분
|
|
</th>
|
|
{renderPartHeaders(pbnstYears, headerColors6)}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{pbnstByCpcYears.map((row, idx, arr) => (
|
|
<tr key={idx}>
|
|
{renderCells(idx, row, arr, pbnstByCpcYearsHeader, 10000)}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<SourceText />
|
|
</div>
|
|
</section>
|
|
|
|
<Hr className="my-11" />
|
|
|
|
{/* 4) 시도별 하수처리수 재이용 현황 */}
|
|
<section
|
|
id="status-treated-region"
|
|
className="scroll-mt-[120px]"
|
|
>
|
|
<SectionTitle>시도별 하수처리수 재이용 현황</SectionTitle>
|
|
|
|
<div className="mt-8">
|
|
<div className={tableStyles.container}>
|
|
<div className="mb-2 text-right text-[13px] text-neutral-600">
|
|
(단위: 백만m³/일)
|
|
</div>
|
|
|
|
<table className={tableStyles.table}>
|
|
<colgroup>
|
|
<col style={{ width: "160px" }} />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
<col />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`}> 시도 </th>
|
|
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
|
|
연간<br />하수<br />처리량
|
|
</th>
|
|
<th colSpan={7} className={`${tableStyles.th} ${tableStyles.headerBg} py-2`} >
|
|
재이용현황
|
|
</th>
|
|
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
|
|
재이용률<br />(%)
|
|
</th>
|
|
<th rowSpan={3} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
|
|
비고
|
|
</th>
|
|
</tr>
|
|
<tr>
|
|
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
|
|
계
|
|
</th>
|
|
<th rowSpan={2} className={`${tableStyles.th} ${tableStyles.headerBg}`} >
|
|
장내
|
|
</th>
|
|
<th colSpan={5} className={`${tableStyles.th} ${tableStyles.headerBg} py-1`} >
|
|
장외
|
|
</th>
|
|
</tr>
|
|
<tr>
|
|
<th className={`${tableStyles.th} bg-[#01ACE5] text-white py-2 text-[16px]`}>
|
|
소계
|
|
</th>
|
|
<th className={`${tableStyles.th} bg-[#1FB1C2] text-white py-2 text-[13px]`}>
|
|
하천유지용수
|
|
</th>
|
|
<th className={`${tableStyles.th} bg-[#3EB59F] text-white py-2 text-[16px]`}>
|
|
공업용수
|
|
</th>
|
|
<th className={`${tableStyles.th} bg-[#5DB97C] text-white py-2 text-[16px]`}>
|
|
농업용수
|
|
</th>
|
|
<th className={`${tableStyles.th} bg-[#6DBB4F] text-white py-2 text-[16px]`}>
|
|
기타용수
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{pbnstBySido.map((row, idx) => (
|
|
<tr key={idx} className="bg-white">
|
|
{renderSidoUsgCells(idx, row, pbnstBySidoHeader)}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<SourceText />
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|