@@ -153,6 +153,10 @@ function SelectBox({
|
||||
);
|
||||
}
|
||||
|
||||
function Hr({ className = "" }: { className?: string }) {
|
||||
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
|
||||
}
|
||||
|
||||
export default function DemandPlaceRegisterPage() {
|
||||
const { Common, initializeCommonData } = useDmStore((state) => state);
|
||||
const { selectSidoData, selectSigunData } = Common;
|
||||
@@ -401,177 +405,199 @@ export default function DemandPlaceRegisterPage() {
|
||||
const pageTitle = editMode ? "수요처수정" : "수요처등록";
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
<SubPageHeader
|
||||
title={pageTitle}
|
||||
crumbs={[
|
||||
{ label: "Home", href: "/reuse" },
|
||||
{ label: "수요신청", href: "/reuse/apply" },
|
||||
{ label: pageTitle },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
<FormRow label="시도" required>
|
||||
<SelectBox
|
||||
value={sido}
|
||||
onChange={(v) => {
|
||||
setSido(v);
|
||||
setSigungu("");
|
||||
}}
|
||||
options={sidoOptions}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="시군구" required>
|
||||
<select
|
||||
value={sigungu}
|
||||
onChange={(e) => setSigungu(e.target.value)}
|
||||
className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none"
|
||||
>
|
||||
{sigunguOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="회사명" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={company}
|
||||
onChange={setCompany}
|
||||
placeholder="회사명을 입력해주세요."
|
||||
<main className="min-w-0 flex-1">
|
||||
<div className="relative">
|
||||
{/* 서브 타이틀 + 브레드크럼 */}
|
||||
<SubPageHeader
|
||||
title={pageTitle}
|
||||
crumbs={[
|
||||
{ label: "Home", href: "/main" },
|
||||
{ label: "수요신청", href: "/apply/dmdco" },
|
||||
{ label: pageTitle },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
{/* 컨텐츠 영역 */}
|
||||
<div className="max-w-[1320px] pb-24">
|
||||
{/* 타이틀 */}
|
||||
<div className="inline-block">
|
||||
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
|
||||
<span className="relative z-10">{pageTitle}</span>
|
||||
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* 구분선 */}
|
||||
<Hr className="mt-[40px]" />
|
||||
|
||||
<FormRow label="사업자등록번호" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={formatBusinessNumber(bizNo)}
|
||||
onChange={(v) => setBizNo(v.replace(/\D/g, "").slice(0, 10))}
|
||||
placeholder="사업자 등록번호를 입력해주세요."
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
{/* 필수값 안내 */}
|
||||
<p className="my-4 text-[16px] text-neutral-600 font-medium">
|
||||
<span className="text-red-500">*</span> 는 필수 값입니다.
|
||||
</p>
|
||||
|
||||
<Divider />
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
<FormRow label="시도" required>
|
||||
<SelectBox
|
||||
value={sido}
|
||||
onChange={(v) => {
|
||||
setSido(v);
|
||||
setSigungu("");
|
||||
}}
|
||||
options={sidoOptions}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow label="종사자등록번호">
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<div className="w-[420px] max-w-full">
|
||||
<TextInput
|
||||
value={workerNo}
|
||||
onChange={(v) => setWorkerNo(v.replace(/\D/g, "").slice(0, 4))}
|
||||
placeholder="종사자등록번호를 입력해주세요."
|
||||
disabled={noWorkerNo}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
<FormRow label="시군구" required>
|
||||
<select
|
||||
value={sigungu}
|
||||
onChange={(e) => setSigungu(e.target.value)}
|
||||
className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none"
|
||||
>
|
||||
{sigunguOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="회사명" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={company}
|
||||
onChange={setCompany}
|
||||
placeholder="회사명을 입력해주세요."
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="사업자등록번호" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={formatBusinessNumber(bizNo)}
|
||||
onChange={(v) => setBizNo(v.replace(/\D/g, "").slice(0, 10))}
|
||||
placeholder="사업자 등록번호를 입력해주세요."
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="종사자등록번호">
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<div className="w-[420px] max-w-full">
|
||||
<TextInput
|
||||
value={workerNo}
|
||||
onChange={(v) => setWorkerNo(v.replace(/\D/g, "").slice(0, 4))}
|
||||
placeholder="종사자등록번호를 입력해주세요."
|
||||
disabled={noWorkerNo}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={noWorkerNo}
|
||||
onChange={(e) => {
|
||||
setNoWorkerNo(e.target.checked);
|
||||
if (e.target.checked) setWorkerNo("");
|
||||
}}
|
||||
className="h-4 w-4 rounded border-neutral-300"
|
||||
/>
|
||||
해당없음
|
||||
</label>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="사업자주소" required>
|
||||
<div className="max-w-[420px] space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-[180px]">
|
||||
<TextInput value={zip} onChange={setZip} placeholder="우편번호" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsPostcodeModalOpen(true)}
|
||||
className="h-11 rounded bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
>
|
||||
우편번호 검색
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TextInput value={addr1} onChange={setAddr1} placeholder="주소" />
|
||||
<TextInput
|
||||
value={addr2}
|
||||
onChange={setAddr2}
|
||||
placeholder="상세 주소를 입력하세요."
|
||||
inputRef={detailAddrRef}
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="전화번호" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={tel}
|
||||
onChange={(v) => setTel(formatKoreanPhoneNumber(v))}
|
||||
placeholder="전화번호를 입력하세요."
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="비밀번호" required={!editMode}>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={pw}
|
||||
onChange={setPw}
|
||||
placeholder="비밀번호를 입력하세요."
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="비밀번호 확인" required={!editMode}>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={pw2}
|
||||
onChange={setPw2}
|
||||
placeholder="비밀번호를 한번 더 입력하세요."
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center py-12">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
|
||||
>
|
||||
{editMode ? "수요처 수정" : "수요처 등록"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<PostcodeModal
|
||||
open={isPostcodeModalOpen}
|
||||
onClose={() => setIsPostcodeModalOpen(false)}
|
||||
onSelect={handleSelectAddress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={noWorkerNo}
|
||||
onChange={(e) => {
|
||||
setNoWorkerNo(e.target.checked);
|
||||
if (e.target.checked) setWorkerNo("");
|
||||
}}
|
||||
className="h-4 w-4 rounded border-neutral-300"
|
||||
/>
|
||||
해당없음
|
||||
</label>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="사업자주소" required>
|
||||
<div className="max-w-[420px] space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-[180px]">
|
||||
<TextInput value={zip} onChange={setZip} placeholder="우편번호" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsPostcodeModalOpen(true)}
|
||||
className="h-11 rounded bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
>
|
||||
우편번호 검색
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TextInput value={addr1} onChange={setAddr1} placeholder="주소" />
|
||||
<TextInput
|
||||
value={addr2}
|
||||
onChange={setAddr2}
|
||||
placeholder="상세 주소를 입력하세요."
|
||||
inputRef={detailAddrRef}
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="전화번호" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={tel}
|
||||
onChange={(v) => setTel(formatKoreanPhoneNumber(v))}
|
||||
placeholder="전화번호를 입력하세요."
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="비밀번호" required={!editMode}>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={pw}
|
||||
onChange={setPw}
|
||||
placeholder="비밀번호를 입력하세요."
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="비밀번호 확인" required={!editMode}>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
value={pw2}
|
||||
onChange={setPw2}
|
||||
placeholder="비밀번호를 한번 더 입력하세요."
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</FormRow>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center py-12">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
|
||||
>
|
||||
{editMode ? "수요처 수정" : "수요처 등록"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<PostcodeModal
|
||||
open={isPostcodeModalOpen}
|
||||
onClose={() => setIsPostcodeModalOpen(false)}
|
||||
onSelect={handleSelectAddress}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ import Lnb from "@/components/Lnb";
|
||||
|
||||
export default function ApplyLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="flex gap-0">
|
||||
<div className="relative">
|
||||
{/* 우측 메뉴 */}
|
||||
<Lnb />
|
||||
<main className="flex-1 px-[100px] pt-[80px]">{children}</main>
|
||||
|
||||
{/* 본문 */}
|
||||
<div className="flex max-w-[1400px] gap-[56px] py-[50px]">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -422,261 +422,279 @@ export default function DemandRegisterPage() {
|
||||
setUsePurpose(value);
|
||||
}
|
||||
|
||||
const pageTitle = "수요정보등록";
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<main className="bg-white">
|
||||
<main className="min-w-0 flex-1">
|
||||
<div className="relative">
|
||||
{/* 서브 타이틀 + 브레드크럼 */}
|
||||
<SubPageHeader
|
||||
title={pageTitle}
|
||||
crumbs={[
|
||||
{ label: "Home", href: "/main" },
|
||||
{ label: "수요신청", href: "/apply/dmdco" },
|
||||
{ label: pageTitle },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* 서브 타이틀 + 브레드크럼 */}
|
||||
<SubPageHeader
|
||||
title="수요정보등록"
|
||||
crumbs={[
|
||||
{ label: "Home", href: "/reuse" },
|
||||
{ label: "수요신청", href: "/reuse/apply" },
|
||||
{ label: "수요정보등록" },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* 컨텐츠 영역 */}
|
||||
<div>
|
||||
|
||||
{/* 기본정보 테이블 */}
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
<FormRow label="요구시기" required>
|
||||
<DateInput value={reqDate} onChange={setReqDate} />
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="요구수량(m²/d)" required>
|
||||
<TextInput value={reqQty} onChange={setReqQty} placeholder="" />
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="사용용도" required>
|
||||
<select
|
||||
value={usePurpose}
|
||||
onChange={(e) => fncSetOptionValue(e)}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"
|
||||
>
|
||||
<option value="">선택하세요</option>
|
||||
{useTypesData.map((item) => (
|
||||
<option key={item.val} value={item.val}>
|
||||
{item.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="희망단가(원)" required>
|
||||
<TextInput value={hopePrice} onChange={setHopePrice} />
|
||||
</FormRow>
|
||||
</div>
|
||||
|
||||
{/* 요구수질 섹션 타이틀 바 */}
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-6 py-6 text-center">
|
||||
<span className="text-lg font-semibold text-neutral-800">요구수질</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpenRef(true)}
|
||||
className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50"
|
||||
>
|
||||
수질기준 참고
|
||||
</button>
|
||||
{/* 수질기준참고 모달 */}
|
||||
<WaterQualityRefModal
|
||||
open={openRef}
|
||||
onClose={() => setOpenRef(false)}
|
||||
imageSrc="/images/landing/water_quality_ref_1.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 요구수질 테이블 */}
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
{/* 1행 */}
|
||||
<QualityRow
|
||||
leftLabel="총대장균군수(개/mg)"
|
||||
|
||||
leftControl={
|
||||
<div className="flex items-center gap-4">
|
||||
<TextInput
|
||||
value={coliform}
|
||||
onChange={setColiform}
|
||||
disabled={noDetect}
|
||||
className={noDetect ? "bg-neutral-100 text-neutral-400" : ""}
|
||||
/>
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={noDetect}
|
||||
onChange={(e) => {
|
||||
setNoDetect(e.target.checked);
|
||||
if (e.target.checked) setColiform("");
|
||||
}}
|
||||
className="h-4 w-4 rounded border-neutral-300"
|
||||
/>
|
||||
불검출
|
||||
</label>
|
||||
{/* 컨텐츠 영역 */}
|
||||
<div className="max-w-[1320px] pb-24">
|
||||
{/* 타이틀 */}
|
||||
<div className="inline-block">
|
||||
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
|
||||
<span className="relative z-10">{pageTitle}</span>
|
||||
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
|
||||
</h2>
|
||||
</div>
|
||||
}
|
||||
rightLabel="총질소(T-N)(mg/L)"
|
||||
rightControl={
|
||||
<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 구분선 */}
|
||||
<Hr className="mt-[40px]" />
|
||||
|
||||
{/* 2행 */}
|
||||
<QualityRow
|
||||
leftLabel="결합잔류염소(mg/L)"
|
||||
/* leftRequired */
|
||||
leftControl={<TextInput value={residualCl} onChange={setResidualCl} />}
|
||||
rightLabel="총인(T-P)(mg/L)"
|
||||
rightControl={
|
||||
<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
{/* 필수값 안내 */}
|
||||
<p className="my-4 text-[16px] text-neutral-600 font-medium">
|
||||
<span className="text-red-500">*</span> 는 필수 값입니다.
|
||||
</p>
|
||||
|
||||
{/* 3행 */}
|
||||
<QualityRow
|
||||
leftLabel="탁도(NTU)"
|
||||
/* leftRequired */
|
||||
leftControl={
|
||||
<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />
|
||||
}
|
||||
rightLabel="수소이온농도(pH)"
|
||||
rightControl={
|
||||
<div className="flex items-center gap-2">
|
||||
<TextInput
|
||||
value={ph.min}
|
||||
onChange={(v) => setPh((p) => ({ ...p, min: v }))}
|
||||
className="w-32"
|
||||
/>
|
||||
<span className="text-neutral-500">~</span>
|
||||
<TextInput
|
||||
value={ph.max}
|
||||
onChange={(v) => setPh((p) => ({ ...p, max: v }))}
|
||||
className="w-32"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
{/* 기본정보 테이블 */}
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
<FormRow label="요구시기" required>
|
||||
<DateInput value={reqDate} onChange={setReqDate} />
|
||||
</FormRow>
|
||||
|
||||
{/* 4행 */}
|
||||
<QualityRow
|
||||
leftLabel={
|
||||
<>
|
||||
생물화학적산소요구량
|
||||
<br />
|
||||
(BOD)(mg/L)
|
||||
</>
|
||||
}
|
||||
/* leftRequired */
|
||||
leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />}
|
||||
rightLabel="염화물(mgCL/L)"
|
||||
rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />}
|
||||
/>
|
||||
<Divider />
|
||||
<Divider />
|
||||
|
||||
{/* 5행 */}
|
||||
<QualityRow
|
||||
leftLabel="냄새"
|
||||
/* leftRequired */
|
||||
leftControl={
|
||||
<select
|
||||
value={odor}
|
||||
onChange={(e) => setOdor(e.target.value)}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"
|
||||
>
|
||||
<option>불쾌하지 않을 것</option>
|
||||
<option>약간 있음</option>
|
||||
<option>있음</option>
|
||||
</select>
|
||||
}
|
||||
rightLabel="전기전도도(µs/cm)"
|
||||
rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix={farmText} />}
|
||||
/>
|
||||
<Divider />
|
||||
<FormRow label="요구수량(m²/d)" required>
|
||||
<TextInput value={reqQty} onChange={setReqQty} placeholder="" />
|
||||
</FormRow>
|
||||
|
||||
{/* 6행 */}
|
||||
<QualityRow
|
||||
leftLabel="색도(도)"
|
||||
/* leftRequired */
|
||||
leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />}
|
||||
rightLabel="비고"
|
||||
rightControl={
|
||||
<textarea
|
||||
value={remark}
|
||||
onChange={(e) => setRemark(e.target.value)}
|
||||
className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<Divider />
|
||||
|
||||
{/* 후보지 선택 */}
|
||||
<div className="grid grid-cols-12">
|
||||
<LabelCell>
|
||||
공급처 후보지 선택
|
||||
<br />
|
||||
(필수아님)
|
||||
</LabelCell>
|
||||
|
||||
<div className="col-span-12 md:col-span-10 p-6">
|
||||
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
|
||||
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
|
||||
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
|
||||
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
|
||||
|
||||
<div className="flex items-center gap-3 xl:ml-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
|
||||
onClick={(e) => {
|
||||
searchCandidate(e);
|
||||
|
||||
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
|
||||
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
|
||||
}}>
|
||||
후보지 조회
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
|
||||
onClick={(e) => {
|
||||
// TODO: 유사사례 연결
|
||||
searchSimilarCase(e);
|
||||
}}
|
||||
<FormRow label="사용용도" required>
|
||||
<select
|
||||
value={usePurpose}
|
||||
onChange={(e) => fncSetOptionValue(e)}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"
|
||||
>
|
||||
유사사례
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<option value="">선택하세요</option>
|
||||
{useTypesData.map((item) => (
|
||||
<option key={item.val} value={item.val}>
|
||||
{item.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormRow>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="flex justify-center py-12">
|
||||
<button
|
||||
type="button"
|
||||
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
|
||||
onClick={() => {
|
||||
// TODO: 저장 처리
|
||||
saveInfo();
|
||||
}}
|
||||
>
|
||||
수요정보 등록
|
||||
</button>
|
||||
</div>
|
||||
<Divider />
|
||||
|
||||
<FormRow label="희망단가(원)" required>
|
||||
<TextInput value={hopePrice} onChange={setHopePrice} />
|
||||
</FormRow>
|
||||
</div>
|
||||
|
||||
{/* 요구수질 섹션 타이틀 바 */}
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-2 py-6 text-center">
|
||||
<span className="text-lg font-semibold text-neutral-800">요구수질</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpenRef(true)}
|
||||
className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50"
|
||||
>
|
||||
수질기준 참고
|
||||
</button>
|
||||
{/* 수질기준참고 모달 */}
|
||||
<WaterQualityRefModal
|
||||
open={openRef}
|
||||
onClose={() => setOpenRef(false)}
|
||||
imageSrc="/images/landing/water_quality_ref_1.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 요구수질 테이블 */}
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
{/* 1행 */}
|
||||
<QualityRow
|
||||
leftLabel="총대장균군수(개/mg)"
|
||||
|
||||
leftControl={
|
||||
<div className="flex items-center gap-4">
|
||||
<TextInput
|
||||
value={coliform}
|
||||
onChange={setColiform}
|
||||
disabled={noDetect}
|
||||
className={noDetect ? "bg-neutral-100 text-neutral-400" : ""}
|
||||
/>
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={noDetect}
|
||||
onChange={(e) => {
|
||||
setNoDetect(e.target.checked);
|
||||
if (e.target.checked) setColiform("");
|
||||
}}
|
||||
className="h-4 w-4 rounded border-neutral-300"
|
||||
/>
|
||||
불검출
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
rightLabel="총질소(T-N)(mg/L)"
|
||||
rightControl={
|
||||
<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 2행 */}
|
||||
<QualityRow
|
||||
leftLabel="결합잔류염소(mg/L)"
|
||||
/* leftRequired */
|
||||
leftControl={<TextInput value={residualCl} onChange={setResidualCl} />}
|
||||
rightLabel="총인(T-P)(mg/L)"
|
||||
rightControl={
|
||||
<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 3행 */}
|
||||
<QualityRow
|
||||
leftLabel="탁도(NTU)"
|
||||
/* leftRequired */
|
||||
leftControl={
|
||||
<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />
|
||||
}
|
||||
rightLabel="수소이온농도(pH)"
|
||||
rightControl={
|
||||
<div className="flex items-center gap-2">
|
||||
<TextInput
|
||||
value={ph.min}
|
||||
onChange={(v) => setPh((p) => ({ ...p, min: v }))}
|
||||
className="w-32"
|
||||
/>
|
||||
<span className="text-neutral-500">~</span>
|
||||
<TextInput
|
||||
value={ph.max}
|
||||
onChange={(v) => setPh((p) => ({ ...p, max: v }))}
|
||||
className="w-32"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 4행 */}
|
||||
<QualityRow
|
||||
leftLabel={
|
||||
<>
|
||||
생물화학적산소요구량
|
||||
<br />
|
||||
(BOD)(mg/L)
|
||||
</>
|
||||
}
|
||||
/* leftRequired */
|
||||
leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />}
|
||||
rightLabel="염화물(mgCL/L)"
|
||||
rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 5행 */}
|
||||
<QualityRow
|
||||
leftLabel="냄새"
|
||||
/* leftRequired */
|
||||
leftControl={
|
||||
<select
|
||||
value={odor}
|
||||
onChange={(e) => setOdor(e.target.value)}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"
|
||||
>
|
||||
<option>불쾌하지 않을 것</option>
|
||||
<option>약간 있음</option>
|
||||
<option>있음</option>
|
||||
</select>
|
||||
}
|
||||
rightLabel="전기전도도(µs/cm)"
|
||||
rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix={farmText} />}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 6행 */}
|
||||
<QualityRow
|
||||
leftLabel="색도(도)"
|
||||
/* leftRequired */
|
||||
leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />}
|
||||
rightLabel="비고"
|
||||
rightControl={
|
||||
<textarea
|
||||
value={remark}
|
||||
onChange={(e) => setRemark(e.target.value)}
|
||||
className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{/* 후보지 선택 */}
|
||||
<div className="grid grid-cols-12">
|
||||
<LabelCell>
|
||||
공급처 후보지 선택
|
||||
<br />
|
||||
(필수아님)
|
||||
</LabelCell>
|
||||
|
||||
<div className="col-span-12 md:col-span-10 p-6">
|
||||
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
|
||||
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
|
||||
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
|
||||
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
|
||||
|
||||
<div className="flex items-center gap-3 xl:ml-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
|
||||
onClick={(e) => {
|
||||
searchCandidate(e);
|
||||
|
||||
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
|
||||
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
|
||||
}}>
|
||||
후보지 조회
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
|
||||
onClick={(e) => {
|
||||
// TODO: 유사사례 연결
|
||||
searchSimilarCase(e);
|
||||
}}
|
||||
>
|
||||
유사사례
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="flex justify-center py-12">
|
||||
<button
|
||||
type="button"
|
||||
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
|
||||
onClick={() => {
|
||||
// TODO: 저장 처리
|
||||
saveInfo();
|
||||
}}
|
||||
>
|
||||
수요정보 등록
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -836,6 +854,10 @@ function InlineSuffixInput({
|
||||
);
|
||||
}
|
||||
|
||||
function Hr({ className = "" }: { className?: string }) {
|
||||
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
|
||||
}
|
||||
|
||||
function DateInput({
|
||||
value,
|
||||
onChange,
|
||||
|
||||
@@ -326,402 +326,414 @@ export default function DemandStatusPage() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const pageTitle = "수요신청현황";
|
||||
|
||||
return (
|
||||
|
||||
<main className="bg-white">
|
||||
<SubPageHeader
|
||||
title="수요신청현황"
|
||||
crumbs={[
|
||||
{ label: "Home", href: "/main" },
|
||||
{ label: "수요신청", href: "/apply/status" },
|
||||
{ label: "수요신청현황" },
|
||||
]}
|
||||
hideFooterLine
|
||||
hideGuideText
|
||||
/>
|
||||
<main className="min-w-0 flex-1">
|
||||
<div className="relative">
|
||||
{/* 서브 타이틀 + 브레드크럼 */}
|
||||
<SubPageHeader
|
||||
title={pageTitle}
|
||||
crumbs={[
|
||||
{ label: "Home", href: "/main" },
|
||||
{ label: "수요신청", href: "/apply/dmdco" },
|
||||
{ label: pageTitle },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<section className="mt-6">
|
||||
<div className="text-2xl font-bold text-slate-700">수요정보 신청현황</div>
|
||||
{/* 컨텐츠 영역 */}
|
||||
<div className="max-w-[1320px] pb-24">
|
||||
{/* 타이틀 */}
|
||||
<div className="inline-block">
|
||||
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
|
||||
<span className="relative z-10">{pageTitle}</span>
|
||||
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<section className="mt-6">
|
||||
<div className="text-2xl font-bold text-slate-700 text-center w-full">수요정보 신청현황</div>
|
||||
|
||||
<div className="mt-3 border border-neutral-200">
|
||||
<table className="w-full table-fixed">
|
||||
<thead className="bg-[#E7F4E2]">
|
||||
<tr className="text-sm text-slate-700">
|
||||
<th className="w-[60px] px-3 py-3 text-center">NO.</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">시도</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">시군구</th>
|
||||
<th className="w-[180px] px-3 py-3 text-center">회사명</th>
|
||||
<th className="px-3 py-3 text-center">공장주소</th>
|
||||
<th className="w-[140px] px-3 py-3 text-center">전화번호</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">요구시기</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">등록일자</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">수정일자</th>
|
||||
<th className="w-[110px] px-3 py-3 text-center">진행상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<div className="mt-3 border border-neutral-200">
|
||||
<table className="w-full table-fixed">
|
||||
<thead className="bg-[#E7F4E2]">
|
||||
<tr className="text-sm text-slate-700">
|
||||
<th className="w-[60px] px-3 py-3 text-center">NO.</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">시도</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">시군구</th>
|
||||
<th className="w-[180px] px-3 py-3 text-center">회사명</th>
|
||||
<th className="px-3 py-3 text-center">공장주소</th>
|
||||
<th className="w-[140px] px-3 py-3 text-center">전화번호</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">요구시기</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">등록일자</th>
|
||||
<th className="w-[120px] px-3 py-3 text-center">수정일자</th>
|
||||
<th className="w-[110px] px-3 py-3 text-center">진행상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody className="bg-white text-base text-slate-700">
|
||||
{transformedSdmInfoStatusList.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-3 py-10 text-center text-slate-400">
|
||||
조회된 신청내역이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
transformedSdmInfoStatusList.map((sdmInfoStatus, index) => {
|
||||
const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn;
|
||||
return (
|
||||
<tr
|
||||
key={`${sdmInfoStatus.dmdInfoSn}-${index}`}
|
||||
role="row"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn);
|
||||
}
|
||||
}}
|
||||
className={[
|
||||
"border-t border-neutral-200 outline-none",
|
||||
"cursor-pointer",
|
||||
active ? "bg-slate-50" : "hover:bg-slate-50",
|
||||
"focus-visible:ring-2 focus-visible:ring-slate-300",
|
||||
].join(" ")}
|
||||
aria-selected={active}
|
||||
>
|
||||
<td className="px-3 py-3 text-center">{sdmInfoStatus.index || index + 1}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.daddr)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.telno)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.dmndTmng)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.regDt)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.mdfcnDt)}</td>
|
||||
<td className="px-3 py-3 text-center">
|
||||
<StatusBadge v={formatValue(sdmInfoStatus.status)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-10 pb-16" ref={detailRef}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div>
|
||||
{
|
||||
!isEditMode ? (
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
onClick={() => {
|
||||
setOriginData(selectedSdmInfoStatus);
|
||||
setEditData({...selectedSdmInfoStatus});
|
||||
setIsEditMode(true)}
|
||||
}
|
||||
>
|
||||
수정
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
onClick={() => {
|
||||
saveInfo();
|
||||
}}
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
onClick={() => {
|
||||
setEditData(originData);
|
||||
setIsEditMode(false)
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{!selectedSdmInfoStatus ? (
|
||||
<div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400">
|
||||
상세 정보를 표시할 신청건이 없습니다.
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 border border-neutral-200">
|
||||
<div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
||||
요구수량
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 text-sm">
|
||||
{topFields.map((field) => (
|
||||
<div
|
||||
key={field.label}
|
||||
className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200"
|
||||
>
|
||||
<div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
{field.label}
|
||||
{field.required ? <span className="ml-1 text-red-500">*</span> : null}
|
||||
<tbody className="bg-white text-base text-slate-700">
|
||||
{transformedSdmInfoStatusList.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-3 py-10 text-center text-slate-400">
|
||||
조회된 신청내역이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
transformedSdmInfoStatusList.map((sdmInfoStatus, index) => {
|
||||
const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn;
|
||||
return (
|
||||
<tr
|
||||
key={`${sdmInfoStatus.dmdInfoSn}-${index}`}
|
||||
role="row"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn);
|
||||
}
|
||||
}}
|
||||
className={[
|
||||
"border-t border-neutral-200 outline-none",
|
||||
"cursor-pointer",
|
||||
active ? "bg-slate-50" : "hover:bg-slate-50",
|
||||
"focus-visible:ring-2 focus-visible:ring-slate-300",
|
||||
].join(" ")}
|
||||
aria-selected={active}
|
||||
>
|
||||
<td className="px-3 py-3 text-center">{sdmInfoStatus.index || index + 1}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.daddr)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.telno)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.dmndTmng)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.regDt)}</td>
|
||||
<td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.mdfcnDt)}</td>
|
||||
<td className="px-3 py-3 text-center">
|
||||
<StatusBadge v={formatValue(sdmInfoStatus.status)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-10 pb-16" ref={detailRef}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div>
|
||||
{
|
||||
!isEditMode ?
|
||||
<div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
|
||||
{field.key==='useUsgCd'? useTypesData.filter(item=>item.val===field.value)[0]?.text: field.value}
|
||||
</div>
|
||||
: <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
|
||||
{field.key ==='dmndTmng'?
|
||||
<DateInput value={editData?.[field.key] ?? ""} onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: v,
|
||||
}))
|
||||
} />
|
||||
: field.key ==='useUsgCd'?
|
||||
<select
|
||||
value={editData?.[field.key] ?? ""}
|
||||
onChange={(e) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: e.target.value,
|
||||
}))
|
||||
!isEditMode ? (
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
onClick={() => {
|
||||
setOriginData(selectedSdmInfoStatus);
|
||||
setEditData({...selectedSdmInfoStatus});
|
||||
setIsEditMode(true)}
|
||||
}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"
|
||||
>
|
||||
<option value="">선택하세요</option>
|
||||
{useTypesData.map((item) => (
|
||||
<option key={item.val} value={item.val}>
|
||||
{item.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
: <TextInput value={editData?.[field.key] ?? ""} onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: v,
|
||||
}))
|
||||
}></TextInput>
|
||||
}
|
||||
>
|
||||
수정
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
onClick={() => {
|
||||
saveInfo();
|
||||
}}
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
||||
onClick={() => {
|
||||
setEditData(originData);
|
||||
setIsEditMode(false)
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
||||
요구수질
|
||||
</div>
|
||||
|
||||
{waterFields.map((field) => {
|
||||
const { a, b } = editData?.ph ? getRange(editData.ph) : { a: "", b: "" };
|
||||
return (
|
||||
<div
|
||||
key={field.label}
|
||||
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200"
|
||||
>
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
{field.label}
|
||||
{!selectedSdmInfoStatus ? (
|
||||
<div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400">
|
||||
상세 정보를 표시할 신청건이 없습니다.
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 border border-neutral-200">
|
||||
<div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
||||
요구수량
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{
|
||||
!isEditMode ? field.value
|
||||
:
|
||||
field.key === "grssClfg" ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<TextInput
|
||||
value={
|
||||
editData?.grssClfg === -1
|
||||
? ""
|
||||
: editData?.grssClfg ?? ""
|
||||
}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
grssClfg: v,
|
||||
}))
|
||||
}
|
||||
disabled={editData?.grssClfg === -1}
|
||||
className={
|
||||
editData?.grssClfg === -1
|
||||
? "bg-neutral-100 text-neutral-400"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
<label className="flex items-center gap-2 w-3/4 text-sm text-neutral-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editData?.grssClfg === -1}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
grssClfg: checked ? -1 : "",
|
||||
}));
|
||||
}}
|
||||
className="h-4 w-4 rounded border-neutral-300"
|
||||
/>
|
||||
불검출
|
||||
</label>
|
||||
<div className="grid grid-cols-12 text-sm">
|
||||
{topFields.map((field) => (
|
||||
<div
|
||||
key={field.label}
|
||||
className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200"
|
||||
>
|
||||
<div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
{field.label}
|
||||
{field.required ? <span className="ml-1 text-red-500">*</span> : null}
|
||||
</div>
|
||||
) : field.key === 'smll' ? (
|
||||
<select
|
||||
value={
|
||||
editData?.smll === -1
|
||||
? ""
|
||||
: editData?.smll ?? ""
|
||||
}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
grssClfg: v,
|
||||
}))
|
||||
}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500">
|
||||
<option>불쾌하지 않을 것</option>
|
||||
<option>약간 있음</option>
|
||||
<option>있음</option>
|
||||
</select>
|
||||
) : field.key === 'ph' ? (
|
||||
<>
|
||||
<TextInput
|
||||
value={a}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: `${v}~${b}`,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
~
|
||||
<TextInput
|
||||
value={b}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: `${a}~${v}`,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TextInput
|
||||
value={editData?.[field.key] ?? ""}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: v,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
{
|
||||
field.key==='tn'||field.key === 'tph'||field.key === 'trbt'||field.key==='blgcOxygDmndQnt'||field.key==='clIonQnt'||field.key==='cdtnv'|| field.key=='crmty'
|
||||
?<>이하</>:<></>
|
||||
!isEditMode ?
|
||||
<div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
|
||||
{field.key==='useUsgCd'? useTypesData.filter(item=>item.val===field.value)[0]?.text: field.value}
|
||||
</div>
|
||||
: <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
|
||||
{field.key ==='dmndTmng'?
|
||||
<DateInput value={editData?.[field.key] ?? ""} onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: v,
|
||||
}))
|
||||
} />
|
||||
: field.key ==='useUsgCd'?
|
||||
<select
|
||||
value={editData?.[field.key] ?? ""}
|
||||
onChange={(e) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"
|
||||
>
|
||||
<option value="">선택하세요</option>
|
||||
{useTypesData.map((item) => (
|
||||
<option key={item.val} value={item.val}>
|
||||
{item.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
: <TextInput value={editData?.[field.key] ?? ""} onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: v,
|
||||
}))
|
||||
}></TextInput>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
||||
요구수질
|
||||
</div>
|
||||
|
||||
{waterFields.map((field) => {
|
||||
const { a, b } = editData?.ph ? getRange(editData.ph) : { a: "", b: "" };
|
||||
return (
|
||||
<div
|
||||
key={field.label}
|
||||
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200"
|
||||
>
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
{field.label}
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{
|
||||
!isEditMode ? field.value
|
||||
:
|
||||
field.key === "grssClfg" ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<TextInput
|
||||
value={
|
||||
editData?.grssClfg === -1
|
||||
? ""
|
||||
: editData?.grssClfg ?? ""
|
||||
}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
grssClfg: v,
|
||||
}))
|
||||
}
|
||||
disabled={editData?.grssClfg === -1}
|
||||
className={
|
||||
editData?.grssClfg === -1
|
||||
? "bg-neutral-100 text-neutral-400"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
<label className="flex items-center gap-2 w-3/4 text-sm text-neutral-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editData?.grssClfg === -1}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
grssClfg: checked ? -1 : "",
|
||||
}));
|
||||
}}
|
||||
className="h-4 w-4 rounded border-neutral-300"
|
||||
/>
|
||||
불검출
|
||||
</label>
|
||||
</div>
|
||||
) : field.key === 'smll' ? (
|
||||
<select
|
||||
value={
|
||||
editData?.smll === -1
|
||||
? ""
|
||||
: editData?.smll ?? ""
|
||||
}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
grssClfg: v,
|
||||
}))
|
||||
}
|
||||
className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500">
|
||||
<option>불쾌하지 않을 것</option>
|
||||
<option>약간 있음</option>
|
||||
<option>있음</option>
|
||||
</select>
|
||||
) : field.key === 'ph' ? (
|
||||
<>
|
||||
<TextInput
|
||||
value={a}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: `${v}~${b}`,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
~
|
||||
<TextInput
|
||||
value={b}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: `${a}~${v}`,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TextInput
|
||||
value={editData?.[field.key] ?? ""}
|
||||
onChange={(v) =>
|
||||
setEditData((prev) => ({
|
||||
...prev,
|
||||
[field.key]: v,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
{
|
||||
field.key==='tn'||field.key === 'tph'||field.key === 'trbt'||field.key==='blgcOxygDmndQnt'||field.key==='clIonQnt'||field.key==='cdtnv'|| field.key=='crmty'
|
||||
?<>이하</>:<></>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)})}
|
||||
|
||||
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
||||
공급처 후보지
|
||||
{
|
||||
isEditMode &&
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-25 ml-12 rounded-md bg-[#888] px-2 text-base text-white hover:bg-neutral-700"
|
||||
onClick={(e) => {
|
||||
searchCandidate(e);
|
||||
|
||||
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
|
||||
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
|
||||
}}>
|
||||
후보지 조회
|
||||
</button>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
{candidateNames.map((candidateName, index) => (
|
||||
isEditMode ? (
|
||||
<div
|
||||
key={`candidate-${index}`}
|
||||
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
|
||||
>
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
후보지{index + 1}
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
<TextInput
|
||||
value={candidateName}
|
||||
disabled={true}
|
||||
onChange={(v) => {
|
||||
setEditData((prev) => {
|
||||
const newList = [...(prev.splyCndtList || [])];
|
||||
newList[index] = {
|
||||
...newList[index],
|
||||
ruseUtztnFcltNm: v,
|
||||
};
|
||||
return {
|
||||
...prev,
|
||||
splyCndtList: newList,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) :
|
||||
<div
|
||||
key={`candidate-${index}`}
|
||||
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
|
||||
>
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
후보지{index + 1}
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{candidateName}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200">
|
||||
수요신청 진행상태
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
진행상태
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{formatValue(selectedSdmInfoStatus.status)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
공급처매칭결과
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)})}
|
||||
|
||||
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
||||
공급처 후보지
|
||||
{
|
||||
isEditMode &&
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-25 ml-12 rounded-md bg-[#888] px-2 text-base text-white hover:bg-neutral-700"
|
||||
onClick={(e) => {
|
||||
searchCandidate(e);
|
||||
|
||||
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
|
||||
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
|
||||
}}>
|
||||
후보지 조회
|
||||
</button>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
{candidateNames.map((candidateName, index) => (
|
||||
isEditMode ? (
|
||||
<div
|
||||
key={`candidate-${index}`}
|
||||
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
|
||||
>
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
후보지{index + 1}
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
<TextInput
|
||||
value={candidateName}
|
||||
disabled={true}
|
||||
onChange={(v) => {
|
||||
setEditData((prev) => {
|
||||
const newList = [...(prev.splyCndtList || [])];
|
||||
newList[index] = {
|
||||
...newList[index],
|
||||
ruseUtztnFcltNm: v,
|
||||
};
|
||||
return {
|
||||
...prev,
|
||||
splyCndtList: newList,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) :
|
||||
<div
|
||||
key={`candidate-${index}`}
|
||||
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
|
||||
>
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
후보지{index + 1}
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{candidateName}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200">
|
||||
수요신청 진행상태
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
진행상태
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{formatValue(selectedSdmInfoStatus.status)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
|
||||
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
||||
공급처매칭결과
|
||||
</div>
|
||||
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
||||
{formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,50 @@
|
||||
|
||||
import CaseImageSlider from "@/components/CaseImageSlider";
|
||||
|
||||
type Slide = {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
|
||||
type ContentBlock = {
|
||||
title: string;
|
||||
items: TextItem[];
|
||||
};
|
||||
|
||||
type InfoCard = {
|
||||
title: string;
|
||||
desc: string;
|
||||
};
|
||||
|
||||
type TableData = {
|
||||
headers: string[];
|
||||
rows: string[][];
|
||||
};
|
||||
|
||||
type OverviewCompareRow = {
|
||||
country: string;
|
||||
flag?: string;
|
||||
overseas: string;
|
||||
current: string;
|
||||
future: string;
|
||||
};
|
||||
|
||||
type GlobalCase =
|
||||
| {
|
||||
type: "overview";
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
| {
|
||||
type: "default";
|
||||
id: string;
|
||||
title: string;
|
||||
slides: Slide[];
|
||||
blocks?: ContentBlock[];
|
||||
cards?: InfoCard[];
|
||||
table?: TableData;
|
||||
};
|
||||
|
||||
function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -11,105 +55,720 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
// 구분선
|
||||
function BlockTitle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="relative inline-block h-[16px] w-[27px] align-middle">
|
||||
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
|
||||
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
|
||||
</span>
|
||||
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Hr({ className = "" }: { className?: string }) {
|
||||
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
|
||||
}
|
||||
|
||||
type TextItem =
|
||||
| string
|
||||
| {
|
||||
text: React.ReactNode;
|
||||
depth?: number; // 0: 기본 / 1: 하위 / 2: 더 하위
|
||||
};
|
||||
|
||||
function ContentList({ items }: { items: TextItem[] }) {
|
||||
return (
|
||||
<ul className="mt-4 space-y-2 text-[16px] leading-[1.8] text-[#4F4F4F]">
|
||||
{items.map((item, index) => {
|
||||
const isString = typeof item === "string";
|
||||
const text = isString ? item : item.text;
|
||||
const depth = isString ? 0 : item.depth ?? 0;
|
||||
|
||||
if (depth === 3) {
|
||||
return (
|
||||
<li key={index} className="ml-[56px] list-none text-[#777]">
|
||||
* {text}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
if (depth === 2) {
|
||||
return (
|
||||
<li key={index} className="ml-[52px] list-none text-[#666]">
|
||||
{text}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
if (depth === 1) {
|
||||
return (
|
||||
<li key={index} className="ml-[34px] list-none text-[#777]">
|
||||
- {text}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index} className="ml-6 list-disc pl-1 font-semibold text-[#555]">
|
||||
{text}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function ContentBlocks({
|
||||
blocks,
|
||||
cards,
|
||||
table,
|
||||
}: {
|
||||
blocks?: ContentBlock[];
|
||||
cards?: InfoCard[];
|
||||
table?: TableData;
|
||||
}) {
|
||||
if (!blocks?.length) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-10 space-y-9">
|
||||
{blocks.map((block, index) => (
|
||||
<div key={`${block.title}-${index}`}>
|
||||
<BlockTitle>{block.title}</BlockTitle>
|
||||
|
||||
{!!block.items.length && (
|
||||
<ContentList items={block.items} />
|
||||
)}
|
||||
|
||||
{block.title.includes("재생 수자원 개발법") && (
|
||||
<InfoCards cards={cards} />
|
||||
)}
|
||||
|
||||
{block.title.includes("대만 재이용 시설별 재이용수 공급단지") && (
|
||||
<DataTable table={table} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoCards({ cards }: { cards?: InfoCard[] }) {
|
||||
if (!cards?.length) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-8 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
{cards.map((card) => (
|
||||
<div
|
||||
key={card.title}
|
||||
className="rounded-[8px] border-2 border-[#01ACE5] bg-[#EAF5F5] p-5"
|
||||
>
|
||||
<strong className="block text-[20px] font-bold text-[#333]">
|
||||
{card.title}
|
||||
</strong>
|
||||
<p className="mt-3 text-[15px] leading-[1.6] text-[#555]">
|
||||
{card.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DataTable({ table }: { table?: TableData }) {
|
||||
if (!table) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-8 overflow-x-auto">
|
||||
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
{table.headers.map((header, index) => (
|
||||
<th
|
||||
key={header}
|
||||
className={[
|
||||
"border border-[#D8DDE3] px-4 py-3 text-[18px] font-medium",
|
||||
index === 0
|
||||
? "bg-[#BFC9D2] text-[#222]"
|
||||
: index === 1
|
||||
? "bg-[#14A9D9] text-white"
|
||||
: "bg-[#70BF4B] text-white",
|
||||
].join(" ")}
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{table.rows.map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={`${rowIndex}-${cellIndex}`}
|
||||
className={[
|
||||
"border border-[#D8DDE3] px-4 py-3 text-[16px] text-[#4F4F4F]",
|
||||
cellIndex === 1
|
||||
? "bg-[#EAF7F8]"
|
||||
: cellIndex === 2
|
||||
? "bg-[#EAF8E6]"
|
||||
: "bg-white",
|
||||
].join(" ")}
|
||||
>
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
사례개요 전용 컴포넌트
|
||||
========================= */
|
||||
|
||||
const compareRows: OverviewCompareRow[] = [
|
||||
{
|
||||
country: "한국",
|
||||
flag: "/images/landing/business/globalCase/flag_kr.png",
|
||||
overseas: "12.1%",
|
||||
current: "15.6%",
|
||||
future: "17%",
|
||||
},
|
||||
{
|
||||
country: "대만",
|
||||
flag: "/images/landing/business/globalCase/flag_tw.png",
|
||||
overseas: "2.4%",
|
||||
current: "4.3%",
|
||||
future: "16.5%",
|
||||
},
|
||||
];
|
||||
|
||||
const reuseRateRows = [
|
||||
["이스라엘", "중동&북아프리카", "92.3", "튀르키예", "동유럽&중앙아시아", "26.6"],
|
||||
["쿠웨이트", "중동&북아프리카", "89.3", "독일", "서유럽", "25.0"],
|
||||
["호주", "동아시아&태평양", "49.0", "스페인", "서유럽", "24.4"],
|
||||
["싱가포르", "동아시아&태평양", "44.4", "한국", "동아시아&태평양", "12.1"],
|
||||
["그리스", "서유럽", "39.1", "미국", "북아메리카", "13.1"],
|
||||
["사우디아라비아", "중동&북아프리카", "32.8", "프랑스", "서유럽", "9.5"],
|
||||
["이탈리아", "서유럽", "31.7", "대만", "동아시아&태평양", "2.4"],
|
||||
["룩셈부르크", "서유럽", "29.1", "일본", "동아시아&태평양", "0.8"],
|
||||
["포르투갈", "서유럽", "29.0", "", "", ""],
|
||||
];
|
||||
|
||||
function WaterBullet() {
|
||||
return (
|
||||
<img
|
||||
src="/images/landing/business/globalCase/icon_bullet_water.png"
|
||||
alt="bullet"
|
||||
className="h-[33px] w-[27px] mt-1"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewNoticeBox() {
|
||||
return (
|
||||
<div className="mx-auto mt-5 max-w-[1040px] rounded-[20px] bg-[#EAF7F8] px-8 py-7">
|
||||
<div className="flex items-start gap-2">
|
||||
<WaterBullet />
|
||||
<div>
|
||||
<strong className="block text-[26px] font-extrabold text-[#007FA4]">
|
||||
우리나라 하수처리수 재이용률은 15% 수준
|
||||
</strong>
|
||||
|
||||
<ul className="mt-5 list-disc space-y-2 pl-5 text-[16px] leading-[1.7] text-[#555]">
|
||||
<li>
|
||||
2024년 하수도 통계 기준 <b>15.6%</b>이며, 제2차 물 재이용 기본 계획을 고려한{" "}
|
||||
<b style={{ color: "#1A1A1A" }}>2030년 목표는 17%</b>
|
||||
</li>
|
||||
<li>
|
||||
대만은 가뭄과 공업용수 증가에 대응하여 <b style={{ color: "#1A1A1A" }}>현재 4.3% 수준이며, 16.5%까지 확대 계획 수립</b>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewCompareTable() {
|
||||
return (
|
||||
<div className="mx-auto mt-5 max-w-[1040px] overflow-x-auto">
|
||||
<table className="w-full min-w-[760px] text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="bg-[#BFC9D2] px-4 py-4 text-[18px] font-medium text-[#222] rounded-tl-[10px] rounded-tr-[10px]">
|
||||
국가
|
||||
</th>
|
||||
<th className="bg-[#14A9D9] px-4 py-4 text-[18px] font-medium text-white rounded-tl-[10px] rounded-tr-[10px]">
|
||||
해외정보
|
||||
</th>
|
||||
<th className="bg-[#55BDA5] px-4 py-4 text-[18px] font-medium text-white rounded-tl-[10px] rounded-tr-[10px]">
|
||||
현재
|
||||
</th>
|
||||
<th className="bg-[#6AB84E] px-4 py-4 text-[18px] font-medium text-white rounded-tl-[10px] rounded-tr-[10px]">
|
||||
미래(목표, 예측치)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{compareRows.map((row) => (
|
||||
<tr key={row.country}>
|
||||
<td className="border border-[#AEB9C2] bg-white px-4 py-4 text-[20px] font-bold text-[#333]">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
{row.flag && (
|
||||
<img
|
||||
src={row.flag}
|
||||
alt={`${row.country} 국기`}
|
||||
className="h-[28px] w-[42px] object-cover"
|
||||
/>
|
||||
)}
|
||||
{row.country}
|
||||
</div>
|
||||
</td>
|
||||
<td className="border border-[#AEB9C2] bg-[#EAF7F8] px-4 py-4 text-[28px] font-bold text-[#4F4F4F]">
|
||||
{row.overseas}
|
||||
</td>
|
||||
<td className="border border-[#AEB9C2] bg-white px-4 py-4 text-[28px] font-bold text-[#4F4F4F]">
|
||||
{row.current}
|
||||
</td>
|
||||
<td className="border border-[#AEB9C2] bg-[#EAF8E6] px-4 py-4 text-[28px] font-bold text-[#4F4F4F]">
|
||||
{row.future}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewDescriptionBox() {
|
||||
return (
|
||||
<div className="mx-auto mt-5 max-w-[1040px] rounded-[20px] bg-[#EAF7F8] px-7 py-6">
|
||||
<ul className="list-disc space-y-2 pl-5 text-[16px] leading-[1.7] text-[#555]">
|
||||
<li>
|
||||
전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐
|
||||
</li>
|
||||
<li>
|
||||
이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewReuseRateTable() {
|
||||
return (
|
||||
<div className="mx-auto mt-5 max-w-[1040px] overflow-x-auto">
|
||||
<table className="w-full min-w-[960px] text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
{["국가명", "지역", "재이용률", "국가명", "지역", "재이용률"].map((header, index) => (
|
||||
<th
|
||||
key={`${header}-${index}`}
|
||||
className={[
|
||||
"px-4 py-3 text-[17px] font-bold rounded-tl-[10px] rounded-tr-[10px]",
|
||||
index % 3 === 0
|
||||
? "bg-[#BFC9D2] text-[#222]"
|
||||
: index % 3 === 1
|
||||
? "bg-[#14A9D9] text-white"
|
||||
: "bg-[#6AB84E] text-white",
|
||||
].join(" ")}
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{reuseRateRows.map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={`${rowIndex}-${cellIndex}`}
|
||||
className={[
|
||||
"border border-[#AEB9C2] px-4 py-3 text-[15px] text-[#4F4F4F]",
|
||||
cellIndex % 3 === 1
|
||||
? "bg-[#EAF7F8]"
|
||||
: cellIndex % 3 === 2
|
||||
? "bg-[#EAF8E6]"
|
||||
: "bg-white font-semibold",
|
||||
].join(" ")}
|
||||
>
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewSection() {
|
||||
return (
|
||||
<div className="mt-10">
|
||||
<div>
|
||||
<BlockTitle>주요 국가 물 재이용 계획</BlockTitle>
|
||||
<OverviewNoticeBox />
|
||||
<OverviewCompareTable />
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<BlockTitle>주요 국가의 하수처리수 재이용률 (%)</BlockTitle>
|
||||
<OverviewDescriptionBox />
|
||||
<OverviewReuseRateTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
데이터
|
||||
========================= */
|
||||
|
||||
const globalCases: GlobalCase[] = [
|
||||
{
|
||||
type: "overview",
|
||||
id: "example-global-overview",
|
||||
title: "사례 개요",
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
id: "example-global-taiwan",
|
||||
title: "[대만] 국가 주도의 공업용수 공급",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/globalCase/taiwan_01.png",
|
||||
alt: "대만 국가 주도의 공업용수 공급 1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/taiwan_02.png",
|
||||
alt: "대만 국가 주도의 공업용수 공급 2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/taiwan_03.png",
|
||||
alt: "대만 국가 주도의 공업용수 공급 3",
|
||||
},
|
||||
],
|
||||
blocks: [
|
||||
{
|
||||
title: "재생 수자원 개발법 제정 (2015년제정, 2022개정)",
|
||||
items: [
|
||||
{ text: "목적: 수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결" },
|
||||
{ text: "내용: 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등" }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "대만정부 하수재이용 사업 시행",
|
||||
items: [
|
||||
{ text: "대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑)" },
|
||||
{ text: "1,518억의 사업비 중 92% 국비, 지방비 8% 투입", depth: 1 },
|
||||
{ text: "재이용수는 24km 관로로 타이난 TSMC로 공급", depth: 1 },
|
||||
{ text: "안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능" },
|
||||
{ text: "공급단가는 수질과 운영조건에 따라 다르게 책정" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "대만 재이용 시설별 재이용수 공급단지",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "특이사항",
|
||||
items: [
|
||||
"대만은 강수량이 풍부하지만 지역별 편차로 수자원 확보에 어려움을 겪고 있으며, 특히 남부 지역은 우기와 건기의 강수 차이가 큼",
|
||||
"최근 수년간 심각한 가뭄이 발생하면서 반도체 산업을 국가 핵심 산업으로 지정, 공업용수 수요가 크게 증가",
|
||||
"주요 반도체 공장이 남부에 집중되어 있어 물 부족 문제가 지속적으로 발생, 2021년 가뭄 당시 산업단지 용수 공급에도 차질 발생",
|
||||
"정부는 2023년부터 산업용수 추가 요금을 부과하고, 농업용수 규제를 강화하는 등 용수 관리 정책 시행",
|
||||
"기후변화 대응을 위해 수자원 보호 및 재이용 정책을 지속적으로 확대",
|
||||
"산업용수 다소비 산업과 하수처리수를 재이용수 주요 공급원으로 활용 계획",
|
||||
],
|
||||
},
|
||||
],
|
||||
cards: [
|
||||
{
|
||||
title: "제4조.",
|
||||
desc: "물 소비 계획에 따른 재이용수 이용 관련 내용 (일정 비율 재이용수 사용 규정)",
|
||||
},
|
||||
{
|
||||
title: "제5조.",
|
||||
desc: "재이용수의 운영자, 사용자 관련 내용 (지자체에서 하수 무상공급 제공 가능)",
|
||||
},
|
||||
{
|
||||
title: "제7조.",
|
||||
desc: "재이용수 이용 범위, 이용량 재이용수 수질 기준 내용",
|
||||
},
|
||||
{
|
||||
title: "제8조.",
|
||||
desc: "재이용수 사업 주체, 책임 등에 관한 내용 (광역인 경우, 정부가 지자체 지정)",
|
||||
},
|
||||
],
|
||||
table: {
|
||||
headers: ["사업", "가격(NTD/㎥)", "가격(원/㎥)"],
|
||||
rows: [
|
||||
["Fongshan(가오슝)", "23.39", "1,038원"],
|
||||
["Linhai(가오슝)", "19.28", "858원"],
|
||||
["Anping(타이난)", "38.28", "1,699원"],
|
||||
["Yongkang(타이난)", "23.67", "1,050원"],
|
||||
["Rende(타이난)", "28.26", "1,254원"],
|
||||
["Shuinan(타이중)", "15.00", "668원"],
|
||||
["Quaitou(가오슝)", "35.44", "1,573원"],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
id: "example-global-singapore",
|
||||
title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/globalCase/singapore_01.png",
|
||||
alt: "싱가포르 NEWater 사례 1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/singapore_02.png",
|
||||
alt: "싱가포르 NEWater 사례 2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/singapore_03.png",
|
||||
alt: "싱가포르 NEWater 사례 3",
|
||||
},
|
||||
],
|
||||
blocks: [
|
||||
{
|
||||
title: "싱가포르 NEWater 비중 확대",
|
||||
items: [
|
||||
"2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설",
|
||||
"NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용",
|
||||
"2060년까지 NEWater 비중 55%까지 높이는 것이 싱가포르의 목표",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "싱가포르 재이용수 의무화 및 지원정책",
|
||||
items: [
|
||||
{ text: "공업용수 수요가 높은 산업에 대해 물 재이용을 의무화" },
|
||||
{ text: "반도체업계 전체 비 재이용수(non-domestic용수) 17% 차지 → 공업용수 수요 원인", depth: 1 },
|
||||
{ text: "2024년 1월 1일부터 연간 최소 60,000㎥를 소비하는 사업자는 재이용수 사용 의무화", depth: 1 },
|
||||
{ text: "반도체 공장의 경우 최소 50% 이상의 재이용수를 요구함(현재는 43% 정도)", depth: 1 },
|
||||
{ text: "재정 지원을 통해 재이용 활성화를 지원" },
|
||||
{ text: "재이용 설비를 기준으로 2025년까지 싱가포르 수자원공사의 2가지 금융상품 제공", depth: 1 },
|
||||
{ text: "1. Water Efficiency Fund", depth: 2 },
|
||||
{ text: "2. Industrial Water Solutions Demonstration Fund", depth: 2 },
|
||||
{ text: "2025년 기준 지원 한도 상향 → 물 재이용 활성 도모", depth: 1 },
|
||||
{ text: "투입비용 50% → 75% 지원 / WSDP: $400만 → $500만", depth: 3 },
|
||||
{ text: "물 효율이 높은 장비를 도입하는 데에도 최대 $300만 지원 가능", depth: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "특이사항",
|
||||
items: [
|
||||
{ text: "싱가포르는 천연 수자원이 많지 않아, 말레이시아 조호르와 1962년 맺은 협약으로 물을 수입해왔으며, 재이용수를 늘리기 위한 노력을 계속하고 있음" },
|
||||
{ text: "싱가포르는 국토 내에 호수와 하천이 적고 집수공간도 적어 빗물로 확보할 수 있는 수자원량은 전체 물 수요의 20%에 불과함", depth: 1 },
|
||||
|
||||
{ text: "싱가포르는 2002년부터 하수 재이용수인 NEWater를 개발, 비음용수와 간접음용수로 활용하고 있음" },
|
||||
{ text: "재이용수를 NEWater라는 이름으로 명명하고 NEWater에 대한 신뢰 확보를 위해 NEWater 방문자 센터도 건설함", depth: 1 },
|
||||
{ text: "비음용수로 많이 사용하며 건기에는 NEWater를 저수지에 추가하여 원수와 섞음. 원수는 상수도에서 처리 한 후 가정에 수돗물로 공급함", depth: 1 },
|
||||
{ text: "NEWater는 전용 파이프를 통해 주로 웨이퍼 제조 공장, 산업단지, 상업용 건물에서 산업 및 냉각수로 사용. 제일 많이 사용하는 곳은 웨이퍼 제조 공장", depth: 1 },
|
||||
|
||||
{ text: "2024년 4월 기준 1㎥ 당 공업용수 가격은 $1.66이며, Non-domestic 기준 NEWater 가격은 $2.41" },
|
||||
{ text: "공업용수 가격 $1.66은 수도요금(water tariff)과 수자원세(waterborne tax)로 구분됨. $1.66에는 $1만큼의 수자원세가 포함됨" },
|
||||
{ text: "2023년 싱가포르는 산업용수 수요가 높은 산업에 대해 물 재이용을 의무화했으며, 정부는 기금을 통해 물 재이용을 지원함" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
id: "example-global-australia",
|
||||
title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/globalCase/australia_01.png",
|
||||
alt: "호주 생활 속 물 재이용 1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/australia_02.png",
|
||||
alt: "호주 광역 물 재이용수 공급 2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/australia_03.png",
|
||||
alt: "호주 광역 물 재이용수 공급 3",
|
||||
},
|
||||
],
|
||||
blocks: [
|
||||
{
|
||||
title: "생활용수로 재이용수의 적극 활용",
|
||||
items: [
|
||||
"재이용수를 공원, 골프장 등의 관계용수로 공급(초록색 지역)",
|
||||
"Dual pipe로 재이용수를 특정 주거단지로 공급(보라색 지역)",
|
||||
"현재 7,000여 가구가 재이용수를 사용",
|
||||
"2030년까지 43,000가구로 확대 예정",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "호주 재이용수 광역 활용(퀸즈랜드주 브리즈번)",
|
||||
items: [
|
||||
{ text: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망" },
|
||||
{ text: "이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급" },
|
||||
|
||||
{
|
||||
text: (
|
||||
<>
|
||||
12개 댐, 32개 수처리 시설, 3개 재이용시설, 담수화 플랜트 1개, 저수지 28개와{" "}
|
||||
<span className="text-[#01ACE5]">
|
||||
600km 이상의 물 공급 관로를 건설
|
||||
</span>
|
||||
</>
|
||||
),
|
||||
depth: 1,
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<>
|
||||
지역적으로 강수량이 고르지 않아,{" "}
|
||||
<span className="text-[#01ACE5]">
|
||||
댐의 저수율이 불균등 할 때
|
||||
</span>{" "}
|
||||
지역의 물 공급을 보충하는 중요한 역할 수행
|
||||
</>
|
||||
),
|
||||
depth: 1,
|
||||
},
|
||||
|
||||
{ text: "안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능" },
|
||||
{ text: "공급단가는 수질과 운영조건에 따라 다르게 책정" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "특이사항",
|
||||
items: [
|
||||
"호주는 2000~2009년 강수량 감소로 밀레니엄 가뭄이 발생하여 심각한 물 부족 현상이 발생함",
|
||||
"호주는 해안 도시에 인구가 밀집하여 거주하고 있으며 인구 증가율이 타 선진국 대비 높아, 향후 도시에 공급할 수자원 확보가 필요함",
|
||||
"호주정부는 1990년대부터 하·폐수가 방류될 경우의 환경 피해에 대해 인식하기 시작하고 배출규제와 위원회를 구성하기 시작",
|
||||
"연방정부는 주에 인센티브를 주는 방식으로 물 정책을 장려하고 토지소유권과 물 권리를 구분하여 거래를 허용하고 물 재이용 활성화를 위한 Water Smart Australia 프로그램을 시행",
|
||||
"호주의 용수공급 체계는 영국과 미국의 특성들이 존재하지만, 주의 특성과 주정부의 정책 방향에 따라 주마다 많은 차이가 있음",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
id: "example-global-eu",
|
||||
title: "[EU] 법률을 제정, EU국가에서 물 재이용 유도",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/globalCase/eu_01.png",
|
||||
alt: "EU 물 재이용 기술 사례",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/globalCase/eu_02.png",
|
||||
alt: "EU 물 재이용 유도 사례",
|
||||
},
|
||||
],
|
||||
blocks: [
|
||||
{
|
||||
title: "물 재이용에 대한 법률을 제정(2020 제정, 2023 시행)",
|
||||
items: [
|
||||
"유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741)",
|
||||
"2023년부터 모든 EU국가에서 시행",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "유럽연합 회원국 물 재이용 지원정책",
|
||||
items: [
|
||||
{ text: "유럽연합 차원에서 다양한 물 재이용 프로그램 자금 지원" },
|
||||
|
||||
{ text: "2021년부터 3년간 Life RECYCLIO 프로그램 지원 → 세부내용 물 재이용 프로그램", depth: 1 },
|
||||
|
||||
{ text: "1. 환경 관련 사업에 자금을 지원하는 Life 프로그램의 일환으로 80만 유로를 지원", depth: 2 },
|
||||
{ text: "2. 식품·농업 분야 물 재이용 확대를 위해 기술 이전 및 평가 실시", depth: 2 },
|
||||
|
||||
{ text: "물 재이용 정책 수행을 위해 BOOST-INN 180만 유로를 지원", depth: 1 },
|
||||
|
||||
{ text: "프랑스, 이탈리아, 스페인 등 유럽 선진국 솔루션 공유", depth: 3 },
|
||||
|
||||
{ text: "EU산하 JRC(공동연구센터)에서 물 재이용 기술제공 지원" },
|
||||
|
||||
{ text: (
|
||||
<>
|
||||
‘물 재이용 회원 관리 실무 커뮤니티(Water Reuse Management Community of Practice)’ 시범사업 수행
|
||||
<br />
|
||||
유럽 전역에 성공적인 모델 활용 및 혁신적인 물 재이용 솔루션을 확장할 수 있는 기술을 제공
|
||||
</>
|
||||
),
|
||||
depth: 1
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "특이사항",
|
||||
items: [
|
||||
"2019년 기준 EU 인구의 38%가 물 부족의 영향을 받을 것으로 예상됨. 이 흐름대로면 2030년까지 유럽 유역 전반에 물 부족 문제가 발생하게 될 것으로 예상. 인구 증가, 도시화의 확대, 염분화 및 기후변화들이 수자원 문제들의 주요 요인이며, 식습관 변화와 산업용수 수요 증가 등으로 악화되고 있는 상황",
|
||||
"유럽의 물 부족 상황은 지역에 따라 다르게 나타나 스페인을 비롯한 남부유럽에서 특히 심각함. 전반적으로 북유럽에 비해 남유럽의 물 스트레스가 심하며, 특히 스페인과 포르투갈, 프랑스 동부지역의 물 스트레스가 심각함",
|
||||
"유럽 연합에서도 물 부족 문제를 인식하고 2000년 EU Water Framework Directive를 개발, 이후 2020년 재이용에 관한 법률을 제정",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function DefaultGlobalSection({
|
||||
item,
|
||||
}: {
|
||||
item: Extract<GlobalCase, { type: "default" }>;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={item.slides}
|
||||
/>
|
||||
|
||||
<ContentBlocks
|
||||
blocks={item.blocks}
|
||||
cards={item.cards}
|
||||
table={item.table}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function GlobalCaseSection() {
|
||||
const title = "해외 재이용 사례";
|
||||
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 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
|
||||
<span className="relative z-10">{title}</span>
|
||||
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
|
||||
</h2>
|
||||
</div>
|
||||
<div className="mt-10 pb-[120px]">
|
||||
<section id="example-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>
|
||||
<div className="mt-10 pb-[120px]">
|
||||
{globalCases.map((item, index) => (
|
||||
<section key={item.id} id={item.id} className="scroll-mt-[120px]">
|
||||
<SectionTitle>{item.title}</SectionTitle>
|
||||
|
||||
<Hr className="my-16" />
|
||||
{item.type === "overview" ? (
|
||||
<OverviewSection />
|
||||
) : (
|
||||
<DefaultGlobalSection item={item} />
|
||||
)}
|
||||
|
||||
<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>
|
||||
{index !== globalCases.length - 1 && <Hr className="my-20" />}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
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" />
|
||||
<span className="relative inline-block h-[16px] w-[27px] align-middle"> <span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" /> <span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" /></span>
|
||||
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
|
||||
</div>
|
||||
);
|
||||
@@ -128,7 +128,7 @@ export default function GreywaterExampleSection() {
|
||||
projectName="안산시 중수도 설치사업"
|
||||
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
|
||||
totalCost="81억원"
|
||||
treatment="MBR공법"
|
||||
treatment="MBLM공법"
|
||||
/>
|
||||
|
||||
<SupplyTable
|
||||
@@ -151,7 +151,7 @@ export default function GreywaterExampleSection() {
|
||||
projectName="용인시 중수도 설치사업"
|
||||
purpose="물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용"
|
||||
totalCost="40억원"
|
||||
treatment="MBR공법"
|
||||
treatment="MBLM공법"
|
||||
/>
|
||||
|
||||
<SupplyTable
|
||||
@@ -172,7 +172,7 @@ export default function GreywaterExampleSection() {
|
||||
projectName="평택시 중수도 설치사업"
|
||||
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
|
||||
totalCost="21억원"
|
||||
treatment="MBR공법"
|
||||
treatment="MBLM공법"
|
||||
/>
|
||||
|
||||
<SupplyTable
|
||||
|
||||
@@ -11,62 +11,215 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
// 구분선
|
||||
function BlockTitle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="relative inline-block h-[16px] w-[27px] align-middle">
|
||||
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
|
||||
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
|
||||
</span>
|
||||
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Hr({ className = "" }: { className?: string }) {
|
||||
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
|
||||
}
|
||||
|
||||
function InfoList({
|
||||
projectName,
|
||||
purpose,
|
||||
details,
|
||||
}: {
|
||||
projectName: string;
|
||||
purpose: string;
|
||||
details: string[];
|
||||
}) {
|
||||
return (
|
||||
<div className="mt-10 space-y-9">
|
||||
<div>
|
||||
<BlockTitle>사업명</BlockTitle>
|
||||
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
|
||||
<li>{projectName}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<BlockTitle>사업목적</BlockTitle>
|
||||
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
|
||||
<li>{purpose}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<BlockTitle>사업 주요 내용</BlockTitle>
|
||||
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
|
||||
{details.map((item, index) => (
|
||||
<li key={index}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type RainTableRow = {
|
||||
facility: string;
|
||||
storage: string;
|
||||
supply: string;
|
||||
pipe: string;
|
||||
};
|
||||
|
||||
function RainTable({ rows }: { rows: RainTableRow[] }) {
|
||||
return (
|
||||
<div className="mt-8 overflow-x-auto">
|
||||
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-3 text-[18px] font-medium text-white">
|
||||
시설명
|
||||
</th>
|
||||
<th className="border border-[#D8DDE3] bg-[#39B2BD] px-4 py-3 text-[18px] font-medium text-white">
|
||||
저류조용량(㎥)
|
||||
</th>
|
||||
<th className="border border-[#D8DDE3] bg-[#58B99A] px-4 py-3 text-[18px] font-medium text-white">
|
||||
빗물공급기
|
||||
</th>
|
||||
<th className="border border-[#D8DDE3] bg-[#70BF4B] px-4 py-3 text-[18px] font-medium text-white">
|
||||
빗물분사장치(m)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{rows.map((row, index) => (
|
||||
<tr key={`${row.facility}-${index}`}>
|
||||
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
|
||||
{row.facility}
|
||||
</td>
|
||||
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
|
||||
{row.storage}
|
||||
</td>
|
||||
|
||||
{index === 0 && (
|
||||
<td
|
||||
rowSpan={rows.length}
|
||||
className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]"
|
||||
>
|
||||
{row.supply}
|
||||
</td>
|
||||
)}
|
||||
|
||||
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
|
||||
{row.pipe}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RainExampleSection() {
|
||||
const title = "빗물 재이용 사례";
|
||||
|
||||
return (
|
||||
<div className="bg-white">
|
||||
{/* 상단 타이틀 */}
|
||||
<div className="inline-block">
|
||||
<h2 className="relative inline-block px-2 text-[28px] font-extrabold 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 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>
|
||||
|
||||
<section id="example-rain-suwon" className="mt-10 scroll-mt-[120px]">
|
||||
<SectionTitle>경기도 수원시</SectionTitle>
|
||||
<div className="mt-10 pb-[120px]">
|
||||
<section id="example-rain-suwon" className="scroll-mt-[120px]">
|
||||
<SectionTitle>경기도 수원시</SectionTitle>
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/rain/rain_01.png",
|
||||
alt: "수원시 사례 1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/rain/rain_02.png",
|
||||
alt: "수원시 사례 2",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{ src: "/images/landing/business/rain/suwon_01.png", alt: "수원시 빗물이용시설 사례 1" },
|
||||
{ src: "/images/landing/business/rain/suwon_02.png", alt: "수원시 빗물이용시설 사례 2" },
|
||||
{ src: "/images/landing/business/rain/suwon_03.png", alt: "수원시 빗물이용시설 사례 3" },
|
||||
{ src: "/images/landing/business/rain/suwon_04.png", alt: "수원시 빗물이용시설 사례 4" },
|
||||
{ src: "/images/landing/business/rain/suwon_05.png", alt: "수원시 빗물이용시설 사례 5" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Hr className="my-16" />
|
||||
<InfoList
|
||||
projectName="수원시 빗물이용시설 설치사업"
|
||||
purpose="월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물공급기를 설치하여 축서기 도시 열섬효과 및 도로 비산먼지 저감"
|
||||
details={[
|
||||
"총사업비 : 20억원",
|
||||
"위치 : 수원시 영통구, 팔달구 및 장안구 일원",
|
||||
]}
|
||||
/>
|
||||
|
||||
<section id="example-rain-jeju" className="scroll-mt-[120px]">
|
||||
<SectionTitle>제주특별자치도</SectionTitle>
|
||||
<div className="mt-9">
|
||||
<BlockTitle>사업량</BlockTitle>
|
||||
|
||||
<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>
|
||||
<RainTable
|
||||
rows={[
|
||||
{
|
||||
facility: "광교중학교",
|
||||
storage: "4,500",
|
||||
supply: "1대",
|
||||
pipe: "160",
|
||||
},
|
||||
{
|
||||
facility: "수원월드컵경기장",
|
||||
storage: "12,000",
|
||||
supply: "1대",
|
||||
pipe: "451",
|
||||
},
|
||||
{
|
||||
facility: "수원종합운동장",
|
||||
storage: "10,000",
|
||||
supply: "1대",
|
||||
pipe: "231",
|
||||
},
|
||||
{
|
||||
facility: "수원컨벤션센터",
|
||||
storage: "3,600",
|
||||
supply: "1대",
|
||||
pipe: "460",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Hr className="my-20" />
|
||||
|
||||
<section id="example-rain-jeju" className="scroll-mt-[120px]">
|
||||
<SectionTitle>제주특별자치도</SectionTitle>
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{ src: "/images/landing/business/rain/jeju_01.png", alt: "제주특별자치도 빗물이용시설 사례 1" },
|
||||
{ src: "/images/landing/business/rain/jeju_02.png", alt: "제주특별자치도 빗물이용시설 사례 2" },
|
||||
{ src: "/images/landing/business/rain/jeju_03.png", alt: "제주특별자치도 빗물이용시설 사례 3" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<InfoList
|
||||
projectName="제주특별자치도 중규모 빗물이용시설 설치사업"
|
||||
purpose="강우시 비닐하우스에서 발생하는 대체수자원인 빗물을 순환 활용하고자 하는 사업으로, 지하수 사용 저감과 집중호우시 도로와 농경지 침수피해 예방"
|
||||
details={[
|
||||
"총사업비 : 278억원",
|
||||
"위치 : 제주특별자치도 서귀포시 남원읍 위미리 일원",
|
||||
"사업량 : 저류조 7,000㎥, 배수갑 500㎡, 관로 21.7km",
|
||||
"사업기간 : 2024. 5. ~ 2028. 12.",
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,20 @@
|
||||
|
||||
import CaseImageSlider from "@/components/CaseImageSlider";
|
||||
|
||||
type SewageCase = {
|
||||
id: string;
|
||||
title: string;
|
||||
slides: {
|
||||
src: string;
|
||||
alt: string;
|
||||
}[];
|
||||
projectName: string;
|
||||
purpose: string[];
|
||||
method?: string[];
|
||||
details: string[];
|
||||
note?: string[];
|
||||
};
|
||||
|
||||
function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -11,245 +25,342 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
// 구분선
|
||||
function BlockTitle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="relative inline-block h-[16px] w-[27px] align-middle">
|
||||
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
|
||||
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
|
||||
</span>
|
||||
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Hr({ className = "" }: { className?: string }) {
|
||||
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
|
||||
}
|
||||
|
||||
function InfoBlock({
|
||||
title,
|
||||
items,
|
||||
}: {
|
||||
title: string;
|
||||
items?: string[];
|
||||
}) {
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BlockTitle>{title}</BlockTitle>
|
||||
<ul className="mt-4 list-disc space-y-2 pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
|
||||
{items.map((item, index) => (
|
||||
<li key={`${title}-${index}`}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoList({ item }: { item: SewageCase }) {
|
||||
return (
|
||||
<div className="mt-10 space-y-9">
|
||||
<InfoBlock title="사업명" items={[item.projectName]} />
|
||||
<InfoBlock title="사업목적" items={item.purpose} />
|
||||
<InfoBlock title="사업방식" items={item.method} />
|
||||
<InfoBlock title="사업 주요 내용" items={item.details} />
|
||||
<InfoBlock title="특이사항" items={item.note} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const sewageCases: SewageCase[] = [
|
||||
{
|
||||
id: "example-sewage-guri",
|
||||
title: "경기도 구리시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/guri_01.png",
|
||||
alt: "구리시 도로자동청소시스템",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/guri_02.png",
|
||||
alt: "구리시 도로자동청소시스템 위치도",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/guri_03.png",
|
||||
alt: "구리시 도로자동청소시스템 위치도2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/guri_04.png",
|
||||
alt: "구리시 도로자동청소시스템 원리",
|
||||
},
|
||||
],
|
||||
projectName: "구리시 도로자동청소시스템 설치사업",
|
||||
purpose: [
|
||||
"고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화",
|
||||
],
|
||||
details: [
|
||||
"총사업비 : 61억원",
|
||||
"위치 : 구리시 경춘로 및 갈매중앙로 일대 등",
|
||||
"관로 : 5.8km",
|
||||
"공급량 : 178㎥/일",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-bucheon",
|
||||
title: "경기도 부천시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/bucheon_01.png",
|
||||
alt: "부천시 하수 재이용수 생산시설",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/bucheon_02.png",
|
||||
alt: "부천시 하수처리수 재이용 사례",
|
||||
},
|
||||
],
|
||||
projectName: "부천시 하수 재이용수 생산시설 설치 운영",
|
||||
purpose: [
|
||||
"굴포하수처리시설 하수처리수 재이용수 생산시설 설치 운영을 통해 여월천 등 부천 관내 하천의 건천화 방지, 하천 생태계 보존 및 주민들에게 친수 공간 제공",
|
||||
],
|
||||
details: [
|
||||
"총사업비 : 212억원",
|
||||
"위치 : 부천시 여월천, 삼정천, 심곡천 및 시민의 강",
|
||||
"관로 : 23.6km",
|
||||
"공급량 : 50,000㎥/일",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-suwon",
|
||||
title: "경기도 수원시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/suwon_01.png",
|
||||
alt: "수원시 황구지천1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/suwon_02.png",
|
||||
alt: "수원시 황구지천2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/suwon_03.png",
|
||||
alt: "수원시 황구지천 지도",
|
||||
},
|
||||
],
|
||||
projectName: "수원시 황구지천 하수처리수 재이용사업",
|
||||
purpose: [
|
||||
"황구지천 공공하수처리시설 하수처리수 재이용사업으로 황구 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전",
|
||||
],
|
||||
details: [
|
||||
"총사업비 : 110억원",
|
||||
"위치 : 수원시 권선구 호매실천, 금곡천",
|
||||
"관로 : 3.9km",
|
||||
"공급량 : 25,000㎥/일(하수처리시설 용량 : 45,000㎥/일)",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-paju",
|
||||
title: "경기도 파주시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/paju_01.png",
|
||||
alt: "파주시 하수처리수 시설 전경1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/paju_02.png",
|
||||
alt: "파주시 하수처리수 시설 전경2",
|
||||
},
|
||||
],
|
||||
projectName: "파주시 하수처리수 재이용사업",
|
||||
purpose: [
|
||||
"운정 및 금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주LCD산업단지 내 3개 업체에 공업용수를 공급(LG디스플레이, 희성전자, 엔비텍)",
|
||||
"하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례",
|
||||
],
|
||||
method: ["민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)"],
|
||||
details: [
|
||||
"총사업비 : 651억원",
|
||||
"위치 : 파주시 금촌동 714번지 일원",
|
||||
"관로 : 13.6km",
|
||||
"공급량 : 40,000㎥/일(시설용량 : 41,200㎥/일)",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-pohang",
|
||||
title: "경상북도 포항시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/pohang_01.png",
|
||||
alt: "포항시 하수처리수 전경1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/pohang_02.png",
|
||||
alt: "포항시 하수처리수 전경2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/pohang_03.png",
|
||||
alt: "포항시 하수처리수 재이용 공급계통",
|
||||
},
|
||||
],
|
||||
projectName: "포항시 하수처리수 재이용사업",
|
||||
purpose: [
|
||||
"포항시 내 공단 개발 확대로 지속적이고 안정적인 공업용수 공급 추진(포스코, 동국산업 등)",
|
||||
],
|
||||
method: ["민간투자사업(운영사 : ㈜포웰, 한국수자원공사)"],
|
||||
details: [
|
||||
"총사업비 : 1,400억원",
|
||||
"위치 : 포항시 남구 상도동 125번지 일원",
|
||||
"관로 : 11.7km",
|
||||
"공급량 : 100,000㎥/일(시설용량 : 100,000㎥/일)",
|
||||
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-ulsan",
|
||||
title: "울산광역시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/ulsan_01.png",
|
||||
alt: "울산광역시 폐수처리수 전경1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/ulsan_02.png",
|
||||
alt: "울산광역시 폐수처리수 전경2",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/ulsan_03.png",
|
||||
alt: "울산광역시 폐수처리수 시설1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/ulsan_04.png",
|
||||
alt: "울산광역시 폐수처리수 시설2",
|
||||
},
|
||||
],
|
||||
projectName: "울산광역시 폐수처리수 재이용사업",
|
||||
purpose: [
|
||||
"’18년 갈수기시 낙동강 수질 악화로 울산석유화학단지 용수 공급 부족을 해소하기 위해 미포산단 내 한화케미칼 등 8개사 공업용수 공급",
|
||||
],
|
||||
method: ["민간투자사업(시행사 :울산화이텍워터(주))"],
|
||||
details: [
|
||||
"총사업비 : 1,729억원",
|
||||
"위치 : 울산광역시 울주군 청량읍 상개로 14 일원",
|
||||
"관로 : 11.7km",
|
||||
"공급량 : 17,000㎥/일(시설용량 : 86,000㎥/일)",
|
||||
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O, EDI)",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-yeosu",
|
||||
title: "전라남도 여수시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/yeosu_01.png",
|
||||
alt: "여수시 하수처리수 재이용시설1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/yeosu_02.png",
|
||||
alt: "여수시 하수처리수 재이용시설2",
|
||||
},
|
||||
],
|
||||
projectName: "여수시 하수처리수 재이용사업",
|
||||
purpose: [
|
||||
"수요자가 원하는 양질의 공업용수를 저렴하게 공급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선을 위한 여수국가산단 내 공업용수 공급(GS칼텍스, YNCC 등)",
|
||||
],
|
||||
method: ["민간투자사업(시행사 : 여수그린허브(주), 운영사 : 테크로스)"],
|
||||
details: [
|
||||
"총사업비 : 750억원",
|
||||
"위치 : 여수시 신월로 284 일원",
|
||||
"관로 : 18.6km",
|
||||
"공급량 : 50,000㎥/일(시설용량 : 50,000㎥/일)",
|
||||
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-boryeong",
|
||||
title: "충청남도 보령시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/boryeong_01.png",
|
||||
alt: "보령시 하수처리수 재이용시설1",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/boryeong_02.png",
|
||||
alt: "보령시 하수처리수 재이용시설2",
|
||||
},
|
||||
],
|
||||
projectName: "보령시 하수처리수 재이용사업",
|
||||
purpose: [
|
||||
"한국중부발전(주)의 보령 및 신보령화력발전소에 10,000㎥/일의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진",
|
||||
"국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(기후에너지환경부, 충청남도, 보령시, 한국중부발전(주))를 바탕으로 추진되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모",
|
||||
],
|
||||
method: ["재정사업(2017년 한국환경공단과 사업대행 위·수탁협약 체결)"],
|
||||
details: [
|
||||
"총사업비 : 315억원",
|
||||
"위치 : 보령시 대천방조제로 322 일원",
|
||||
"관로 : 8.7km",
|
||||
"공급량 : 10,000㎥/일(시설용량 : 10,000㎥/일)",
|
||||
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
|
||||
],
|
||||
note: [
|
||||
"국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "example-sewage-asan",
|
||||
title: "충청남도 아산시",
|
||||
slides: [
|
||||
{
|
||||
src: "/images/landing/business/sewage/asan_01.png",
|
||||
alt: "아산시 하수처리수 재이용시설",
|
||||
},
|
||||
{
|
||||
src: "/images/landing/business/sewage/asan_02.png",
|
||||
alt: "아산시 하수처리수 재이용 공정도",
|
||||
},
|
||||
],
|
||||
projectName: "아산시 하수처리수 재이용사업",
|
||||
purpose: [
|
||||
"신도시 개발에 따른 인구 급증으로 발생하는 하수를 재처리하여 탕정산업단지(삼성디스플레이)에 공업용수를 안정적으로 공급함으로써 지역기반 및 활성화를 도모",
|
||||
],
|
||||
method: ["민간투자사업(시행사 : 아산스마트원터, 운영사 : 한국수자원공사)"],
|
||||
details: [
|
||||
"총사업비 : 297억원",
|
||||
"위치 : 아산시 탕정면 갈산리 807 일원",
|
||||
"관로 : 4.2km",
|
||||
"공급량 : 27,000㎥/일(시설용량 : 45,000㎥/일)",
|
||||
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function SewageExampleSection() {
|
||||
const title = "하수처리수 재이용 사례";
|
||||
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 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">
|
||||
<section id="example-sewage-bolyeong" className="scroll-mt-[120px]">
|
||||
<SectionTitle>충청남도 보령시</SectionTitle>
|
||||
<div className="mt-10 pb-[120px]">
|
||||
{sewageCases.map((item, index) => (
|
||||
<section key={item.id} id={item.id} className="scroll-mt-[120px]">
|
||||
<SectionTitle>{item.title}</SectionTitle>
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/sewage/bolyeong_01.png",
|
||||
alt: "보령시 하수 재이용 1",
|
||||
},
|
||||
]}
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={item.slides}
|
||||
/>
|
||||
|
||||
<section className="mt-6">
|
||||
<SectionTitle>사업명</SectionTitle>
|
||||
<p className="mt-8 text-base text-gray-600">
|
||||
충남 보령 하수재이용
|
||||
<br />: 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해
|
||||
보령하수처리시설의 하수처리수 재이용 사업 추진
|
||||
</p>
|
||||
</section>
|
||||
<InfoList item={item} />
|
||||
|
||||
<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>
|
||||
{index !== sewageCases.length - 1 && <Hr className="my-20" />}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function GlobalCasePage() {
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/globalCase/overview_01.png",
|
||||
@@ -242,7 +242,7 @@ export default function GlobalCasePage() {
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/globalCase/taiwan_01.png",
|
||||
@@ -442,7 +442,7 @@ export default function GlobalCasePage() {
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/globalCase/australia_01.png",
|
||||
@@ -556,7 +556,7 @@ export default function GlobalCasePage() {
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/globalCase/singapore_01.png",
|
||||
@@ -727,7 +727,7 @@ export default function GlobalCasePage() {
|
||||
|
||||
<CaseImageSlider
|
||||
className="mt-8"
|
||||
maxWidthClassName="max-w-[760px]"
|
||||
maxWidthClassName="max-w-[1240px]"
|
||||
slides={[
|
||||
{
|
||||
src: "/images/landing/business/globalCase/eu_01.png",
|
||||
|
||||
@@ -27,15 +27,18 @@ const menus: BusinessSubNavMenu[] = [
|
||||
},
|
||||
{
|
||||
key: "example-sewage",
|
||||
label: "하수처리수 재이용 사례",
|
||||
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: "여수시" },
|
||||
{ id: "example-sewage-guri", label: "경기도 구리시" },
|
||||
{ id: "example-sewage-bucheon", label: "경기도 부천시" },
|
||||
{ id: "example-sewage-suwon", label: "경기도 수원시" },
|
||||
{ id: "example-sewage-paju", label: "경기도 파주시" },
|
||||
{ id: "example-sewage-pohang", label: "경기도 포항시" },
|
||||
{ id: "example-sewage-ulsan", label: "울산광역시" },
|
||||
{ id: "example-sewage-yeosu", label: "전라남도 여수시" },
|
||||
{ id: "example-sewage-boryeong", label: "충청남도 보령시" },
|
||||
{ id: "example-sewage-asan", label: "충청남도 아산시" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -45,8 +48,8 @@ const menus: BusinessSubNavMenu[] = [
|
||||
sections: [
|
||||
{ id: "example-global-overview", label: "사례 개요" },
|
||||
{ id: "example-global-taiwan", label: "대만" },
|
||||
{ id: "example-global-australia", label: "호주" },
|
||||
{ id: "example-global-singapore", label: "싱가포르" },
|
||||
{ id: "example-global-australia", label: "호주" },
|
||||
{ id: "example-global-eu", label: "EU" },
|
||||
],
|
||||
},
|
||||
@@ -71,13 +74,10 @@ export default function ExamplePage() {
|
||||
{/* 우측 앵커 네비 */}
|
||||
<BusinessSubNav menus={menus} offsetTop={120} />
|
||||
|
||||
<div className="max-w-[1300px] pb-24">
|
||||
<div className="max-w-[1320px] pb-24">
|
||||
<RainExampleSection />
|
||||
<Hr className="my-16" />
|
||||
<GreywaterExampleSection />
|
||||
<Hr className="my-16" />
|
||||
<SewageExampleSection />
|
||||
<Hr className="my-16" />
|
||||
<GlobalCaseSection />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function IntroPage() {
|
||||
<BusinessSubNav menus={menus} offsetTop={120} />
|
||||
|
||||
{/* 본문 */}
|
||||
<div className="max-w-[1300px] pb-24">
|
||||
<div className="max-w-[1320px] pb-24">
|
||||
<LawIntroSection />
|
||||
<RainIntroSection />
|
||||
<ReuseWaterIntroSection />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "../reuse.module.css";
|
||||
import { StoreProvider } from "../store";
|
||||
import TopNav from "@/components/TopNav";
|
||||
import Lnb from "@/components/Lnb";
|
||||
|
||||
export default function WithLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
@@ -9,10 +8,8 @@ export default function WithLayout({ children }: { children: React.ReactNode })
|
||||
<TopNav />
|
||||
|
||||
<div className="pt-[83px] flex">
|
||||
{/* ✅ 좌측 LNB */}
|
||||
<Lnb />
|
||||
|
||||
{/* ✅ 본문 */}
|
||||
{/* 본문 */}
|
||||
<main className="ml-[260px] w-full">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
@@ -23,6 +24,87 @@ function SourceText() {
|
||||
);
|
||||
}
|
||||
|
||||
function SewageIntroSummary() {
|
||||
const items = [
|
||||
{
|
||||
icon: "/images/landing/icon_cycle_water.png",
|
||||
title: "안정적인 재이용률 유지",
|
||||
desc: "연간 약 1,136백만m³의 하수처리수가 재이용되고 있으며, 약 15%에 달하는 높은 재이용률을 통해 수자원 선순환 체계를 구축하고 있습니다.",
|
||||
},
|
||||
{
|
||||
icon: "/images/landing/icon_water_flow.png",
|
||||
title: "메마른 하천을 살리는 물",
|
||||
desc: "재이용되는 물의 가장 많은 부분이 하천유지용수로 사용되어 도심 속 하천의 생태계를 보호하고 쾌적한 수변 환경을 조성합니다.",
|
||||
},
|
||||
{
|
||||
icon: "/images/landing/icon_location.png",
|
||||
title: "지역별 균형 있는 활용",
|
||||
desc: "전국적으로 고르게 시설이 확충되고 있으며, 특히 경기 지역을 중심으로 대규모 공업용수 공급망을 구축하여 지역 경제와 환경 보호를 동시에 실현하고 있습니다.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mb-[70px] px-[60px]">
|
||||
<div className="grid grid-cols-[1fr_400px] items-center gap-[136px]">
|
||||
{/* 좌측 */}
|
||||
<div>
|
||||
<h3 className="text-[40px] font-extrabold leading-[1.35] tracking-[-0.1em] 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">
|
||||
<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]",
|
||||
@@ -44,220 +126,224 @@ export default function SewageStatusSection() {
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-10">
|
||||
<div className="mt-[70px] pb-[120px]">
|
||||
{/* 1) 연도별 하수처리수 재이용 현황 */}
|
||||
<section id="status-treated-year" className="mt-10 scroll-mt-[120px]">
|
||||
<SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle>
|
||||
<section>
|
||||
{/* 인트로 */}
|
||||
<SewageIntroSummary />
|
||||
|
||||
<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`}
|
||||
<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}`}
|
||||
>
|
||||
{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 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
구분
|
||||
</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>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</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);
|
||||
|
||||
<SourceText />
|
||||
</div>
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Hr className="my-11" />
|
||||
|
||||
{/* 2) 용도별 하수처리수 재이용 현황 */}
|
||||
{/* 2) 연도별 하수처리수 재이용 현황 */}
|
||||
<section id="status-treated-usage" className="scroll-mt-[120px]">
|
||||
<SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle>
|
||||
<SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle>
|
||||
|
||||
<div className="mt-8">
|
||||
<div className={tableStyles.container}>
|
||||
|
||||
@@ -58,7 +58,7 @@ const menus: BusinessSubNavMenu[] = [
|
||||
},
|
||||
{
|
||||
id: "status-treated-usage",
|
||||
label: "용도별 하수처리수 재이용 현황",
|
||||
label: "연도별 하수처리수 재이용 현황",
|
||||
},
|
||||
{
|
||||
id: "status-treated-facility",
|
||||
@@ -86,7 +86,7 @@ export default function StatusPage() {
|
||||
{/* 우측 앵커 네비 */}
|
||||
<BusinessSubNav menus={menus} offsetTop={120} />
|
||||
|
||||
<div className="max-w-[1300px] pb-24">
|
||||
<div className="max-w-[1320px] pb-24">
|
||||
<RainStatusSection />
|
||||
<GreywaterStatusSection />
|
||||
<SewageStatusSection />
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function Cases() {
|
||||
|
||||
<div className="mt-14">
|
||||
<a
|
||||
href="/reuse/cases/rain"
|
||||
href="/example"
|
||||
className="
|
||||
inline-flex items-center justify-center gap-3
|
||||
w-[260px] h-[52px]
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function Hero() {
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-items-center gap-[40px] mx-auto max-w-[1520px] px-6 text-center text-white">
|
||||
<h1 className="text-5xl font-bold tracking-tight">물 재이용</h1>
|
||||
<p className="text-xl font-medium leading-6 text-white/85 max-w-[720px] mx-auto">
|
||||
<p className="text-2xl font-medium leading-8 text-white/85 max-w-[720px] mx-auto">
|
||||
빗물, 오수, 하수처리수, 폐수처리수, 발전소 온배수를 재이용처리하여<br />
|
||||
생활, 공업, 농업, 조경, 하천유지 등의 용도로 재이용합니다.
|
||||
</p>
|
||||
|
||||
@@ -20,25 +20,25 @@ const INTRO_CARDS: IntroCardItem[] = [
|
||||
title: "물 재이용 법",
|
||||
desc: "물의 재이용 촉진 및 지원에 관한 법률을 시행하고, 일부 법률은 개정되었습니다.",
|
||||
image: card01,
|
||||
href: "/reuse/intro/law",
|
||||
href: "/intro#law-plan",
|
||||
},
|
||||
{
|
||||
title: "친환경 수자원 빗물",
|
||||
desc: "가뭄을 극복하고 홍수를 예방하기 위해 비가 내릴 때 빗물을 모아두었다가 사용합니다.",
|
||||
image: card02,
|
||||
href: "/reuse/intro/rain",
|
||||
href: "/intro#rain-intro",
|
||||
},
|
||||
{
|
||||
title: "알뜰하게 재이용되는 중수",
|
||||
desc: "한번 사용한 물을 중수도 시설을 통해 재생하여 개별적 또는 지역적으로 재이용할 수 있습니다.",
|
||||
image: card03,
|
||||
href: "/reuse/intro/greywater",
|
||||
href: "/intro#greywater-intro",
|
||||
},
|
||||
{
|
||||
title: "하수처리수 재이용",
|
||||
desc: "사용하고 버려진 공공하수처리장으로 모여진 물을 용도에 맞는 처리 후 재이용 할 수 있습니다.",
|
||||
image: card04,
|
||||
href: "/reuse/intro/treated",
|
||||
href: "/intro#treatedwater-intro",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function IntroCards() {
|
||||
<h3 className="text-lg font-semibold tracking-tight">
|
||||
{card.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-white/90 line-clamp-3">
|
||||
<p className="mt-2 text-sm leading-relaxed text-white/60 line-clamp-3">
|
||||
{card.desc}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
@@ -9,167 +8,151 @@ type Slide = { src: string; alt?: string };
|
||||
type Props = {
|
||||
slides: Slide[];
|
||||
className?: string;
|
||||
maxWidthClassName?: string; // 예: "max-w-[760px]"
|
||||
maxWidthClassName?: string;
|
||||
autoPlayMs?: number;
|
||||
imageHeightClassName?: string;
|
||||
};
|
||||
|
||||
const PER_VIEW = 2;
|
||||
|
||||
export default function CaseImageSlider({
|
||||
slides,
|
||||
className = "",
|
||||
maxWidthClassName = "max-w-[760px]",
|
||||
maxWidthClassName = "max-w-[700px]",
|
||||
autoPlayMs = 4500,
|
||||
imageHeightClassName = "h-[340px]",
|
||||
}: Props) {
|
||||
const safeSlides = useMemo(() => slides.filter(Boolean), [slides]);
|
||||
const [idx, setIdx] = useState(0);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
|
||||
const startX = useRef(0);
|
||||
const deltaX = useRef(0);
|
||||
|
||||
const total = safeSlides.length;
|
||||
const canSlide = total > 1;
|
||||
const pageCount = Math.ceil(safeSlides.length / PER_VIEW);
|
||||
const canSlide = pageCount > 1;
|
||||
|
||||
const go = (dir: number) => {
|
||||
const goTo = (nextPage: number) => {
|
||||
if (!canSlide) return;
|
||||
setIdx((prev) => {
|
||||
const next = prev + dir;
|
||||
if (next < 0) return total - 1;
|
||||
if (next >= total) return 0;
|
||||
return next;
|
||||
});
|
||||
|
||||
if (nextPage < 0) setPage(pageCount - 1);
|
||||
else if (nextPage >= pageCount) setPage(0);
|
||||
else setPage(nextPage);
|
||||
};
|
||||
|
||||
// ← → 키보드 지원(선택)
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (!canSlide) return;
|
||||
if (e.key === "ArrowLeft") go(-1);
|
||||
if (e.key === "ArrowRight") go(1);
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [canSlide, total]);
|
||||
const goNext = () => goTo(page + 1);
|
||||
const goPrev = () => goTo(page - 1);
|
||||
|
||||
// 드래그(마우스/터치)로 넘기기
|
||||
const onPointerDown = (e: React.PointerEvent) => {
|
||||
// ✅ 자동롤링 (hover 시 정지 포함)
|
||||
useEffect(() => {
|
||||
if (!canSlide || dragging || isHover) return;
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
setPage((prev) => (prev + 1 >= pageCount ? 0 : prev + 1));
|
||||
}, autoPlayMs);
|
||||
|
||||
return () => window.clearInterval(timer);
|
||||
}, [canSlide, dragging, isHover, pageCount, autoPlayMs]);
|
||||
|
||||
// ✅ swipe
|
||||
const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!canSlide) return;
|
||||
|
||||
setDragging(true);
|
||||
startX.current = e.clientX;
|
||||
deltaX.current = 0;
|
||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
};
|
||||
|
||||
const onPointerMove = (e: React.PointerEvent) => {
|
||||
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragging) return;
|
||||
deltaX.current = e.clientX - startX.current;
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (!dragging) return;
|
||||
|
||||
setDragging(false);
|
||||
|
||||
const threshold = 60; // 넘김 감도
|
||||
if (deltaX.current > threshold) go(-1);
|
||||
else if (deltaX.current < -threshold) go(1);
|
||||
const threshold = 50;
|
||||
|
||||
if (deltaX.current > threshold) goPrev();
|
||||
if (deltaX.current < -threshold) goNext();
|
||||
|
||||
deltaX.current = 0;
|
||||
};
|
||||
|
||||
if (!total) return null;
|
||||
if (!safeSlides.length) return null;
|
||||
|
||||
return (
|
||||
<section className={className}>
|
||||
<div className={`mx-auto ${maxWidthClassName}`}>
|
||||
<div className="relative">
|
||||
{/* 좌측 버튼 */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => go(-1)}
|
||||
className="absolute left-[-60px] top-1/2 -translate-y-1/2 z-10
|
||||
w-[80px] h-[80px] rounded-full border border-black/20 bg-white
|
||||
grid place-items-center shadow-sm hover:bg-black/[0.03]"
|
||||
>
|
||||
<Image
|
||||
src="/images/landing/icon_left_arrow.png"
|
||||
alt="이전"
|
||||
width={12}
|
||||
height={22}
|
||||
className="w-[12px] h-[22px]"
|
||||
/>
|
||||
</button>
|
||||
|
||||
|
||||
{/* 우측 버튼 */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => go(1)}
|
||||
className="absolute right-[-60px] top-1/2 -translate-y-1/2 z-10
|
||||
w-[80px] h-[80px] rounded-full border border-black/20 bg-white
|
||||
grid place-items-center shadow-sm hover:bg-black/[0.03]"
|
||||
>
|
||||
<Image
|
||||
src="/images/landing/icon_right_arrow.png"
|
||||
alt="다음"
|
||||
width={12}
|
||||
height={22}
|
||||
className="w-[12px] h-[22px]"
|
||||
/>
|
||||
</button>
|
||||
|
||||
|
||||
{/* 뷰포트 */}
|
||||
<div
|
||||
className="overflow-hidden select-none touch-pan-y cursor-grab active:cursor-grabbing"
|
||||
onMouseEnter={() => setIsHover(true)} // ✅ hover 시작 → 정지
|
||||
onMouseLeave={() => setIsHover(false)} // ✅ hover 끝 → 재시작
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerUp}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
"overflow-hidden",
|
||||
"select-none touch-pan-y",
|
||||
"cursor-grab active:cursor-grabbing",
|
||||
"w-[583px] h-[400px] mx-auto",
|
||||
].join(" ")}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerUp}
|
||||
className="flex transition-transform duration-500 ease-out"
|
||||
style={{
|
||||
transform: `translateX(-${page * 100}%)`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex transition-transform duration-300 ease-out"
|
||||
style={{ transform: `translateX(-${idx * 100}%)` }}
|
||||
>
|
||||
{safeSlides.map((s, i) => (
|
||||
<div key={`${s.src}-${i}`} className="min-w-full">
|
||||
<Image
|
||||
src={s.src}
|
||||
alt={s.alt ?? `slide-${i + 1}`}
|
||||
width={583}
|
||||
height={400}
|
||||
className="w-full h-full object-contain"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
draggable={false}
|
||||
/>
|
||||
{Array.from({ length: pageCount }).map((_, pageIndex) => {
|
||||
const pageSlides = safeSlides.slice(
|
||||
pageIndex * PER_VIEW,
|
||||
pageIndex * PER_VIEW + PER_VIEW
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={pageIndex}
|
||||
className="grid min-w-full grid-cols-2 gap-3"
|
||||
>
|
||||
{pageSlides.map((slide, i) => (
|
||||
<div
|
||||
key={`${slide.src}-${i}`}
|
||||
className={`relative ${imageHeightClassName} overflow-hidden rounded-[20px] bg-neutral-100`}
|
||||
>
|
||||
<Image
|
||||
src={slide.src}
|
||||
alt={slide.alt ?? `slide-${pageIndex * PER_VIEW + i + 1}`}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 768px) 50vw, 340px"
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 도트 */}
|
||||
{canSlide && (
|
||||
<div className="mt-3 flex items-center justify-center gap-2">
|
||||
{safeSlides.map((_, i) => {
|
||||
const active = i === idx;
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
onClick={() => setIdx(i)}
|
||||
aria-label={`${i + 1}번 슬라이드`}
|
||||
className={[
|
||||
"h-2 w-2 rounded-full",
|
||||
active ? "bg-black/70" : "bg-black/20 hover:bg-black/35",
|
||||
].join(" ")}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div className="mt-5 flex items-center justify-center gap-[6px]">
|
||||
{Array.from({ length: pageCount }).map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
onClick={() => setPage(i)}
|
||||
className={[
|
||||
"h-[8px] w-[8px] rounded-full transition-all",
|
||||
page === i ? "bg-[#18BCEB]" : "bg-[#D8DDE3]",
|
||||
].join(" ")}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import Image from "next/image";
|
||||
* - BUGFIX 2) 슬라이드 중(busy) clipWidth 변화로 트랙 스냅(-W) 덮어쓰지 않게 분리
|
||||
*/
|
||||
|
||||
type TabKey = "rain" | "reuse" | "sewage" | "global";
|
||||
type TabKey = "rain" | "greywater" | "sewage" | "global";
|
||||
|
||||
type CaseItem = {
|
||||
id: string;
|
||||
@@ -34,7 +34,7 @@ type CaseItem = {
|
||||
|
||||
const TABS: { key: TabKey; label: string }[] = [
|
||||
{ key: "rain", label: "빗물 재이용 사례" },
|
||||
{ key: "reuse", label: "중수 재이용 사례" },
|
||||
{ key: "greywater", label: "중수 재이용 사례" },
|
||||
{ key: "sewage", label: "하수 재이용 사례" },
|
||||
{ key: "global", label: "해외 재이용 사례" },
|
||||
];
|
||||
@@ -43,113 +43,143 @@ const TABS: { key: TabKey; label: string }[] = [
|
||||
const MOCK: Record<TabKey, CaseItem[]> = {
|
||||
rain: [
|
||||
{
|
||||
id: "rain-0",
|
||||
title: "경기도 수원시 빗물이용과 중수도",
|
||||
desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…",
|
||||
imageSrc: "/images/landing/rain_01.png",
|
||||
href: "/example/rain/#suwon",
|
||||
id: "rain-1",
|
||||
title: "경기도 수원시 빗물 재이용",
|
||||
desc: "월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물공급기를 설치하여 축서기 도시 열섬효과 및 도로 비산먼지 저감",
|
||||
imageSrc: "/images/landing/business/rain/suwon_01.png",
|
||||
href: "/example#example-rain-suwon",
|
||||
},
|
||||
{
|
||||
id: "rain-2",
|
||||
title: "제주특별자치도 빗물 재이용",
|
||||
desc: "강우시 비닐하우스에서 발생하는 대체수자원인 빗물을 순환 활용하고자 하는 사업으로, 지하수 사용 저감과 집중호우시 도로와 농경지 침수피해 예방",
|
||||
imageSrc: "/images/landing/business/rain/jeju_01.png",
|
||||
href: "/example#example-rain-jeju",
|
||||
},
|
||||
],
|
||||
greywater: [
|
||||
{
|
||||
id: "greywater-1",
|
||||
title: "경기도 안산시 중수 재이용",
|
||||
desc: "수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현을 위해 안산시 중수 재이용 사업 추진, 공공기관 및 산업체에 중수 공급",
|
||||
imageSrc: "/images/landing/business/greywater/no_img.jpg",
|
||||
href: "/example#example-greywater-ansan",
|
||||
},
|
||||
{
|
||||
id: "greywater-2",
|
||||
title: "경기도 용인시 중수 재이용",
|
||||
desc: "물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용과 지속가능성 도모를 위해 용인시 중수 재이용 사업 추진, 공공기관 및 산업체에 중수 공급",
|
||||
imageSrc: "/images/landing/business/greywater/no_img.jpg",
|
||||
href: "/example#example-greywater-yongin",
|
||||
},
|
||||
{
|
||||
id: "greywater-3",
|
||||
title: "경기도 평택시 중수 재이용",
|
||||
desc: "수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현을 위해 평택시 중수 재이용 사업 추진, 공공기관 및 산업체에 중수 공급",
|
||||
imageSrc: "/images/landing/business/greywater/no_img.jpg",
|
||||
href: "/example#example-greywater-pyeongtaek",
|
||||
},
|
||||
// {
|
||||
// id: "rain-2",
|
||||
// title: "경기도 수원시 빗물이용과 중수도",
|
||||
// desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…",
|
||||
// imageSrc: "/images/landing/rain_01.png",
|
||||
// href: "#",
|
||||
// },
|
||||
],
|
||||
reuse: Array.from({ length: 6 }).map((_, i) => ({
|
||||
id: `reuse-${i}`,
|
||||
title: `중수 재이용 사례 ${i + 1}`,
|
||||
desc: "설명 문구가 들어갑니다. (최대 2~3줄) …",
|
||||
imageSrc: "/images/landing/sample_case.png",
|
||||
href: "#",
|
||||
})),
|
||||
sewage: [
|
||||
{
|
||||
id: "sewage-1",
|
||||
title: "경기도 파주시 하수 재이용",
|
||||
desc: "운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에...",
|
||||
imageSrc: "/images/landing/sewage_01.png",
|
||||
href: "/example/sewage/#paju",
|
||||
title: "구리시 하수 재이용",
|
||||
desc: "고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화",
|
||||
imageSrc: "/images/landing/business/sewage/guri_01.png",
|
||||
href: "/example#example-sewage-guri",
|
||||
},
|
||||
{
|
||||
id: "sewage-2",
|
||||
title: "충남보령 하수재이용",
|
||||
desc: "보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업...",
|
||||
imageSrc: "/images/landing/sewage_02.png",
|
||||
href: "/example/sewage/#bolyeong",
|
||||
title: "부천시 하수재이용",
|
||||
desc: "굴포하수처리시설 하수처리수 재이용수 생산시설 설치 운영을 통해 여월천 등 부천 관내 하천의 건천화 방지, 하천 생태계 보존 및 주민들에게 친수 공간 제공",
|
||||
imageSrc: "/images/landing/business/sewage/bucheon_01.png",
|
||||
href: "/example#example-sewage-bucheon",
|
||||
},
|
||||
{
|
||||
id: "sewage-3",
|
||||
title: "여수시 하수처리수 재이용",
|
||||
desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로...",
|
||||
imageSrc: "/images/landing/sewage_03.png",
|
||||
href: "/example/sewage/#yeosu",
|
||||
title: "수원시 하수처리수 재이용",
|
||||
desc: "황구지천 공공하수처리시설 하수처리수 재이용사업으로 황구 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전",
|
||||
imageSrc: "/images/landing/business/sewage/suwon_01.png",
|
||||
href: "/example#example-sewage-suwon",
|
||||
},
|
||||
{
|
||||
id: "sewage-4",
|
||||
title: "청주시 하수 재이용 시설",
|
||||
desc: "청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO)...",
|
||||
imageSrc: "/images/landing/sewage_04.png",
|
||||
href: "/example/sewage/#cheongju",
|
||||
title: "파주시 하수 재이용 시설",
|
||||
desc: "운정 및 금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주LCD산업단지 내 3개 업체에 공업용수를 공급(LG디스플레이, 희성전자, 엔비텍)",
|
||||
imageSrc: "/images/landing/business/sewage/paju_01.png",
|
||||
href: "/example#example-sewage-paju",
|
||||
},
|
||||
{
|
||||
id: "sewage-5",
|
||||
title: "수원시 황구지천 하천유지용수",
|
||||
desc: "황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로...",
|
||||
imageSrc: "/images/landing/sewage_05.png",
|
||||
href: "/example/sewage/#hwanggu",
|
||||
title: "포항시 하수 재이용 시설",
|
||||
desc: "포항시 내 공단 개발 확대로 지속적이고 안정적인 공업용수 공급 추진(포스코, 동국산업 등), 포항하수처리장 하수처리수를 재처리하여 공업용수로 공급",
|
||||
imageSrc: "/images/landing/business/sewage/pohang_01.png",
|
||||
href: "/example#example-sewage-pohang",
|
||||
},
|
||||
{
|
||||
id: "sewage-6",
|
||||
title: "구리시 도로자동청소시스템",
|
||||
desc: "고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용...",
|
||||
imageSrc: "/images/landing/sewage_06.png",
|
||||
href: "/example/sewage/#guri",
|
||||
title: "울산광역시 하수 재이용 시설",
|
||||
desc: "18년 갈수기시 낙동강 수질 악화로 울산석유화학단지 용수 공급 부족을 해소하기 위해 미포산단 내 한화케미칼 등 8개사 공업용수 공급",
|
||||
imageSrc: "/images/landing/business/sewage/ulsan_01.png",
|
||||
href: "/example#example-sewage-ulsan",
|
||||
},
|
||||
// ...Array.from({ length: 6 }).map((_, i) => ({
|
||||
// id: `sewage-x-${i}`,
|
||||
// title: `하수 재이용 사례 ${i + 4}`,
|
||||
// desc: "설명 문구가 들어갑니다. (최대 2~3줄) …",
|
||||
// imageSrc: "/images/landing/sample_case.png",
|
||||
// href: "#",
|
||||
// })),
|
||||
{
|
||||
id: "sewage-7",
|
||||
title: "여수시 하수 재이용 시설",
|
||||
desc: "수요자가 원하는 양질의 공업용수를 저렴하게 공급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선을 위한 여수국가산단 내 공업용수 공급(GS칼텍스, YNCC 등)",
|
||||
imageSrc: "/images/landing/business/sewage/yeosu_01.png",
|
||||
href: "/example#example-sewage-yeosu",
|
||||
},
|
||||
{
|
||||
id: "sewage-8",
|
||||
title: "보령시 하수 재이용 시설",
|
||||
desc: "한국중부발전(주)의 보령 및 신보령화력발전소에 10,000㎥/일의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진, 하수처리수 재이용을 통한 수자원 절약 및 지역 물 부족 해소",
|
||||
imageSrc: "/images/landing/business/sewage/boryeong_01.png",
|
||||
href: "/example#example-sewage-boryeong",
|
||||
},
|
||||
{
|
||||
id: "sewage-9",
|
||||
title: "아산시 하수 재이용 시설",
|
||||
desc: "신도시 개발에 따른 인구 급증으로 발생하는 하수를 재처리하여 탕정산업단지(삼성디스플레이)에 공업용수를 안정적으로 공급함으로써 지역기반 및 활성화를 도모",
|
||||
imageSrc: "/images/landing/business/sewage/asan_01.png",
|
||||
href: "/example#example-sewage-asan",
|
||||
}
|
||||
],
|
||||
global: [
|
||||
{
|
||||
id: "global-1",
|
||||
title: "해외 재이용 사례 개요",
|
||||
desc: "2024년 하수도 통계 기준 15.6%이며, 제2차 물 재이용 기본 계획을 고려한 2030년 목표는 17%, 우리나라의 하수 처리 재이용률은 15% 수준...",
|
||||
imageSrc: "/images/landing/overview_01.png",
|
||||
href: "/example/globalCase/overview",
|
||||
desc: "전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐. 이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편",
|
||||
imageSrc: "/images/landing/business/globalCase/overview_01.png",
|
||||
href: "/example#example-global-overview",
|
||||
},
|
||||
{
|
||||
id: "global-2",
|
||||
title: "[대만] 국가 주도의 공업용수 공급",
|
||||
desc: "대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑), 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능...",
|
||||
imageSrc: "/images/landing/taiwan_01.png",
|
||||
href: "/example/globalCase/taiwan",
|
||||
desc: "수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결. 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등",
|
||||
imageSrc: "/images/landing/business/globalCase/taiwan_01.png",
|
||||
href: "/example#example-global-taiwan",
|
||||
},
|
||||
{
|
||||
id: "global-3",
|
||||
title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급",
|
||||
desc: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망, 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급...",
|
||||
imageSrc: "/images/landing/australia_01.png",
|
||||
href: "/example/globalCase/australia",
|
||||
title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화",
|
||||
desc: "2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설. NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용",
|
||||
imageSrc: "/images/landing/business/globalCase/singapore_01.png",
|
||||
href: "/example#example-global-singapore",
|
||||
},
|
||||
{
|
||||
id: "global-4",
|
||||
title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화",
|
||||
desc: "2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설, NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용...",
|
||||
imageSrc: "/images/landing/singapore_01.png",
|
||||
href: "/example/globalCase/singapore",
|
||||
title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급",
|
||||
desc: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망. 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급",
|
||||
imageSrc: "/images/landing/business/globalCase/australia_01.png",
|
||||
href: "/example#example-global-australia",
|
||||
},
|
||||
{
|
||||
id: "global-5",
|
||||
title: "[EU] 법률을 제정, EU국가에서 물 재이용 유도",
|
||||
desc: "유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741), 2023년부터 모든 EU국가에서 시행...",
|
||||
imageSrc: "/images/landing/eu_01.png",
|
||||
href: "/example/globalCase/eu",
|
||||
desc: "유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741), 2023년부터 모든 EU국가에서 시행",
|
||||
imageSrc: "/images/landing/business/globalCase/eu_01.png",
|
||||
href: "/example#example-global-eu",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -249,7 +279,7 @@ export default function CaseTabsCarousel() {
|
||||
// 탭별 마지막 그룹 인덱스 기억
|
||||
const tabIndexRef = useRef<Record<TabKey, number>>({
|
||||
rain: 0,
|
||||
reuse: 0,
|
||||
greywater: 0,
|
||||
sewage: 0,
|
||||
global: 0,
|
||||
});
|
||||
|
||||
@@ -1,68 +1,55 @@
|
||||
"use client";
|
||||
|
||||
export type LnbMenuItem = {
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export type LnbMenu = {
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export type LnbConfig = {
|
||||
title: string;
|
||||
menus: LnbMenuItem[];
|
||||
type Props = {
|
||||
menus?: LnbMenu[];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const LNB_MAP = {
|
||||
intro: {
|
||||
title: "소개",
|
||||
menus: [
|
||||
{ label: "물 재이용 법", href: "/intro/law" },
|
||||
{ label: "빗물 이용시설", href: "/intro/rain" },
|
||||
{ label: "중수도", href: "/intro/reuseWater" },
|
||||
{ label: "하수처리수", href: "/intro/treated" },
|
||||
],
|
||||
},
|
||||
const defaultMenus: LnbMenu[] = [
|
||||
{ label: "수요처 등록", href: "/apply/dmdco" },
|
||||
{ label: "수요정보등록", href: "/apply/register" },
|
||||
{ label: "수요신청현황", href: "/apply/status" },
|
||||
];
|
||||
|
||||
status: {
|
||||
title: "현황",
|
||||
menus: [
|
||||
{ label: "빗물이용시설(현황)", href: "/status/rain" },
|
||||
{ label: "중수도(현황)", href: "/status/greywater" },
|
||||
{ label: "하수처리수 재이용시설(현황)", href: "/status/sewage" },
|
||||
],
|
||||
},
|
||||
export default function Lnb({ menus, className = "" }: Props) {
|
||||
const pathname = usePathname();
|
||||
const navMenus = menus ?? defaultMenus;
|
||||
|
||||
case: {
|
||||
title: "사례",
|
||||
menus: [
|
||||
{ label: "빗물 재이용 사례", href: "/example/rain" },
|
||||
{ label: "중수 재이용 사례", href: "/example/greywater" },
|
||||
{ label: "하수 재이용 사례", href: "/example/sewage" },
|
||||
{ label: "해외 재이용 사례", href: "/example/globalCase" },
|
||||
],
|
||||
},
|
||||
return (
|
||||
<aside className={`fixed right-2 top-[110px] z-50 w-[260px] ${className}`}>
|
||||
<ul className="space-y-1">
|
||||
{navMenus.map((menu) => {
|
||||
const isActive =
|
||||
pathname === menu.href ||
|
||||
pathname.startsWith(`${menu.href}/`);
|
||||
|
||||
apply: {
|
||||
title: "수요신청",
|
||||
menus: [
|
||||
{ label: "수요처등록", href: "/apply/dmdco" },
|
||||
{ label: "수요정보등록", href: "/apply/register" },
|
||||
{ label: "수요신청현황", href: "/apply/status" },
|
||||
],
|
||||
},
|
||||
} satisfies Record<string, LnbConfig>;
|
||||
|
||||
export function getCategory(pathname: string): keyof typeof LNB_MAP {
|
||||
if (pathname.startsWith("/intro")) return "intro";
|
||||
if (pathname.startsWith("/status")) return "status";
|
||||
if (pathname.startsWith("/example")) return "case";
|
||||
if (pathname.startsWith("/apply")) return "apply";
|
||||
return "intro";
|
||||
}
|
||||
|
||||
export function isActiveMenu(pathname: string, href: string) {
|
||||
return pathname === href || pathname.startsWith(`${href}/`);
|
||||
}
|
||||
|
||||
// 왼쪽 메뉴는 더 이상 사용 안 함
|
||||
export default function Lnb() {
|
||||
return null;
|
||||
return (
|
||||
<li key={menu.href}>
|
||||
<Link
|
||||
href={menu.href}
|
||||
className={`
|
||||
block text-[16px] font-medium transition px-5 py-2
|
||||
${
|
||||
isActive
|
||||
? "text-[#00AEEF]"
|
||||
: "text-[#767676] hover:text-[#00AEEF]"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{menu.label}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import { useDmStore } from "@/app/store";
|
||||
const LS_USER_KEY = "user";
|
||||
|
||||
const DEFAULT_NAV_ITEMS: Pick<MenuItem, "menuCd" | "menuKornNm" | "urlPath" | "newWndwYn">[] = [
|
||||
{ menuCd: 1, menuKornNm: "물 재이용 소개", urlPath: "/intro/law", newWndwYn: "N" },
|
||||
{ menuCd: 2, menuKornNm: "물 재이용 현황", urlPath: "/status/rain", newWndwYn: "N" },
|
||||
{ menuCd: 3, menuKornNm: "물 재이용 사례", urlPath: "/examle/rain", newWndwYn: "N" },
|
||||
{ menuCd: 4, menuKornNm: "수요신청", urlPath: "/apply/place", newWndwYn: "N" },
|
||||
{ menuCd: 1, menuKornNm: "물 재이용 소개", urlPath: "/intro", newWndwYn: "N" },
|
||||
{ menuCd: 2, menuKornNm: "물 재이용 현황", urlPath: "/status", newWndwYn: "N" },
|
||||
{ menuCd: 3, menuKornNm: "물 재이용 사례", urlPath: "/example", newWndwYn: "N" },
|
||||
{ menuCd: 4, menuKornNm: "수요신청", urlPath: "/apply/dmdco", newWndwYn: "N" },
|
||||
];
|
||||
|
||||
export default function TopNav() {
|
||||
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 340 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 307 KiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 418 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 393 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 421 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 528 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 478 KiB After Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 38 KiB |