341 lines
14 KiB
TypeScript
341 lines
14 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
import SubPageHeader from "@/components/SubPageHeader";
|
|
import { useDmStore } from "@/app/store";
|
|
import type { DemandCompBizType } from "@/app/store/sdmMng";
|
|
|
|
type TopField = { label: string; value: string; required?: boolean };
|
|
type PairField = { label: string; value: string };
|
|
type SessionUser = { dmdInfoId?: string };
|
|
|
|
const formatDate = (value: string | Date | "" | undefined) => {
|
|
if (!value) return "-";
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
const yyyy = date.getFullYear();
|
|
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
const dd = String(date.getDate()).padStart(2, "0");
|
|
return `${yyyy}-${mm}-${dd}`;
|
|
};
|
|
|
|
const formatValue = (value: string | number | null | undefined) => {
|
|
if (value === null || value === undefined || value === "") return "-";
|
|
return String(value);
|
|
};
|
|
|
|
const formatBelow = (value: string | number | null | undefined) => {
|
|
if (value === null || value === undefined || value === "") return "-";
|
|
if (Number(value) === -1) return "불검출";
|
|
return `${value} 이하`;
|
|
};
|
|
|
|
function StatusBadge({ v }: { v: string }) {
|
|
const cls = v === "신청" ? "text-red-600" : "text-lime-600";
|
|
return <span className={["font-medium", cls].join(" ")}>{v || "-"}</span>;
|
|
}
|
|
|
|
export default function DemandStatusPage() {
|
|
const { getSdmInfoStatusList, Sdm, Common } = useDmStore((state) => state);
|
|
const { successLogin, sdmInfoStatusList } = Sdm;
|
|
const { selectSidoData, selectSigunData } = Common;
|
|
|
|
const detailRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false);
|
|
const [selectedDmdInfoSn, setSelectedDmdInfoSn] = useState<number | "" | null>(null);
|
|
const [transformedSdmInfoStatusList, setTransformedSdmInfoStatusList] = useState<DemandCompBizType[]>([]);
|
|
|
|
useEffect(() => {
|
|
const user = sessionStorage.getItem("user");
|
|
if (user) {
|
|
setIsLoginStorageInfo(true);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!successLogin && !isLoginStorageInfo) return;
|
|
|
|
const user = sessionStorage.getItem("user");
|
|
if (!user) return;
|
|
|
|
try {
|
|
const userData = JSON.parse(user) as SessionUser;
|
|
const dmdInfoId = userData.dmdInfoId?.trim();
|
|
if (dmdInfoId) {
|
|
getSdmInfoStatusList(dmdInfoId);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error parsing user data:", error);
|
|
}
|
|
}, [successLogin, isLoginStorageInfo, getSdmInfoStatusList]);
|
|
|
|
useEffect(() => {
|
|
const mappedList = sdmInfoStatusList.map((item) => ({
|
|
...item,
|
|
ctpvCd: selectSidoData.find((sido) => sido.val === item.ctpvCd?.trim())?.text || item.ctpvCd,
|
|
sggCd: selectSigunData.find((sigun) => sigun.val === item.sggCd?.trim())?.text || item.sggCd,
|
|
}));
|
|
|
|
setTransformedSdmInfoStatusList(mappedList);
|
|
}, [sdmInfoStatusList, selectSidoData, selectSigunData]);
|
|
|
|
useEffect(() => {
|
|
const first = transformedSdmInfoStatusList[0];
|
|
if (!first) {
|
|
setSelectedDmdInfoSn(null);
|
|
return;
|
|
}
|
|
|
|
if (selectedDmdInfoSn == null) {
|
|
setSelectedDmdInfoSn(first.dmdInfoSn);
|
|
return;
|
|
}
|
|
|
|
const exists = transformedSdmInfoStatusList.some(
|
|
(item) => item.dmdInfoSn === selectedDmdInfoSn,
|
|
);
|
|
if (!exists) {
|
|
setSelectedDmdInfoSn(first.dmdInfoSn);
|
|
}
|
|
}, [transformedSdmInfoStatusList, selectedDmdInfoSn]);
|
|
|
|
const selectedSdmInfoStatus = useMemo(() => {
|
|
if (!transformedSdmInfoStatusList.length) return null;
|
|
return (
|
|
transformedSdmInfoStatusList.find((item) => item.dmdInfoSn === selectedDmdInfoSn) ??
|
|
transformedSdmInfoStatusList[0]
|
|
);
|
|
}, [transformedSdmInfoStatusList, selectedDmdInfoSn]);
|
|
|
|
const topFields: TopField[] = useMemo(() => {
|
|
if (!selectedSdmInfoStatus) return [];
|
|
|
|
return [
|
|
{ label: "요구시기", value: formatDate(selectedSdmInfoStatus.dmndTmng), required: true },
|
|
{ label: "요구수량(㎥/d)", value: formatValue(selectedSdmInfoStatus.dmndQty) },
|
|
{ label: "사용용도", value: formatValue(selectedSdmInfoStatus.useUsgCd) },
|
|
{ label: "희망단가(원/㎥)", value: formatValue(selectedSdmInfoStatus.wntUntprc) },
|
|
];
|
|
}, [selectedSdmInfoStatus]);
|
|
|
|
const waterFields: PairField[] = useMemo(() => {
|
|
if (!selectedSdmInfoStatus) return [];
|
|
|
|
return [
|
|
{ label: "총대장균군수(개/100mL)", value: formatBelow(selectedSdmInfoStatus.grssClfg) },
|
|
{ label: "결합잔류염소(mg/L)", value: formatBelow(selectedSdmInfoStatus.combResdch) },
|
|
{ label: "탁도(NTU)", value: formatBelow(selectedSdmInfoStatus.trbt) },
|
|
|
|
{ label: "생물학적산소요구량(BOD)(mg/L)", value: formatBelow(selectedSdmInfoStatus.blgcOxygDmndQnt) },
|
|
{ label: "냄새", value: formatValue(selectedSdmInfoStatus.smll) },
|
|
{ label: "색도(도)", value: formatBelow(selectedSdmInfoStatus.crmty) },
|
|
|
|
{ label: "총질소(T-N)(mg/L)", value: formatBelow(selectedSdmInfoStatus.tn) },
|
|
{ label: "총인(T-P)(mg/L)", value: formatBelow(selectedSdmInfoStatus.tph) },
|
|
{ label: "수소이온농도(pH)", value: formatValue(selectedSdmInfoStatus.ph) },
|
|
|
|
{ label: "염화물(mgCL/L)", value: formatBelow(selectedSdmInfoStatus.clIonQnt) },
|
|
{ label: "전기전도도(μS/cm)", value: formatBelow(selectedSdmInfoStatus.cdtnv) },
|
|
{ label: "비고", value: formatValue(selectedSdmInfoStatus.rmrk) },
|
|
];
|
|
}, [selectedSdmInfoStatus]);
|
|
|
|
const candidateNames = useMemo(() => {
|
|
const list = selectedSdmInfoStatus?.splyCndtList ?? [];
|
|
return [
|
|
list[0]?.ruseUtztnFcltNm ?? "",
|
|
list[1]?.ruseUtztnFcltNm ?? "",
|
|
list[2]?.ruseUtztnFcltNm ?? "",
|
|
];
|
|
}, [selectedSdmInfoStatus]);
|
|
|
|
const onSelectSdmInfoStatus = (dmdInfoSn: number | "") => {
|
|
setSelectedDmdInfoSn(dmdInfoSn);
|
|
requestAnimationFrame(() => {
|
|
detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
});
|
|
};
|
|
|
|
return (
|
|
<main className="bg-white">
|
|
<SubPageHeader
|
|
title="수요신청현황"
|
|
crumbs={[
|
|
{ label: "Home", href: "/main" },
|
|
{ label: "수요신청", href: "/apply/status" },
|
|
{ label: "수요신청현황" },
|
|
]}
|
|
hideFooterLine
|
|
hideGuideText
|
|
/>
|
|
|
|
<div>
|
|
<section className="mt-6">
|
|
<div className="text-2xl font-bold text-slate-700">수요정보 신청현황</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-[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">
|
|
<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>
|
|
<button
|
|
type="button"
|
|
className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700"
|
|
>
|
|
수정
|
|
</button>
|
|
</div>
|
|
|
|
{!selectedSdmInfoStatus ? (
|
|
<div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400">
|
|
상세 정보를 표시할 신청건이 없습니다.
|
|
</div>
|
|
) : (
|
|
<div className="mt-3 border border-neutral-200">
|
|
<div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200">
|
|
요구수량
|
|
</div>
|
|
|
|
<div className="grid grid-cols-12 text-sm">
|
|
{topFields.map((field) => (
|
|
<div
|
|
key={field.label}
|
|
className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200"
|
|
>
|
|
<div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
|
{field.label}
|
|
{field.required ? <span className="ml-1 text-red-500">*</span> : null}
|
|
</div>
|
|
<div className="col-span-8 px-4 py-4 flex items-center justify-center text-center">
|
|
{field.value}
|
|
</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) => (
|
|
<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">
|
|
{field.value}
|
|
</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>
|
|
|
|
{candidateNames.map((candidateName, index) => (
|
|
<div
|
|
key={`candidate-${index}`}
|
|
className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"
|
|
>
|
|
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
|
후보지{index + 1}
|
|
</div>
|
|
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
|
{candidateName}
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200">
|
|
수요신청 진행상태
|
|
</div>
|
|
|
|
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
|
|
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
|
진행상태
|
|
</div>
|
|
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
|
{formatValue(selectedSdmInfoStatus.status)}
|
|
</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200">
|
|
<div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700">
|
|
공급처매칭결과
|
|
</div>
|
|
<div className="col-span-7 px-3 py-4 flex items-center justify-center text-center">
|
|
{formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|