Files
frontend/apps/dm/app/(with-layout)/status/_components/SewageStatusSection.tsx
T

489 lines
19 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>
);
}