Files
frontend/apps/dm_new/app/(with-layout)/apply/status/page.tsx
T

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>
);
}