Files
frontend/apps/cs/src/pages/recent/ui/recent-page.tsx
T

406 lines
12 KiB
TypeScript

"use client";
import dayjs from "dayjs";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import {
Button,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { SortSelect } from "@/widgets/post";
import {
PostApi,
deleteReasonMessages,
type DeletePost,
type RecentPost,
type RecentPostParams,
} from "@/entities/post";
import { BoardApi } from "@/entities/board";
import { AuthorityButton } from "@/entities/menu-authority";
import { ENDPOINT } from "@/shared/config";
import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker";
import { useAlertStore, useSelectPrompt, useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
import { MovePostDialog } from "./move-post-dialog";
type DateRange = {
from?: Date | undefined;
to?: Date | undefined;
};
const selectData = Object.entries(deleteReasonMessages).map(
([label, value]) => ({ label, value }),
);
function RecentPage() {
const router = useRouter();
const { prompt } = useSelectPrompt(selectData);
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const [tableSort, setTableSort] = useState("regDt,desc");
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [presetDateRange, setPresetDateRange] = useState("");
const [dateRange, setDateRange] = useState<DateRange>({
from: undefined,
to: undefined,
});
const [keywordType, setKeywordType] = useState("");
const [keyword, setKeyword] = useState("");
const [bbsId, setBbsId] = useState<string | null>(null);
const [delYn, setDelYn] = useState("N");
const [searchParams, setSearchParams] = useState<RecentPostParams>({
keywordType: "",
keyword: "",
bbsId: null,
delYn: "N",
frDateYmd: "",
toDateYmd: "",
sort: "regDt,desc",
});
const [isDeletePostData, setIsDeletePostData] = useState(false);
const query = useQuery({
queryKey: [
ENDPOINT.BOARD_SERVICE.POSTS,
searchParams,
pagination,
tableSort,
],
queryFn: () =>
PostApi.getRecentPostList({
...searchParams,
page: pagination.pageIndex,
size: pagination.pageSize,
sort: tableSort,
}),
placeholderData: keepPreviousData,
});
const { data: boardList } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.BBS],
queryFn: () => BoardApi.getBoardList(),
});
const handleRowClick = (row: RecentPost) => {
router.push(
`/bbs/${row.bbsTypeCd.toLowerCase()}/${row.bbsId}/${row.pstNo}`,
);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
rowSelection,
},
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
});
const isSelectedRow = table.getFilteredSelectedRowModel().rows.length > 0;
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsDeletePostData(delYn === "Y");
setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keywordType,
keyword,
bbsId: bbsId ? parseInt(bbsId) : null,
delYn,
frDateYmd: dateRange?.from
? dayjs(dateRange.from).format("YYYYMMDD")
: "",
toDateYmd: dateRange?.to ? dayjs(dateRange.to).format("YYYYMMDD") : "",
});
};
const handleReset = () => {
setKeywordType("");
setKeyword("");
setBbsId(null);
setDelYn("N");
setDateRange({
from: undefined,
to: undefined,
});
setSearchParams({
...searchParams,
keywordType: "",
keyword: "",
bbsId: null,
delYn: "N",
frDateYmd: "",
toDateYmd: "",
});
setIsDeletePostData(false);
};
const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange;
switch (value) {
case "1W":
presetDateRange = {
from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(),
};
break;
case "1M":
presetDateRange = {
from: dayjs().subtract(30, "day").toDate(),
to: dayjs().toDate(),
};
break;
case "3M":
presetDateRange = {
from: dayjs().subtract(90, "day").toDate(),
to: dayjs().toDate(),
};
break;
default:
presetDateRange = {
from: undefined,
to: undefined,
};
}
setPresetDateRange(value);
setDateRange(presetDateRange);
};
const handleDateRangeChange = (dateRange: DateRange) => {
setDateRange(dateRange);
setPresetDateRange("");
};
const onSoftDeletePost = async () => {
const { resolved, value: delRsn } =
await prompt("삭제 사유를 입력해주세요.");
if (!resolved) return;
const selectedPosts: DeletePost[] = table
.getFilteredSelectedRowModel()
.rows.map((row) => ({
bbsId: row.original.bbsId,
pstNo: row.original.pstNo,
delRsn,
}));
PostApi.softDeletePost(selectedPosts);
alert("삭제가 완료되었습니다.", query.refetch);
setRowSelection({});
};
const onHardDeletePost = async () => {
if (
await confirm(
"게시물을 완전 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.",
)
) {
const selectedPosts: DeletePost[] = table
.getFilteredSelectedRowModel()
.rows.map((row) => ({
bbsId: row.original.bbsId,
pstNo: row.original.pstNo,
}));
PostApi.hardDeletePost(selectedPosts);
alert("삭제가 완료되었습니다.", query.refetch);
setRowSelection({});
}
};
const onRestorePost = async () => {
if (await confirm("게시물을 복원하시겠습니까?")) {
const selectedPosts: DeletePost[] = table
.getFilteredSelectedRowModel()
.rows.map((row) => ({
bbsId: row.original.bbsId,
pstNo: row.original.pstNo,
}));
PostApi.restorePost(selectedPosts);
alert("복원이 완료되었습니다.", query.refetch);
setRowSelection({});
}
};
return (
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2">날짜</Label>
<Select value={presetDateRange} onValueChange={handlePresetDateRange}>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1W">일주일</SelectItem>
<SelectItem value="1M">1개월</SelectItem>
<SelectItem value="3M">3개월</SelectItem>
</SelectContent>
</Select>
<InputDateRangePicker
dateRange={dateRange}
setDateRange={handleDateRangeChange}
/>
<Label className="ml-4 mr-2 text-label">게시판</Label>
<Select
value={bbsId?.toString() || ""}
onValueChange={(value) => setBbsId(value ? value : null)}
>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{boardList?.content.map((board) => (
<SelectItem key={board.bbsId} value={board.bbsId.toString()}>
{board.bbsNm}
</SelectItem>
))}
</SelectContent>
</Select>
<Label className="ml-4 mr-2 text-label">상태</Label>
<Select value={delYn} onValueChange={(value) => setDelYn(value)}>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="N">정상</SelectItem>
<SelectItem value="Y">삭제</SelectItem>
</SelectContent>
</Select>
<Label className="ml-4 mr-2 text-label">검색</Label>
<Select
value={keywordType}
onValueChange={(value) => setKeywordType(value)}
>
<SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="title">제목</SelectItem>
<SelectItem value="all">제목+내용</SelectItem>
<SelectItem value="writer">작성자</SelectItem>
</SelectContent>
</Select>
<Input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
<div className="flex gap-2">
{isDeletePostData ? (
<>
<AuthorityButton
authority="mngAuthrtYn"
onClick={onRestorePost}
disabled={!isSelectedRow}
>
게시물 복원
</AuthorityButton>
<AuthorityButton
authority="delAuthrtYn"
color="red"
onClick={onHardDeletePost}
disabled={!isSelectedRow}
>
완전삭제
</AuthorityButton>
</>
) : (
<>
<MovePostDialog
selectedPosts={table
.getFilteredSelectedRowModel()
.rows.map((row) => ({
fromBbsId: row.original.bbsId,
pstNo: row.original.pstNo,
}))}
boardList={boardList?.content ?? []}
resetSelectedPost={() => table.setRowSelection({})}
/>
<AuthorityButton
authority="delAuthrtYn"
color="red"
onClick={onSoftDeletePost}
disabled={!isSelectedRow}
>
선택삭제
</AuthorityButton>
</>
)}
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
<SortSelect value={tableSort} onValueChange={setTableSort} />
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
}
export { RecentPage };