Files
frontend/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx
T

332 lines
9.6 KiB
TypeScript

/**
* 유지관리 정보 - 노후 하수관로 정비사업 파일 목록
*
* 목록 조회, 첨부파일 다운로드·삭제, 업로드·상세 팝업 연결을 제공한다.
*/
"use client";
import { useEffect, useMemo, useState } from "react";
import {
getCoreRowModel,
useReactTable,
type ColumnDef,
type PaginationState,
} from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { deleteData, useCookieAuth } from "@/components/lib/api";
import { searchData } from "@/components/lib/common/apiHelpers";
import { formatDate, handleDownload } from "@/components/lib/utils";
import LoadingOverlay from "@/components/ui/LoadingBar";
import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
import { useAlertStore } from "@/shared/lib";
import {
getStandardRowNumber,
StandardDataTable,
} from "@common/business_components";
import { Button, Input, Label } from "@common/components";
import { SearchDatePicker } from "../components/SearchDatePicker";
const PAGE_SIZE = 10;
type OldPpRow = Record<string, unknown> & {
rowNumber: number;
dcrpPplMtncBizSn: string;
dcrpPplMtncBizNm?: string;
atflId?: string;
};
type OldPpSearch = {
fromDate: Date;
toDate: Date;
projectName: string;
};
const COLUMNS: Array<{
id: keyof OldPpRow;
title: string;
width: number;
}> = [
{ id: "rowNumber", title: "순번", width: 70 },
{ id: "dcrpPplMtncBizNm", title: "파일명", width: 260 },
{ id: "mngYr", title: "년도", width: 90 },
{ id: "mngQu", title: "분기", width: 90 },
{ id: "regDt", title: "등록 일자", width: 120 },
{ id: "rgtrNm", title: "등록자", width: 120 },
];
const Page = () => {
const auth = useCookieAuth();
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const [fromDate, setFromDate] = useState(() => {
const date = new Date();
date.setDate(date.getDate() - 7);
return date;
});
const [toDate, setToDate] = useState(new Date());
const [registrant, setRegistrant] = useState("");
const [projectName, setProjectName] = useState("");
const [fileName, setFileName] = useState("");
const [attachmentId, setAttachmentId] = useState("");
const [businessId, setBusinessId] = useState("");
const [rows, setRows] = useState<OldPpRow[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const columns = useMemo<ColumnDef<OldPpRow>[]>(
() =>
COLUMNS.map((column) => ({
id: String(column.id),
accessorFn: (row) => row[column.id],
header: column.title,
cell: ({ getValue }) => String(getValue() ?? ""),
meta: { width: column.width, align: "center" },
})),
[],
);
const pagination: PaginationState = {
pageIndex: page - 1,
pageSize: PAGE_SIZE,
};
const table = useReactTable({
data: rows,
columns,
state: { pagination },
rowCount: total,
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
getRowId: (row) => String(row.dcrpPplMtncBizSn),
onPaginationChange: (updater) => {
const next =
typeof updater === "function" ? updater(pagination) : updater;
setPage(next.pageIndex + 1);
},
});
const fetchData = async (
targetPage = page,
condition: OldPpSearch = { fromDate, toDate, projectName },
) => {
setLoading(true);
try {
const result = await searchData("v1/dcrpPpl", {
fromRegDt: formatDate(condition.fromDate),
toRegDt: formatDate(condition.toDate),
dcrpPplMtncBizNm: condition.projectName,
auth,
page: targetPage - 1,
size: String(PAGE_SIZE),
sort: "dcrpPplMtncBizSn,desc",
});
setRows(
(result.data ?? []).map((item: OldPpRow, rowIndex: number) => ({
...item,
rowNumber: getStandardRowNumber({
total: result.total,
rowIndex,
pageIndex: targetPage - 1,
pageSize: PAGE_SIZE,
}),
})),
);
setTotal(result.total);
} catch (error) {
console.error("노후 하수관로 정비사업 목록 조회 실패:", error);
} finally {
setFileName("");
setAttachmentId("");
setBusinessId("");
setLoading(false);
}
};
useEffect(() => {
void fetchData();
}, [page]);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
if (
event.data?.type !== WINDOW_POPUP_MESSAGE_TYPE.OLD_PP_UPLOAD_COMPLETED
)
return;
if (page === 1) void fetchData(1);
else setPage(1);
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [fromDate, page, projectName, toDate]);
const handleSearch = async () => {
if (page === 1) {
await fetchData(1);
return;
}
setPage(1);
};
const handleReset = async () => {
const nextFromDate = new Date();
nextFromDate.setDate(nextFromDate.getDate() - 7);
const nextToDate = new Date();
setFromDate(nextFromDate);
setToDate(nextToDate);
setRegistrant("");
setProjectName("");
if (page !== 1) {
setPage(1);
return;
}
await fetchData(1, {
fromDate: nextFromDate,
toDate: nextToDate,
projectName: "",
});
};
const handleFileDownload = () => {
if (!fileName) {
alert("파일을 선택해주세요");
return;
}
handleDownload(`/attach-service/api/v1/download/${attachmentId}`, fileName);
};
const handleDelete = async () => {
if (!businessId) {
alert("삭제할 파일을 선택해주세요");
return;
}
if (!(await confirm("삭제하시겠습니까?"))) return;
const attachmentResult = await deleteData(
`/attach-service/api/v1/atfl/attachments/${attachmentId}`,
);
if (!attachmentResult) {
alert("삭제에 실패했습니다.");
return;
}
const businessResult = await deleteData(`v1/dcrpPpl/${businessId}`);
if (businessResult.status === 200) {
alert("삭제되었습니다.", () => void fetchData());
return;
}
alert("삭제에 실패했습니다.");
};
const handleRegister = () => {
if (!openWindowPopup("oldPpUpload")) {
alert("팝업이 차단되었습니다. 브라우저 팝업 허용 설정을 확인해주세요.");
}
};
const handleOpenDetail = (row: OldPpRow) => {
const nextFileName = String(row.dcrpPplMtncBizNm ?? "");
const nextAttachmentId = String(row.atflId ?? "");
setFileName(nextFileName);
setAttachmentId(nextAttachmentId);
setBusinessId(String(row.dcrpPplMtncBizSn ?? ""));
if (
!openWindowPopup("oldPpTable", {
fileNm: nextFileName,
atflId: nextAttachmentId,
sourcePath: window.location.pathname,
})
) {
alert("팝업이 차단되었습니다. 브라우저 팝업 허용 설정을 확인해주세요.");
}
};
return (
<>
<LoadingOverlay isLoading={loading} />
<div className="card card-border !mt-6">
<form
className="flex flex-wrap items-center gap-2 p-2"
onSubmit={(event) => {
event.preventDefault();
void handleSearch();
}}
>
<div className="flex items-center gap-2">
<Label className="mr-2 whitespace-nowrap text-label">
등록기간
</Label>
<SearchDatePicker
selected={fromDate}
onSelect={(date) => date && setFromDate(date)}
disabled={{ after: new Date() }}
/>
<span>~</span>
<SearchDatePicker
selected={toDate}
onSelect={(date) => date && setToDate(date)}
disabled={{ after: new Date() }}
/>
</div>
<div className="flex items-center gap-2">
<Label className="mr-2 whitespace-nowrap text-label">등록자</Label>
<Input
className="w-[12rem] !text-[1.3rem]"
value={registrant}
onChange={(event) => setRegistrant(event.target.value)}
/>
</div>
<div className="flex items-center gap-2">
<Label className="mr-2 whitespace-nowrap text-label">파일명</Label>
<Input
className="w-[18rem] !text-[1.3rem]"
value={projectName}
onChange={(event) => setProjectName(event.target.value)}
/>
</div>
<Button type="submit" className="w-[7.4rem]">
조회
</Button>
<Button
type="button"
color="white"
className="w-[8.8rem]"
onClick={() => void handleReset()}
>
초기화
</Button>
</form>
</div>
<div className="mt-4 w-full min-w-0 max-w-full overflow-hidden">
<StandardDataTable
table={table}
title="노후 하수관로 정비사업 목록"
onRowClick={handleOpenDetail}
actions={
<>
<Button type="button" onClick={handleFileDownload}>
엑셀다운로드
</Button>
<Button type="button" onClick={handleRegister}>
등록
</Button>
<Button type="button" color="red" onClick={handleDelete}>
삭제
</Button>
</>
}
/>
</div>
</>
);
};
export default Page;