337 lines
9.7 KiB
TypeScript
337 lines
9.7 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"
|
|
disabled={loading || !businessId}
|
|
onClick={handleDelete}
|
|
>
|
|
삭제
|
|
</Button>
|
|
</>
|
|
}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Page;
|