This commit is contained in:
2025-06-20 09:56:59 +09:00
75 changed files with 2296 additions and 664 deletions
@@ -0,0 +1,2 @@
import { GroupsPage } from "@/pages/groups";
export default GroupsPage;
@@ -35,6 +35,16 @@ export class MenuAuthorityApiService implements MenuAuthorityService {
);
return response.data;
}
public async getGroupMenuAuthorityList(
params: MenuAuthorityParams,
): Promise<MenuAuthorityList> {
const response = await client.get<MenuAuthorityList>(
`${ENDPOINT.CMS_SERVICE.MENU_AUTHRTS}/group`,
{ params },
);
return response.data;
}
}
export const MenuAuthorityApi = new MenuAuthorityApiService();
@@ -1,2 +1,9 @@
export { MenuAuthorityApi } from "./api/menu-authority-service";
export {
menuAuthorityFormSchema,
type MenuAuthority,
type MenuAuthorityPayload,
type MenuAuthorityFormData,
} from "./model/menu-authority-interface";
export { AuthorityButton } from "./ui/authority-button";
export { ManagementAuthorityDialog } from "./ui/management-authority-dialog";
@@ -43,6 +43,9 @@ export type MenuAuthorityFormData = z.infer<typeof menuAuthorityFormSchema>;
export interface MenuAuthorityService {
getMenuAuthorityList(params: MenuAuthorityParams): Promise<MenuAuthorityList>;
getGroupMenuAuthorityList(
params: MenuAuthorityParams,
): Promise<MenuAuthorityList>;
getPageMenuAuthority(menuCd: number): Promise<PageMenuAuthority[]>;
saveMenuAuthority(data: MenuAuthorityPayload[]): Promise<void>;
}
@@ -1,11 +1,10 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { getCoreRowModel } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Button,
DataTable,
Dialog,
DialogClose,
DialogContent,
@@ -15,7 +14,7 @@ import {
DialogTrigger,
} from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
import {
menuAuthorityFormSchema,
type MenuAuthorityFormData,
@@ -35,7 +34,7 @@ function ManagementAuthorityDialog({
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const { data: menuAuthorityList } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENU_AUTHRTS, menuCd],
queryFn: () => MenuAuthorityApi.getMenuAuthorityList({ menuCd }),
enabled: !!menuCd,
@@ -52,14 +51,16 @@ function ManagementAuthorityDialog({
const form = useForm<MenuAuthorityFormData>({
resolver: zodResolver(menuAuthorityFormSchema),
values: {
menuAuthority: menuAuthorityList?.content ?? [],
menuAuthority: query.data?.content ?? [],
},
});
const table = useReactTable({
data: menuAuthorityList?.content ?? [],
columns: createColumns(form),
getCoreRowModel: getCoreRowModel(),
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: createColumns(form),
getCoreRowModel: getCoreRowModel(),
},
});
const handleSave = async () => {
@@ -82,9 +83,7 @@ function ManagementAuthorityDialog({
<DialogHeader>
<DialogTitle>그룹별 메뉴 권한</DialogTitle>
</DialogHeader>
<div className="max-h-[40rem] overflow-auto">
<DataTable table={table} />
</div>
<div className="max-h-[40rem] overflow-auto">{tableComponent}</div>
<DialogFooter className="!justify-center">
<Button size="lg" onClick={handleSave}>
저장
@@ -9,7 +9,7 @@ import type {
} from "../model/role-interface";
export class RoleApiService implements RoleService {
public async getRoles(params: RoleParams): Promise<RoleList> {
public async getRoles(params?: RoleParams): Promise<RoleList> {
const response = await client.get<RoleList>(ENDPOINT.USER_SERVICE.ROLES, {
params,
});
+8
View File
@@ -0,0 +1,8 @@
export { RoleApi } from "./api/role-service";
export {
roleFormSchema,
type Role,
type RoleParams,
type RoleFormData,
type RoleList,
} from "./model/role-interface";
@@ -28,7 +28,7 @@ export type RoleList = PageableList<Role>;
export type RoleFormData = z.infer<typeof roleFormSchema>;
export interface RoleService {
getRoles: (params: RoleParams) => Promise<RoleList>;
getRoles: (params?: RoleParams) => Promise<RoleList>;
getRoleInfo: (roleId: string) => Promise<Role>;
createRole: (data: RoleFormData) => Promise<void>;
updateRole: (roleId: string, data: RoleFormData) => Promise<void>;
+5 -6
View File
@@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import {
cn,
Select,
SelectContent,
SelectItem,
@@ -12,12 +13,13 @@ import { SiteApi } from "../api/site-service";
type SiteSelectProps = {
placeholder?: string;
enabledAllSite?: boolean;
className?: string;
} & React.ComponentPropsWithoutRef<typeof Select>;
function SiteSelect({
defaultValue,
placeholder = "선택",
enabledAllSite,
className,
...props
}: SiteSelectProps) {
const { data: sites } = useQuery({
@@ -31,13 +33,10 @@ function SiteSelect({
},
});
const defaultValueText =
sites?.find((item) => item.siteId === defaultValue)?.siteNm ?? placeholder;
return (
<Select {...props}>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder={defaultValueText} />
<SelectTrigger className={cn("w-[12rem]", className)}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{sites?.map((item) => (
@@ -20,6 +20,7 @@ export interface UserSearchParams extends PageableParams {
grpId?: string;
frDateYmd?: string;
toDateYmd?: string;
userName?: string;
}
// 사용자 목록 응답 인터페이스
@@ -8,6 +8,12 @@ export const columns: ColumnDef<Post>[] = [
{
accessorKey: "rowNo",
header: "NO",
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "pstTtl",
+26 -23
View File
@@ -5,13 +5,13 @@ import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { Button, Input, Label } from "@common/components";
import { POSTING_TYPE, PostingDialog } from "@/entities/posting";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { BannerApi } from "../api/banner-service";
import { createColumns } from "../model/banner-table-columns";
import { type Banner, type BannerParams } from "../model/banner-interface";
@@ -34,7 +34,7 @@ function BannerPage() {
pageSize: 5,
});
const { data: bannerList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS],
queryFn: () =>
BannerApi.getBannerList({
@@ -45,24 +45,9 @@ function BannerPage() {
enabled: false,
});
const table = useReactTable({
data: bannerList?.content ?? [],
columns: createColumns(imageRefs),
state: {
pagination,
rowSelection,
},
rowCount: bannerList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Banner) => {
@@ -78,6 +63,26 @@ function BannerPage() {
return selectedImages;
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns: createColumns(imageRefs),
state: {
pagination,
rowSelection,
},
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
});
useEffect(() => {
const postingTargets = table
.getFilteredSelectedRowModel()
@@ -95,7 +100,7 @@ function BannerPage() {
}, [rowSelection]);
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -133,9 +138,7 @@ function BannerPage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
+15 -17
View File
@@ -5,15 +5,11 @@ import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button, DataTable } from "@common/components";
import { Button } from "@common/components";
import { PostingApi, type PostPayload } from "@/entities/posting";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import {
bannerFormSchema,
@@ -36,7 +32,7 @@ function ModifyPage({ sequential }: { sequential: string }) {
queryFn: () => BannerApi.getBannerInfo(sequential),
});
const { data: postHistory } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, sequential],
queryFn: () =>
PostingApi.getPostingHistory({ pstgTrgtNo: parseInt(sequential) }),
@@ -72,15 +68,17 @@ function ModifyPage({ sequential }: { sequential: string }) {
values: bannerDetail,
});
const table = useReactTable({
data: postHistory?.content ?? [],
columns,
state: {
rowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleDeletePostingHistory = async () => {
@@ -122,7 +120,7 @@ function ModifyPage({ sequential }: { sequential: string }) {
이력삭제
</Button>
</div>
<DataTable table={table} />
{tableComponent}
</div>
</div>
</div>
+25 -23
View File
@@ -5,13 +5,11 @@ import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import {
Button,
DataTable,
Input,
Label,
Select,
@@ -23,6 +21,7 @@ import {
import { CodeSelect } from "@/entities/code";
import { BoardApi, type BoardParams } from "@/entities/board";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { type Board } from "@/entities/board";
import { columns } from "../model/table-columns";
@@ -39,7 +38,7 @@ function BbsPage() {
pageSize: 10,
});
const { data: boardList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.BBS],
queryFn: () =>
BoardApi.getBoardList({
@@ -50,32 +49,37 @@ function BbsPage() {
enabled: false,
});
const table = useReactTable({
data: boardList?.content ?? [],
columns,
state: {
pagination,
rowSelection,
},
rowCount: boardList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Board) => {
router.push(`/bbs/register/${row.bbsId}`);
};
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,
},
});
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -134,9 +138,7 @@ function BbsPage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
+23 -20
View File
@@ -1,13 +1,10 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { LwpoCodeApi, type LwpoCodeParams } from "@/entities/code";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/lwpocode-table-columns";
import { LwpoCodeManagementDialog } from "./lwpocode-dialog";
@@ -25,7 +22,7 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
pageSize: 10,
});
const { data: lwpoCodeList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, pagination],
queryFn: () =>
LwpoCodeApi.getLwpoCodeList({
@@ -38,23 +35,29 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
placeholderData: keepPreviousData,
});
const table = useReactTable({
data: lwpoCodeList?.content ?? [],
columns,
rowCount: lwpoCodeList?.totalElements ?? 0,
state: { pagination },
manualPagination: true,
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
getRowId: (row) => row.lwpoCd,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: { pagination },
manualPagination: true,
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
getRowId: (row) => row.lwpoCd,
},
dataTableOptions: { viewPagination: true },
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
useEffect(() => {
setPagination({ ...pagination, pageIndex: 0 });
}, [upCd]);
return (
<div className="flex-1">
<div className="card card-border">
@@ -88,7 +91,7 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
</LwpoCodeManagementDialog>
</div>
<div className="card !m-4 h-[calc(100vh-35rem)] overflow-auto !p-0">
<DataTable table={table} viewPagination />
{tableComponent}
</div>
</div>
</div>
+20 -21
View File
@@ -1,13 +1,10 @@
import { useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { UpCodeApi, type UpCode, type UpCodeParams } from "@/entities/code";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/upcode-table-columns";
import { UpCodeManagementDialog } from "./upcode-dialog";
@@ -25,7 +22,7 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
pageSize: 10,
});
const { data: upCodeList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.UP_CODE, pagination],
queryFn: () =>
UpCodeApi.getUpCodeList({
@@ -36,18 +33,6 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
placeholderData: keepPreviousData,
});
const table = useReactTable({
data: upCodeList?.content ?? [],
columns,
rowCount: upCodeList?.totalElements ?? 0,
state: { pagination },
manualPagination: true,
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
getRowId: (row) => row.upCd,
});
const handleRowClick = (row: UpCode) => {
table.setRowSelection({ [row.upCd]: true });
handleSelectedUpCd(row.upCd);
@@ -55,9 +40,23 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: { pagination },
manualPagination: true,
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
getRowId: (row) => row.upCd,
},
dataTableOptions: { onRowClick: handleRowClick, viewPagination: true },
});
return (
<div className="flex-1">
<div className="card card-border">
@@ -91,7 +90,7 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
</UpCodeManagementDialog>
</div>
<div className="card !m-4 h-[calc(100vh-35rem)] overflow-auto !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
{tableComponent}
</div>
</div>
</div>
@@ -8,7 +8,12 @@ export const columns = [
columnHelper.display({
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
}),
columnHelper.accessor("contsTtl", {
id: "contsTtl",
+22 -26
View File
@@ -3,18 +3,15 @@
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import {
ContentsApi,
type Contents,
type ContentsParams,
} from "@/entities/content";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
function ContentsPage() {
@@ -28,7 +25,7 @@ function ContentsPage() {
pageSize: 5,
});
const { data: contentsList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS],
queryFn: () =>
ContentsApi.getContentsList({
@@ -42,27 +39,33 @@ function ContentsPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Contents) => {
router.push(`/contents/register/${row.contsSn}`);
};
const table = useReactTable({
data: contentsList?.content ?? [],
columns,
rowCount: contentsList?.totalElements ?? 0,
state: {
pagination,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
variant: "secondary",
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -89,14 +92,7 @@ function ContentsPage() {
등록
</Button>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable
table={table}
onRowClick={handleRowClick}
variant="secondary"
viewPagination
/>
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -11,7 +11,12 @@ export const createColumns = (
{
id: "no",
header: "NO",
cell: (info) => info.row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "atflId",
+21 -23
View File
@@ -2,15 +2,11 @@
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button, DataTable, Input, Label } from "@common/components";
import { Button, Input, Label } from "@common/components";
import { AttachmentApi, type AttachmentParams } from "@/entities/attachment";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { createColumns } from "../model/table-columns";
@@ -26,7 +22,7 @@ function FilePage() {
pageSize: 10,
});
const { data: attachmentsList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS],
queryFn: () =>
AttachmentApi.getAttachmentsList({
@@ -42,7 +38,7 @@ function FilePage() {
mutationFn: (atflId: string) =>
AttachmentApi.deleteAttachmentByAtflId(atflId),
onSuccess: () => {
refetch();
query.refetch();
alert("삭제되었습니다.");
},
});
@@ -53,16 +49,20 @@ function FilePage() {
}
};
const table = useReactTable({
data: attachmentsList?.content ?? [],
columns: createColumns(handleDeleteAttachment),
rowCount: attachmentsList?.totalElements ?? 0,
state: {
pagination,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: createColumns(handleDeleteAttachment),
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
viewPagination: true,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
const handleAttachmentQueryChange =
@@ -72,11 +72,11 @@ function FilePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -108,9 +108,7 @@ function FilePage() {
<div className="card-header">
<h3 className="text-2xl font-medium text-label">파일 목록</h3>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -0,0 +1,72 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
GroupUserService,
GroupUserSearchParams,
GroupUserListResponseDto,
GroupUserDetailResponse,
PageGroupUserList,
GroupUserSaveData,
GroupUserUpdateData,
} from "../model/group-users-interface";
export class GroupUsersApiService implements GroupUserService {
// 그룹 사용자 목록 조회
public async findPageGroupUser(
params: GroupUserSearchParams,
): Promise<PageGroupUserList> {
const response = await client.get<PageGroupUserList>(
`${ENDPOINT.CMS_SERVICE.GROUP_USERS}`,
{ params },
);
return response.data;
}
// 그룹 사용자 단건 조회
public async findByGroupAndUserId(
grpSn: number,
userUnqId: string,
): Promise<GroupUserDetailResponse> {
const response = await client.get<GroupUserDetailResponse>(
`${ENDPOINT.CMS_SERVICE.GROUP_USERS}/${grpSn}/${userUnqId}`,
);
return response.data;
}
// 그룹 사용자 저장
public async saveGroupUser(
data: GroupUserSaveData[],
): Promise<GroupUserListResponseDto[]> {
const response = await client.post<GroupUserListResponseDto[]>(
`${ENDPOINT.CMS_SERVICE.GROUP_USERS}`,
data,
);
return response.data;
}
// 그룹 사용자 수정
public async updateGroupUser(
grpSn: number,
userUnqId: string,
data: GroupUserUpdateData,
): Promise<GroupUserDetailResponse> {
const response = await client.put<GroupUserDetailResponse>(
`${ENDPOINT.CMS_SERVICE.GROUP_USERS}/${grpSn}/${userUnqId}`,
data,
);
return response.data;
}
// 그룹 사용자 삭제
public async deleteGroupUser(
groupId: string,
userUnqId: string,
): Promise<boolean> {
const response = await client.delete<boolean>(
`${ENDPOINT.CMS_SERVICE.GROUP_USERS}/${groupId}/${userUnqId}`,
);
return response.data;
}
}
export const GroupUsersApi = new GroupUsersApiService();
@@ -0,0 +1,59 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
GroupService,
GroupSearchParams,
GroupList,
GroupResponseDto,
GroupFormData,
} from "../model/groups-interface";
export class GroupsApiService implements GroupService {
// 그룹 목록 조회
public async findPageGroup(params: GroupSearchParams): Promise<GroupList> {
const response = await client.get<GroupList>(
`${ENDPOINT.CMS_SERVICE.GROUPS}`,
{ params },
);
return response.data;
}
// 그룹 단건 조회
public async findGroupById(groupId: string): Promise<GroupResponseDto> {
const response = await client.get<GroupResponseDto>(
`${ENDPOINT.CMS_SERVICE.GROUPS}/${groupId}`,
);
return response.data;
}
// 그룹 저장
public async saveGroup(data: GroupFormData): Promise<GroupResponseDto> {
const response = await client.post<GroupResponseDto>(
`${ENDPOINT.CMS_SERVICE.GROUPS}`,
data,
);
return response.data;
}
// 그룹 수정
public async updateGroup(
groupId: string,
data: GroupFormData,
): Promise<GroupResponseDto> {
const response = await client.put<GroupResponseDto>(
`${ENDPOINT.CMS_SERVICE.GROUPS}/${groupId}`,
data,
);
return response.data;
}
// 그룹 삭제
public async deleteGroup(groupId: string): Promise<boolean> {
const response = await client.delete<boolean>(
`${ENDPOINT.CMS_SERVICE.GROUPS}/${groupId}`,
);
return response.data;
}
}
export const GroupsApi = new GroupsApiService();
+1
View File
@@ -0,0 +1 @@
export { GroupsPage } from "./ui/groups-page";
@@ -0,0 +1,117 @@
import type { ColumnDef } from "@tanstack/react-table";
import type { UseFormReturn } from "react-hook-form";
import { Checkbox } from "@common/components";
import type {
MenuAuthority,
MenuAuthorityPayload,
MenuAuthorityFormData,
} from "@/entities/menu-authority";
export const createColumns = (
form: UseFormReturn<MenuAuthorityFormData>,
): ColumnDef<MenuAuthority>[] => {
const getCurrentRow = (i: number) => form.watch("menuAuthority")[i];
const handleCheckedChange =
(i: number, key: keyof MenuAuthorityPayload) => (checked: boolean) => {
if (getCurrentRow(i)?.mode === "R") {
form.setValue(`menuAuthority.${i}.mode`, "U");
}
form.setValue(`menuAuthority.${i}.${key}`, checked ? "Y" : "N");
};
return [
{
accessorKey: "menuNm",
header: "메뉴명",
},
{
accessorKey: "mngAuthrtYn",
header: "관리권한",
cell: ({ row }) => {
return (
<Checkbox
checked={getCurrentRow(row.index)?.mngAuthrtYn === "Y"}
onCheckedChange={(checked) => {
if (checked) {
handleCheckedChange(
row.index,
"regAuthrtYn",
)(checked as boolean);
handleCheckedChange(
row.index,
"mdfcnAuthrtYn",
)(checked as boolean);
handleCheckedChange(
row.index,
"delAuthrtYn",
)(checked as boolean);
handleCheckedChange(
row.index,
"inqAuthrtYn",
)(checked as boolean);
}
handleCheckedChange(row.index, "mngAuthrtYn")(checked as boolean);
}}
/>
);
},
},
{
accessorKey: "regAuthrtYn",
header: "등록권한",
cell: ({ row }) => {
return (
<Checkbox
checked={getCurrentRow(row.index)?.regAuthrtYn === "Y"}
onCheckedChange={(checked) => {
if (checked) {
handleCheckedChange(
row.index,
"inqAuthrtYn",
)(checked as boolean);
}
handleCheckedChange(row.index, "regAuthrtYn")(checked as boolean);
}}
/>
);
},
},
{
accessorKey: "mdfcnAuthrtYn",
header: "수정권한 ",
cell: ({ row }) => {
return (
<Checkbox
checked={getCurrentRow(row.index)?.mdfcnAuthrtYn === "Y"}
onCheckedChange={handleCheckedChange(row.index, "mdfcnAuthrtYn")}
/>
);
},
},
{
accessorKey: "delAuthrtYn",
header: "삭제권한",
cell: ({ row }) => {
return (
<Checkbox
checked={getCurrentRow(row.index)?.delAuthrtYn === "Y"}
onCheckedChange={handleCheckedChange(row.index, "delAuthrtYn")}
/>
);
},
},
{
accessorKey: "inqAuthrtYn",
header: "조회권한",
cell: ({ row }) => {
return (
<Checkbox
checked={getCurrentRow(row.index)?.inqAuthrtYn === "Y"}
onCheckedChange={handleCheckedChange(row.index, "inqAuthrtYn")}
/>
);
},
},
];
};
@@ -0,0 +1,77 @@
import type { PageableList, PageableParams } from "@/shared/model";
// 그룹 사용자 검색 파라미터 인터페이스
export interface GroupUserSearchParams extends PageableParams {
keyword?: string;
}
// 그룹 사용자 목록 응답 인터페이스
export interface GroupUserListResponseDto {
mode: string;
groupId: string;
userUnqId: string;
userName: string;
ogdpSeCd: string;
ogdpSeNm: string;
mphn: string;
}
// 그룹 사용자 페이지 응답 인터페이스
export type PageGroupUserList = PageableList<GroupUserListResponseDto>;
// 그룹 사용자 상세 응답 인터페이스
export interface GroupUserDetailResponse {
grpSn: number;
userUnqId: string;
userId: string;
userName: string;
email: string;
mphn: string;
regDt: string;
rgtr: string;
mdfcnDt: string;
mdfr: string;
rgtrNm: string;
mdfrNm: string;
}
// 그룹 사용자 저장 데이터 인터페이스
export interface GroupUserSaveData {
mode: string;
groupId: string;
userUnqId: string;
}
// 그룹 사용자 수정 데이터 인터페이스
export interface GroupUserUpdateData {
userUnqId: string;
}
// 그룹 사용자 서비스 인터페이스
export interface GroupUserService {
// 그룹 사용자 목록 조회
findPageGroupUser: (
params: GroupUserSearchParams,
) => Promise<PageGroupUserList>;
// 그룹 사용자 단건 조회
findByGroupAndUserId: (
grpSn: number,
userUnqId: string,
) => Promise<GroupUserDetailResponse>;
// 그룹 사용자 저장
saveGroupUser: (
data: GroupUserSaveData[],
) => Promise<GroupUserListResponseDto[]>;
// 그룹 사용자 수정
updateGroupUser: (
grpSn: number,
userUnqId: string,
data: GroupUserUpdateData,
) => Promise<GroupUserDetailResponse>;
// 그룹 사용자 삭제
deleteGroupUser: (groupId: string, userUnqId: string) => Promise<boolean>;
}
@@ -0,0 +1,57 @@
import { Checkbox } from "@common/components";
import { type Table, type Row, type ColumnDef } from "@tanstack/react-table";
import { type GroupUserListResponseDto } from "./group-users-interface";
export const columns: ColumnDef<GroupUserListResponseDto>[] = [
{
id: "select",
header: ({ table }: { table: Table<GroupUserListResponseDto> }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="전체 선택"
/>
),
cell: ({ row }: { row: Row<GroupUserListResponseDto> }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="행 선택"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "groupId",
header: "사용자ID",
},
{
accessorKey: "userName",
header: "이름",
},
{
accessorKey: "ogdpSeNm",
header: "소속",
},
{
accessorKey: "mphn",
header: "연락처",
cell: ({ row }) => {
const phone = row.getValue("mphn") as string;
if (!phone) return "";
const numbers = phone.replace(/\D/g, "");
if (numbers.length === 10) {
return `${numbers.slice(0, 3)}-${numbers.slice(3, 6)}-${numbers.slice(6)}`;
} else if (numbers.length === 11) {
return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice(7)}`;
} else {
return phone;
}
},
},
];
@@ -0,0 +1,87 @@
import { z } from "zod";
import type { PageableList, PageableParams } from "@/shared/model";
// 그룹 검색 파라미터 인터페이스
export interface GroupSearchParams extends PageableParams {
keywordType?: string;
keyword?: string;
groupId?: string;
groupNm?: string;
groupSeCd?: string;
delYn?: string;
authrtId?: string;
siteId?: string;
groupExpln?: string;
roleId?: string;
mngrYn?: string;
}
// 그룹 목록 응답 인터페이스
export interface GroupListResponseDto {
groupId: string;
groupNm: string;
groupSeCd: string;
groupSeNm: string;
delYn: string;
authrtId: string;
groupExpln: string;
siteId: string;
mngrYn: string;
roleId: string;
roleNm: string;
mdfcnDt: string;
mdfrNm: string;
}
// 그룹 상세 응답 인터페이스
export interface GroupResponseDto {
groupId: string;
groupNm: string;
groupSeCd: string;
groupSeNm: string;
groupExpln: string;
delYn: string;
regDt: string;
rgtr: string;
mdfcnDt: string;
mdfr: string;
authrtId: string;
siteId: string;
mngrYn: string;
}
// 그룹 저장 데이터 스키마
export const groupFormSchema = z.object({
groupId: z.string().min(1, { message: "그룹 ID를 입력해주세요." }),
groupNm: z.string().min(1, { message: "그룹명을 입력해주세요." }),
groupSeCd: z.string().min(1, { message: "그룹구분을 선택해주세요." }),
groupExpln: z.string().optional(),
delYn: z.string().default("N"),
authrtId: z.string().min(1, { message: "권한ID를 선택해주세요." }),
siteId: z.string().optional(),
mngrYn: z.string().default("N"),
});
export type GroupList = PageableList<GroupListResponseDto>;
export type GroupFormData = z.infer<typeof groupFormSchema>;
// 그룹 서비스 인터페이스
export interface GroupService {
// 그룹 목록 조회
findPageGroup: (params: GroupSearchParams) => Promise<GroupList>;
// 그룹 단건 조회
findGroupById: (groupId: string) => Promise<GroupResponseDto>;
// 그룹 저장
saveGroup: (data: GroupFormData) => Promise<GroupResponseDto>;
// 그룹 수정
updateGroup: (
groupId: string,
data: GroupFormData,
) => Promise<GroupResponseDto>;
// 그룹 삭제
deleteGroup: (groupId: string) => Promise<boolean>;
}
@@ -0,0 +1,37 @@
import { type ColumnDef } from "@tanstack/react-table";
import { Button } from "@common/components";
import { type GroupListResponseDto } from "./groups-interface";
import { MenuAuthorityDialog } from "../ui/menu-authority-dialog";
import { GroupsDialog } from "../ui/groups-dialog";
export const columns: ColumnDef<GroupListResponseDto>[] = [
{
accessorKey: "groupId",
header: "그룹코드",
},
{
accessorKey: "groupNm",
header: "그룹명",
},
{
accessorKey: "siteNm",
header: "사이트",
},
{
id: "actions",
header: "관리",
cell: ({ row }) => (
<div
onClick={(e) => e.stopPropagation()}
className="flex justify-center gap-2"
>
<MenuAuthorityDialog groupId={row.original.groupId} />
<GroupsDialog groupId={row.original.groupId}>
<Button size="sm" color="white">
수정
</Button>
</GroupsDialog>
</div>
),
},
];
@@ -0,0 +1,191 @@
"use client";
import { useState } from "react";
import {
keepPreviousData,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import {
getCoreRowModel,
type PaginationState,
type RowSelectionState,
type ColumnDef,
} from "@tanstack/react-table";
import {
Button,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
Input,
Checkbox,
} from "@common/components";
import {
UserApi,
type UserListResponse,
type UserSearchParams,
} from "@/entities/user";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable } from "@/shared/lib";
import { GroupUsersApi } from "../api/group-users-service";
type AdditionalUserDialogProps = {
groupId?: string;
};
function AdditionalUserDialog({ groupId }: AdditionalUserDialogProps) {
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage);
const [open, setOpen] = useState(false);
const [userSearchQuery, setUserSearchQuery] = useState<UserSearchParams>({
userName: "",
});
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [userRowSelection, setUserRowSelection] = useState<RowSelectionState>(
{},
);
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, userSearchQuery, pagination],
queryFn: () =>
UserApi.findPageUser({
...userSearchQuery,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
enabled: open,
placeholderData: keepPreviousData,
});
const userColumns: ColumnDef<UserListResponse>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="전체 선택"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="행 선택"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "userId",
header: "사용자ID",
},
{
accessorKey: "userName",
header: "이름",
},
{
accessorKey: "ogdpSeNm",
header: "소속",
},
{
accessorKey: "mphn",
header: "연락처",
},
];
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns: userColumns,
state: { pagination, rowSelection: userRowSelection },
manualPagination: true,
enableRowSelection: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setUserRowSelection,
getRowId: (row) => row.userUnqId,
},
});
const handleAddUsers = async () => {
const selectedRows = Object.keys(userRowSelection);
if (selectedRows.length === 0 || !groupId) return;
try {
const usersToAdd = selectedRows.map((userUnqId) => ({
mode: "C",
groupId,
userUnqId,
}));
await GroupUsersApi.saveGroupUser(usersToAdd);
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId],
});
alert("사용자 추가를 완료했습니다.", () => {
setOpen(false);
setUserRowSelection({});
});
} catch (error) {
console.error(error);
}
};
const handleSearch = () => {
setPagination({ pageIndex: 0, pageSize: 10 });
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button disabled={!groupId}>사용자 추가</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>사용자 추가</DialogTitle>
</DialogHeader>
<div className="mb-4 flex items-end gap-2">
<Input
value={userSearchQuery.userName}
onChange={(e) =>
setUserSearchQuery({
...userSearchQuery,
userName: e.target.value,
})
}
className="!text-[1.3rem]"
placeholder="사용자 이름으로 검색"
/>
<Button onClick={handleSearch}>검색</Button>
</div>
{tableComponent}
<DialogFooter>
<Button color="white" onClick={() => setOpen(false)}>
취소
</Button>
<Button
onClick={handleAddUsers}
disabled={table.getSelectedRowModel().rows.length === 0}
>
추가
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export { AdditionalUserDialog };
+204
View File
@@ -0,0 +1,204 @@
"use client";
import type { UseFormReturn } from "react-hook-form";
import {
Input,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Textarea,
} from "@common/components";
import {
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/shared/ui/form";
import { CodeSelect } from "@/entities/code";
import { SiteSelect } from "@/entities/site";
import { RoleSelect } from "./role-select";
import type { GroupFormData } from "../model/groups-interface";
type GroupFormProps = {
form: UseFormReturn<GroupFormData>;
};
function GroupForm({ form }: GroupFormProps) {
return (
<Table variant="secondary">
<colgroup>
<col width="15%" />
<col width="85%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead>그룹코드</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="groupId"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[24rem] !text-[1.3rem]"
placeholder="그룹코드를 입력하세요"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>그룹명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="groupNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[24rem] !text-[1.3rem]"
placeholder="그룹명을 입력하세요"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>그룹구분</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="groupSeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
upCd="GROUPSECD"
value={field.value ?? ""}
onValueChange={field.onChange}
className="w-[24rem]"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>그룹설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="groupExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
className="h-[10rem] resize-none !text-[1.3rem]"
placeholder="그룹설명을 입력하세요"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="siteId"
render={({ field }) => (
<FormItem>
<FormControl>
<SiteSelect
value={field.value ?? ""}
onValueChange={field.onChange}
className="w-[24rem]"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>관리권한</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="mngrYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="mngrYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="mngrY" />
<Label htmlFor="mngrY">Y</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="mngrN" />
<Label htmlFor="mngrN">N</Label>
</div>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>권한</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="authrtId"
render={({ field }) => (
<FormItem>
<FormControl>
<RoleSelect
value={field.value ?? ""}
onValueChange={field.onChange}
mngrYn={form.watch("mngrYn")}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
);
}
export { GroupForm };
@@ -0,0 +1,116 @@
"use client";
import { useEffect, useState } from "react";
import {
keepPreviousData,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import {
getCoreRowModel,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button } from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable } from "@/shared/lib";
import { columns } from "../model/group-users-table-columns";
import { GroupUsersApi } from "../api/group-users-service";
import { AdditionalUserDialog } from "./additional-user-dialog";
type GroupUsersSectionProps = {
groupId?: string;
};
function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination],
queryFn: () =>
GroupUsersApi.findPageGroupUser({
keyword: groupId,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
enabled: !!groupId,
placeholderData: keepPreviousData,
});
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: { pagination, rowSelection },
manualPagination: true,
enableRowSelection: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
getRowId: (row) => row.userUnqId,
},
dataTableOptions: {
viewPagination: true,
},
});
const handleDeleteSelected = async () => {
const selectedRows = Object.keys(rowSelection);
if (selectedRows.length === 0 || !groupId) return;
try {
const usersToDelete = selectedRows.map((userUnqId) => ({
mode: "D",
groupId,
userUnqId,
}));
await GroupUsersApi.saveGroupUser(usersToDelete);
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId],
});
alert("사용자 삭제를 완료했습니다.", () => {
setRowSelection({});
});
} catch (error) {
console.error(error);
}
};
useEffect(() => {
setPagination({ ...pagination, pageIndex: 0 });
}, [groupId]);
return (
<div className="flex-1">
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">그룹 사용자 목록</h3>
<div className="flex gap-2">
<AdditionalUserDialog groupId={groupId} />
<Button
color="red"
disabled={table.getSelectedRowModel().rows.length === 0}
onClick={handleDeleteSelected}
>
선택삭제
</Button>
</div>
</div>
<div className="card !m-4 h-[calc(100vh-25.5rem)] overflow-auto !p-0">
{tableComponent}
</div>
</div>
</div>
);
}
export { GroupUsersSection };
@@ -0,0 +1,128 @@
"use client";
import { memo, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useQuery } from "@tanstack/react-query";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
} from "@common/components";
import { Form } from "@/shared/ui/form";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { GroupForm } from "./group-form";
import { GroupsApi } from "../api/groups-service";
import { groupFormSchema, type GroupFormData } from "../model/groups-interface";
type GroupsDialogProps = {
groupId?: string;
children: React.ReactNode;
onSuccess?: () => void;
};
const GroupsDialog = memo(
({ groupId, children, onSuccess, ...props }: GroupsDialogProps) => {
const isRegistration = !groupId;
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const [open, setOpen] = useState(false);
const { data: group } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, groupId],
queryFn: () => GroupsApi.findGroupById(groupId!),
enabled: !!groupId,
});
const { mutateAsync: createGroup } = useMutationWrapper({
mutationFn: (data: GroupFormData) => GroupsApi.saveGroup(data),
onSuccess: () => {
alert("등록되었습니다.", () => {
setOpen(false);
onSuccess?.();
});
},
});
const { mutateAsync: updateGroup } = useMutationWrapper({
mutationFn: (data: GroupFormData) =>
GroupsApi.updateGroup(groupId ?? "", data),
onSuccess: () => {
alert("수정되었습니다.", () => {
setOpen(false);
onSuccess?.();
});
},
});
const form = useForm<GroupFormData>({
resolver: zodResolver(groupFormSchema),
values: {
groupId: group?.groupId ?? "",
groupNm: group?.groupNm ?? "",
groupSeCd: group?.groupSeCd ?? "",
groupExpln: group?.groupExpln ?? "",
delYn: group?.delYn ?? "",
authrtId: group?.authrtId ?? "",
siteId: group?.siteId ?? "",
mngrYn: group?.mngrYn ?? "",
},
});
const onSubmit =
(callback: (data: GroupFormData) => void) =>
async (data: GroupFormData) => {
const confirmMessage = isRegistration
? "등록하시겠습니까?"
: "수정하시겠습니까?";
if (await confirm(confirmMessage)) callback(data);
};
return (
<Dialog open={open} onOpenChange={setOpen} {...props}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>
{isRegistration ? "그룹 등록" : "그룹 수정"}
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(
onSubmit(isRegistration ? createGroup : updateGroup),
)}
className="space-y-4"
>
<GroupForm form={form} />
<DialogFooter className="!justify-center">
<Button type="submit" size="lg">
{isRegistration ? "등록" : "수정"}
</Button>
<DialogClose asChild>
<Button type="button" color="white" size="lg">
취소
</Button>
</DialogClose>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
},
);
GroupsDialog.displayName = "GroupsDialog";
export { GroupsDialog };
@@ -0,0 +1,18 @@
"use client";
import { useState } from "react";
import { GroupsSection } from "./groups-section";
import { GroupUsersSection } from "./group-users-section";
function GroupsPage() {
const [selectedGroupId, setSelectedGroupId] = useState("");
return (
<div className="flex">
<GroupsSection handleSelectedGroup={setSelectedGroupId} />
<GroupUsersSection groupId={selectedGroupId} />
</div>
);
}
export { GroupsPage };
@@ -0,0 +1,72 @@
"use client";
import { useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button } from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/groups-table-columns";
import { GroupsApi } from "../api/groups-service";
import { GroupsDialog } from "./groups-dialog";
type GroupsSectionProps = {
handleSelectedGroup: (groupId: string) => void;
};
function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, pagination],
queryFn: () =>
GroupsApi.findPageGroup({
page: pagination.pageIndex,
size: pagination.pageSize,
}),
placeholderData: keepPreviousData,
});
const handleRowClick = (row: { groupId: string }) => {
table.setRowSelection({ [row.groupId]: true });
handleSelectedGroup(row.groupId);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: { pagination },
manualPagination: true,
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
getRowId: (row) => row.groupId,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
});
return (
<div className="flex-1">
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">그룹 목록</h3>
<GroupsDialog onSuccess={query.refetch}>
<Button>등록</Button>
</GroupsDialog>
</div>
<div className="card !m-4 h-[calc(100vh-25.5rem)] overflow-auto !p-0">
{tableComponent}
</div>
</div>
</div>
);
}
export { GroupsSection };
@@ -0,0 +1,109 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { getCoreRowModel } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@common/components";
import {
MenuAuthorityApi,
menuAuthorityFormSchema,
type MenuAuthorityPayload,
type MenuAuthorityFormData,
} from "@/entities/menu-authority";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
import { createColumns } from "../model/group-menu-authority-table-columns";
type MenuAuthorityDialogProps = {
groupId: string;
} & React.ComponentProps<typeof Dialog>;
function MenuAuthorityDialog({ groupId }: MenuAuthorityDialogProps) {
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const [open, setOpen] = useState(false);
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENU_AUTHRTS, groupId],
queryFn: () => MenuAuthorityApi.getGroupMenuAuthorityList({ groupId }),
enabled: open && !!groupId,
});
const { mutateAsync: saveMenuAuthority } = useMutationWrapper({
mutationFn: (data: MenuAuthorityPayload[]) =>
MenuAuthorityApi.saveMenuAuthority(data),
onSuccess: () => {
alert("저장되었습니다.");
},
});
const form = useForm<MenuAuthorityFormData>({
resolver: zodResolver(menuAuthorityFormSchema),
values: {
menuAuthority: query.data?.content ?? [],
},
});
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: createColumns(form),
getCoreRowModel: getCoreRowModel(),
},
});
const handleSave = async (e: React.MouseEvent) => {
e.stopPropagation();
if (await confirm("저장하시겠습니까?")) {
const data = form.getValues("menuAuthority");
const filteredData = data.filter((item) => item.mode !== "R");
const menuAuthorityData = filteredData.map((item) => ({
...item,
groupId,
}));
await saveMenuAuthority(menuAuthorityData);
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button size="sm" onClick={(e) => e.stopPropagation()}>
메뉴권한
</Button>
</DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>그룹별 메뉴 권한</DialogTitle>
</DialogHeader>
<div className="max-h-[40rem] overflow-auto">{tableComponent}</div>
<DialogFooter className="!justify-center">
<Button size="lg" onClick={handleSave}>
저장
</Button>
<DialogClose asChild>
<Button
size="lg"
color="white"
onClick={(e) => e.stopPropagation()}
>
취소
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export { MenuAuthorityDialog };
@@ -0,0 +1,40 @@
import { useQuery } from "@tanstack/react-query";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { RoleApi } from "@/entities/role";
import { ENDPOINT } from "@/shared/config";
type RoleSelectProps = React.ComponentPropsWithoutRef<typeof Select> & {
mngrYn?: string;
};
function RoleSelect({ mngrYn = "N", ...props }: RoleSelectProps) {
const { data: roles } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.ROLES],
queryFn: () => RoleApi.getRoles(),
});
const options = roles?.content.filter((role) => role.mngrYn === mngrYn);
return (
<Select {...props}>
<SelectTrigger className="w-[24rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{options?.map((item) => (
<SelectItem key={item.roleId} value={item.roleId}>
{item.roleName}
</SelectItem>
))}
</SelectContent>
</Select>
);
}
export { RoleSelect };
@@ -31,7 +31,12 @@ export const columns: ColumnDef<Image>[] = [
{
accessorKey: "imgSn",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "imgTypeCd",
+26 -23
View File
@@ -5,13 +5,13 @@ import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { Button, Input, Label } from "@common/components";
import { POSTING_TYPE, PostingDialog } from "@/entities/posting";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { ImageApi } from "../api/image-service";
import { columns } from "../model/image-table-columns";
import { type Image, type ImageParams } from "../model/image-interface";
@@ -32,7 +32,7 @@ function ImagePage() {
pageSize: 5,
});
const { data: imageList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.IMAGES],
queryFn: () =>
ImageApi.getImageList({
@@ -43,30 +43,35 @@ function ImagePage() {
enabled: false,
});
const table = useReactTable({
data: imageList?.content ?? [],
columns,
state: {
pagination,
rowSelection,
},
rowCount: imageList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Image) => {
router.push(`/image/register/${row.imgSn}`);
};
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,
},
});
useEffect(() => {
const postingTargets = table
.getFilteredSelectedRowModel()
@@ -84,7 +89,7 @@ function ImagePage() {
}, [rowSelection]);
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -121,9 +126,7 @@ function ImagePage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
+15 -17
View File
@@ -5,15 +5,11 @@ import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button, DataTable } from "@common/components";
import { Button } from "@common/components";
import { PostingApi, type PostPayload } from "@/entities/posting";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useMutationWrapper, useDataTable } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { imageFormSchema, type ImageFormData } from "../model/image-interface";
import { columns } from "../model/posting-table-columns";
@@ -33,7 +29,7 @@ function ModifyPage({ sequential }: { sequential: string }) {
queryFn: () => ImageApi.getImageInfo(sequential),
});
const { data: postHistory } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, sequential],
queryFn: () =>
PostingApi.getPostingHistory({ pstgTrgtNo: parseInt(sequential) }),
@@ -68,15 +64,17 @@ function ModifyPage({ sequential }: { sequential: string }) {
values: imageDetail,
});
const table = useReactTable({
data: postHistory?.content ?? [],
columns,
state: {
rowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleDeletePostingHistory = async () => {
@@ -118,7 +116,7 @@ function ModifyPage({ sequential }: { sequential: string }) {
이력삭제
</Button>
</div>
<DataTable table={table} />
{tableComponent}
</div>
</div>
</div>
@@ -6,7 +6,12 @@ export const columns: ColumnDef<BoardPopup>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "bbsNm",
@@ -7,7 +7,12 @@ export const columns: ColumnDef<Contents>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
id: "contsTtl",
@@ -1,13 +1,8 @@
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import {
Button,
DataTable,
Dialog,
DialogTrigger,
DialogContent,
@@ -16,6 +11,7 @@ import {
Input,
} from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { BoardApi, type BoardPopup } from "@/entities/board";
import { columns } from "../model/board-table-columns";
@@ -30,21 +26,23 @@ function BoardSearchDialog({
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [bbsNm, setBbsNm] = useState("");
const { data: boardList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.BBS],
queryFn: () => BoardApi.getBoardPopupList({ bbsNm }),
enabled: false,
});
const table = useReactTable({
data: boardList?.content ?? [],
columns,
state: {
rowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
rowSelection,
},
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
useEffect(() => {
@@ -55,7 +53,7 @@ function BoardSearchDialog({
}, [table.getSelectedRowModel().rows]);
useEffect(() => {
refetch();
query.refetch();
}, []);
return (
@@ -78,13 +76,13 @@ function BoardSearchDialog({
value={bbsNm}
onChange={(e) => setBbsNm(e.target.value)}
/>
<Button onClick={() => refetch()}>
<Button onClick={() => query.refetch()}>
<i className="diveicon di-search" />
조회
</Button>
</div>
<div className="h-[40rem] w-full overflow-y-auto">
<DataTable table={table} />
{tableComponent}
</div>
</div>
</DialogContent>
@@ -1,13 +1,8 @@
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import {
Button,
DataTable,
Dialog,
DialogTrigger,
DialogContent,
@@ -16,6 +11,7 @@ import {
Input,
} from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { ContentsApi, type Contents } from "@/entities/content";
import { columns } from "../model/contents-table-colums";
@@ -30,21 +26,23 @@ function ContentSearchDialog({
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [contsTtl, setContsTtl] = useState("");
const { data: contentList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS],
queryFn: () => ContentsApi.getContentsList({ contsTtl }),
enabled: false,
});
const table = useReactTable({
data: contentList?.content ?? [],
columns,
state: {
rowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
rowSelection,
},
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
useEffect(() => {
@@ -55,7 +53,7 @@ function ContentSearchDialog({
}, [table.getSelectedRowModel().rows]);
useEffect(() => {
refetch();
query.refetch();
}, []);
return (
@@ -78,13 +76,13 @@ function ContentSearchDialog({
value={contsTtl}
onChange={(e) => setContsTtl(e.target.value)}
/>
<Button onClick={() => refetch()}>
<Button onClick={() => query.refetch()}>
<i className="diveicon di-search" />
조회
</Button>
</div>
<div className="h-[40rem] w-full overflow-y-auto">
<DataTable table={table} />
{tableComponent}
</div>
</div>
</DialogContent>
@@ -7,7 +7,12 @@ export const columns: ColumnDef<Authorizations>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "authorizationName",
@@ -2,15 +2,10 @@ import { useState, memo } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Button,
DataTable,
Dialog,
DialogClose,
DialogContent,
@@ -32,7 +27,7 @@ import {
type RoleAuthorizationsData,
} from "@/entities/role-authorization";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useMutationWrapper, useDataTable } from "@/shared/lib";
import { createColumns } from "../model/role-authorizations-table-columns";
import type { Authorizations } from "@/entities/authorization";
@@ -49,7 +44,7 @@ const ManagementAuthorityDialog = memo(
const [isOpened, setIsOpened] = useState(false);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const { data: roleAuthorizationList } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.ROLE_AUTHORIZATIONS, authorizationNo],
queryFn: () =>
RoleAuthorizationsApi.getRoleAuthorizationsList({ authorizationNo }),
@@ -68,24 +63,24 @@ const ManagementAuthorityDialog = memo(
resolver: zodResolver(roleAuthorizationsFormSchema),
values: {
roleAuthorizations:
roleAuthorizationList?.content.map(
({ roleId, authorizationNo, useYn }) => ({
roleId,
authorizationNo,
authYn: useYn,
}),
) ?? [],
query.data?.content.map(({ roleId, authorizationNo, useYn }) => ({
roleId,
authorizationNo,
authYn: useYn,
})) ?? [],
},
});
const table = useReactTable({
data: roleAuthorizationList?.content ?? [],
columns: createColumns(form),
state: {
rowSelection,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: createColumns(form),
state: {
rowSelection,
},
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleSave = async () => {
@@ -129,9 +124,7 @@ const ManagementAuthorityDialog = memo(
</TableRow>
</TableBody>
</Table>
<div className="max-h-[40rem] overflow-auto">
<DataTable table={table} />
</div>
<div className="max-h-[40rem] overflow-auto">{tableComponent}</div>
</div>
<DialogFooter className="!justify-center">
<Button size="lg" onClick={handleSave}>
+25 -25
View File
@@ -3,14 +3,11 @@
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { CodeSelect } from "@/entities/code";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import {
AuthorizationsApi,
type Authorizations,
@@ -31,7 +28,7 @@ function ModulePage() {
pageSize: 10,
});
const { data: authorizationsList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS],
queryFn: () =>
AuthorizationsApi.getAuthorizationsList({
@@ -43,16 +40,25 @@ function ModulePage() {
enabled: false,
});
const table = useReactTable({
data: authorizationsList?.content ?? [],
columns,
rowCount: authorizationsList?.totalElements ?? 0,
state: {
pagination,
const handleRowClick = (row: Authorizations) => {
router.push(`/module/register/${row.authorizationNo}`);
};
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
const handleAuthorizationQueryChange =
@@ -62,15 +68,11 @@ function ModulePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: Authorizations) => {
router.push(`/module/register/${row.authorizationNo}`);
query.refetch();
};
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -113,9 +115,7 @@ function ModulePage() {
<h3 className="text-2xl font-medium text-label">모듈API 목록</h3>
<Button onClick={() => router.push("/module/register")}>등록</Button>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -1,13 +1,8 @@
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import {
Button,
DataTable,
Dialog,
DialogTrigger,
DialogContent,
@@ -22,6 +17,7 @@ import {
type SearchUser,
} from "@/entities/user-organization";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/additional-user-table-columns";
type AdditionalUserDialogProps = {
@@ -36,18 +32,20 @@ function AdditionalUserDialog({
const [userNm, setUserNm] = useState("");
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const { data: userList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.USER_ORGANIZATION],
queryFn: () => UserOrganizationApi.searchUser({ userNm }),
enabled: false,
});
const table = useReactTable({
data: userList?.content ?? [],
columns,
state: { rowSelection },
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: { rowSelection },
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
});
const handleAddUsers = () => {
@@ -65,7 +63,7 @@ function AdditionalUserDialog({
};
useEffect(() => {
if (open) refetch();
if (open) query.refetch();
}, [open]);
return (
@@ -87,13 +85,13 @@ function AdditionalUserDialog({
value={userNm}
onChange={(e) => setUserNm(e.target.value)}
/>
<Button onClick={() => refetch()}>
<Button onClick={() => query.refetch()}>
<i className="diveicon di-search" />
조회
</Button>
</div>
<div className="h-[40rem] w-full overflow-y-auto">
<DataTable table={table} />
{tableComponent}
</div>
</div>
<DialogFooter className="!justify-center">
+19 -17
View File
@@ -5,16 +5,16 @@ import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { DataTable } from "@common/components";
import { AuthorityButton } from "@/entities/menu-authority";
import { PostingApi, type PostPayload } from "@/entities/posting";
import { useAlertStore, useUserStore, useMutationWrapper } from "@/shared/lib";
import {
useAlertStore,
useUserStore,
useMutationWrapper,
useDataTable,
} from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import {
popupUpdateFormSchema,
@@ -39,7 +39,7 @@ function ModifyPage({ popupSn }: { popupSn: number }) {
enabled: !!popupSn,
});
const { data: postHistory } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, popupSn],
queryFn: () => PostingApi.getPostingHistory({ pstgTrgtNo: popupSn }),
});
@@ -70,15 +70,17 @@ function ModifyPage({ popupSn }: { popupSn: number }) {
},
});
const table = useReactTable({
data: postHistory?.content ?? [],
columns,
state: {
rowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleDeletePostingHistory = async () => {
@@ -117,7 +119,7 @@ function ModifyPage({ popupSn }: { popupSn: number }) {
이력삭제
</AuthorityButton>
</div>
<DataTable table={table} />
{tableComponent}
</div>
</div>
</div>
+21 -18
View File
@@ -5,14 +5,14 @@ import { useRouter } from "next/navigation";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { Button, Input, Label } from "@common/components";
import { AuthorityButton } from "@/entities/menu-authority";
import { POSTING_TYPE, PostingDialog } from "@/entities/posting";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { PopupApi } from "../api/popup-service";
import { columns } from "../model/table-columns";
import type { Popup, PopupParams } from "../model/popup-interface";
@@ -35,7 +35,7 @@ function PopupPage() {
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const { data: popupList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS],
queryFn: () =>
PopupApi.getPopupList({
@@ -48,25 +48,30 @@ function PopupPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Popup) => {
router.push(`/popup/register/${row.popupSn}`);
};
const table = useReactTable({
data: popupList?.content ?? [],
columns,
rowCount: popupList?.totalElements ?? 0,
state: {
pagination,
rowSelection,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
rowSelection,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
useEffect(() => {
@@ -120,9 +125,7 @@ function PopupPage() {
</AuthorityButton>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -32,6 +32,12 @@ export const columns: ColumnDef<RecentPost>[] = [
{
accessorKey: "rowNo",
header: "NO",
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
meta: {
width: 80,
},
+36 -30
View File
@@ -7,7 +7,6 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
@@ -21,7 +20,6 @@ import {
SelectItem,
SelectTrigger,
SelectValue,
DataTable,
} from "@common/components";
import {
PostApi,
@@ -32,7 +30,12 @@ import {
} from "@/entities/post";
import { BoardApi } from "@/entities/board";
import { AuthorityButton } from "@/entities/menu-authority";
import { useAlertStore, useMutationWrapper, usePrompt } from "@/shared/lib";
import {
useAlertStore,
useMutationWrapper,
usePrompt,
useDataTable,
} from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { columns } from "../model/table-columns";
import { MovePostDialog } from "./move-post-dialog";
@@ -63,7 +66,7 @@ function RecentPage() {
});
const [isDeletePostData, setIsDeletePostData] = useState(false);
const { data: recentPostList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, pagination],
queryFn: () =>
PostApi.getRecentPostList({
@@ -82,23 +85,34 @@ function RecentPage() {
const { mutate: movePost } = useMutationWrapper({
mutationFn: (data: MovePost[]) => PostApi.movePost(data),
onSuccess: () => {
alert("이동이 완료되었습니다.", refetch);
alert("이동이 완료되었습니다.", query.refetch);
},
});
const table = useReactTable({
data: recentPostList?.content ?? [],
columns,
state: {
pagination,
rowSelection,
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,
},
rowCount: recentPostList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const isSelectedRow = table.getFilteredSelectedRowModel().rows.length > 0;
@@ -106,13 +120,7 @@ function RecentPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsDeletePostData(recentPostParams.delYn === "Y");
refetch();
};
const handleRowClick = (row: RecentPost) => {
router.push(
`/bbs/${row.bbsTypeCd.toLowerCase()}/${row.bbsId}/${row.pstNo}`,
);
query.refetch();
};
const handleRecentPostParamsChange =
@@ -160,7 +168,7 @@ function RecentPage() {
delRsn,
}));
PostApi.softDeletePost(selectedPosts);
alert("삭제가 완료되었습니다.", refetch);
alert("삭제가 완료되었습니다.", query.refetch);
setRowSelection({});
};
@@ -177,7 +185,7 @@ function RecentPage() {
pstNo: row.original.pstNo,
}));
PostApi.hardDeletePost(selectedPosts);
alert("삭제가 완료되었습니다.", refetch);
alert("삭제가 완료되었습니다.", query.refetch);
setRowSelection({});
}
};
@@ -191,7 +199,7 @@ function RecentPage() {
pstNo: row.original.pstNo,
}));
PostApi.restorePost(selectedPosts);
alert("복원이 완료되었습니다.", refetch);
alert("복원이 완료되었습니다.", query.refetch);
setRowSelection({});
}
};
@@ -332,9 +340,7 @@ function RecentPage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -1,14 +1,19 @@
import dayjs from "dayjs";
import { useState } from "react";
import type { Role } from "@/entities/role";
import type { ColumnDef } from "@tanstack/react-table";
import { MappingRoleDialog } from "../ui/mapping-role-dialog";
import type { Role } from "./role-interface";
export const columns: ColumnDef<Role>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "roleId",
+1 -2
View File
@@ -3,10 +3,9 @@
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { RoleApi, roleFormSchema, type RoleFormData } from "@/entities/role";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { RoleForm } from "./role-form";
import { RoleApi } from "../api/role-service";
import { roleFormSchema, type RoleFormData } from "../model/role-interface";
function CreatePage() {
const router = useRouter();
@@ -1,8 +1,4 @@
import {
getCoreRowModel,
useReactTable,
type ColumnDef,
} from "@tanstack/react-table";
import { getCoreRowModel, type ColumnDef } from "@tanstack/react-table";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
Dialog,
@@ -11,7 +7,6 @@ import {
DialogHeader,
DialogTitle,
Button,
DataTable,
Switch,
} from "@common/components";
import {
@@ -20,7 +15,7 @@ import {
type RoleAuthorizationsByRoleId,
} from "@/entities/role-authorization";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useMutationWrapper, useDataTable } from "@/shared/lib";
type MappingRoleDialogProps = {
roleId: string;
@@ -37,7 +32,7 @@ function MappingRoleDialog({
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage);
const { data: authorizationList } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS],
queryFn: () =>
RoleAuthorizationsApi.getRoleAuthorizationsListByRoleId({
@@ -63,7 +58,7 @@ function MappingRoleDialog({
const handleApplyAll = (authYn: "Y" | "N") => {
mutate(
authorizationList?.content.map((authorization) => ({
query.data?.content.map((authorization) => ({
roleId,
authorizationNo: authorization.authorizationNo,
authYn,
@@ -71,10 +66,12 @@ function MappingRoleDialog({
);
};
const table = useReactTable({
data: authorizationList?.content ?? [],
columns: createColumns(isPending, applyRoleAuthorization),
getCoreRowModel: getCoreRowModel(),
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: createColumns(isPending, applyRoleAuthorization),
getCoreRowModel: getCoreRowModel(),
},
});
return (
@@ -95,7 +92,7 @@ function MappingRoleDialog({
일괄해제
</Button>
</div>
<DataTable table={table} />
{tableComponent}
</div>
</DialogContent>
</Dialog>
+1 -2
View File
@@ -4,11 +4,10 @@ import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { RoleApi, roleFormSchema, type RoleFormData } from "@/entities/role";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { RoleForm } from "./role-form";
import { RoleApi } from "../api/role-service";
import { roleFormSchema, type RoleFormData } from "../model/role-interface";
function ModifyPage({ roleId }: { roleId: string }) {
const router = useRouter();
+1 -2
View File
@@ -13,10 +13,9 @@ import {
TableRow,
} from "@common/components";
import { FormWrapper } from "@/widgets/form-wrapper";
import { RoleApi, type RoleFormData } from "@/entities/role";
import { FormField, FormItem, FormControl } from "@/shared/ui/form";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { RoleApi } from "../api/role-service";
import type { RoleFormData } from "../model/role-interface";
type RoleFormProps = {
roleId?: string;
+25 -25
View File
@@ -3,16 +3,12 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { RoleApi, type Role, type RoleParams } from "@/entities/role";
import { ENDPOINT } from "@/shared/config";
import { RoleApi } from "../api/role-service";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
import type { Role, RoleParams } from "../model/role-interface";
function RolePage() {
const router = useRouter();
@@ -25,7 +21,7 @@ function RolePage() {
pageSize: 10,
});
const { data: roles } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.ROLES, pagination],
queryFn: () =>
RoleApi.getRoles({
@@ -35,24 +31,30 @@ function RolePage() {
}),
});
const table = useReactTable({
data: roles?.content ?? [],
columns,
state: {
pagination,
const handleRowClick = (row: Role) => {
router.push(`/role/register/${row.roleId}`);
};
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
rowCount: roles?.totalElements ?? 0,
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleRowClick = (row: Role) => {
router.push(`/role/register/${row.roleId}`);
query.refetch();
};
return (
@@ -85,9 +87,7 @@ function RolePage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -6,7 +6,12 @@ export const columns: ColumnDef<Site>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "siteId",
+21 -21
View File
@@ -5,14 +5,11 @@ import "@common/assets/styles/grid.css";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { SiteApi, type Site, type SiteParams } from "@/entities/site";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
function SitePage() {
@@ -26,7 +23,7 @@ function SitePage() {
pageSize: 10,
});
const { data: siteList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES],
queryFn: () =>
SiteApi.getSiteList({
@@ -40,27 +37,32 @@ function SitePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Site) => {
router.push(`/site/register/${row.siteId}`);
};
const table = useReactTable({
data: siteList?.content ?? [],
columns,
rowCount: siteList?.totalElements ?? 0,
state: {
pagination,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -85,9 +87,7 @@ function SitePage() {
<h3 className="text-2xl font-medium text-label">사이트 목록</h3>
<Button onClick={() => router.push("/site/register")}>등록</Button>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
+30 -33
View File
@@ -3,16 +3,12 @@
import { useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button, DataTable } from "@common/components";
import { Button } from "@common/components";
import { PostingApi, type PostPayload } from "@/entities/posting";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useMutationWrapper, useDataTable } from "@/shared/lib";
import { SurveyApi } from "../api/survey-service";
import { columns as postingColumns } from "../model/posting-table-columns";
import { columns as questionColumns } from "../model/question-table-columns";
@@ -33,13 +29,13 @@ function DetailPage({ srvySn }: { srvySn: string }) {
queryFn: () => SurveyApi.getSurveyInfo(parseInt(srvySn)),
});
const { data: postHistory } = useQuery({
const postHistoryQuery = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, srvySn],
queryFn: () =>
PostingApi.getPostingHistory({ pstgTrgtNo: parseInt(srvySn) }),
});
const { data: questionList } = useQuery({
const questionListQuery = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY_QUESTION, srvySn],
queryFn: () => SurveyApi.getQuestionList({ srvySn: parseInt(srvySn) }),
});
@@ -54,26 +50,31 @@ function DetailPage({ srvySn }: { srvySn: string }) {
},
});
const postingTable = useReactTable({
data: postHistory?.content ?? [],
columns: postingColumns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const { tableComponent: postingTableComponent, tableInstance: postingTable } =
useDataTable({
query: postHistoryQuery,
tableOptions: {
columns: postingColumns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
});
const questionTable = useReactTable({
data: questionList?.content ?? [],
columns: questionColumns,
state: {
rowSelection,
const { tableComponent: questionTableComponent } = useDataTable({
query: questionListQuery,
tableOptions: {
columns: questionColumns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleDeletePostingHistory = async () => {
@@ -135,9 +136,7 @@ function DetailPage({ srvySn }: { srvySn: string }) {
이력삭제
</Button>
</div>
<div className="card">
<DataTable table={postingTable} />
</div>
<div className="card">{postingTableComponent}</div>
</div>
<div className="card card-border">
<div className="card-header">
@@ -148,9 +147,7 @@ function DetailPage({ srvySn }: { srvySn: string }) {
onOpenChange={setQuestionAddDialogOpen}
/>
</div>
<div className="card">
<DataTable table={questionTable} />
</div>
<div className="card">{questionTableComponent}</div>
</div>
</div>
);
+25 -22
View File
@@ -5,14 +5,14 @@ import { useRouter } from "next/navigation";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { Button, Input, Label } from "@common/components";
import { POSTING_TYPE, PostingDialog } from "@/entities/posting";
import { AuthorityButton } from "@/entities/menu-authority";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { SurveyApi } from "../api/survey-service";
import type { Survey, SurveyParams } from "../model/survey-interface";
import { columns } from "../model/survey-table-columns";
@@ -35,7 +35,7 @@ function SurveyPage() {
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const { data: surveyList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, pagination],
queryFn: () =>
SurveyApi.getSurveyList({
@@ -46,27 +46,32 @@ function SurveyPage() {
placeholderData: keepPreviousData,
});
const table = useReactTable({
data: surveyList?.content ?? [],
columns,
rowCount: surveyList?.totalElements ?? 0,
state: {
pagination,
rowSelection,
const handleRowClick = (row: Survey) => {
router.push(`/survey/${row.srvySn}`);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
rowSelection,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: Survey) => {
router.push(`/survey/${row.srvySn}`);
query.refetch();
};
useEffect(() => {
@@ -120,9 +125,7 @@ function SurveyPage() {
</AuthorityButton>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -6,7 +6,12 @@ export const columns: ColumnDef<Template>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "tmpltNm",
+21 -21
View File
@@ -3,14 +3,11 @@
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { CodeSelect } from "@/entities/code";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { TemplateApi } from "../api/template-service";
import { columns } from "../model/template-table-columns";
import type { Template, TemplateParams } from "../model/template-interface";
@@ -27,7 +24,7 @@ function TemplatePage() {
pageSize: 5,
});
const { data: templateList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.TEMPLATE],
queryFn: () =>
TemplateApi.getTemplateList({
@@ -41,27 +38,32 @@ function TemplatePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Template) => {
router.push(`/template/register/${row.tmpltSn}`);
};
const table = useReactTable({
data: templateList?.content ?? [],
columns,
rowCount: templateList?.totalElements ?? 0,
state: {
pagination,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -109,9 +111,7 @@ function TemplatePage() {
등록
</Button>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -6,7 +6,12 @@ export const columns: ColumnDef<Terms>[] = [
{
id: "no",
header: "NO",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "trmsTtl",
+21 -21
View File
@@ -3,14 +3,11 @@
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { CodeSelect } from "@/entities/code";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { TermsApi } from "../api/terms-service";
import { columns } from "../model/terms-table-columns";
import type { TermsParams, Terms } from "../model/terms-interface";
@@ -27,7 +24,7 @@ function TermsPage() {
pageSize: 5,
});
const { data: termsList, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.TERMS],
queryFn: () =>
TermsApi.getTermsList({
@@ -41,27 +38,32 @@ function TermsPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleRowClick = (row: Terms) => {
router.push(`/terms/register/${row.trmsSn}`);
};
const table = useReactTable({
data: termsList?.content ?? [],
columns,
rowCount: termsList?.totalElements ?? 0,
state: {
pagination,
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
});
useEffect(() => {
refetch();
query.refetch();
}, [pagination]);
return (
@@ -107,9 +109,7 @@ function TermsPage() {
<h3 className="text-2xl font-medium text-label">약관 목록</h3>
<Button onClick={() => router.push("/terms/register")}>등록</Button>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -6,7 +6,12 @@ export const columns: ColumnDef<UserListResponse>[] = [
{
id: "no",
header: "No",
cell: ({ row }) => row.index + 1,
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "userId",
@@ -5,7 +5,6 @@ import {
useQueryClient,
} from "@tanstack/react-query";
import {
useReactTable,
getCoreRowModel,
getPaginationRowModel,
type PaginationState,
@@ -21,7 +20,6 @@ import {
Button,
Input,
Label,
DataTable,
} from "@common/components";
import {
LwpoCodeApi,
@@ -29,6 +27,7 @@ import {
type LwpoCodeParams,
} from "@/entities/code";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
interface AgencySearchDialogProps {
onSelectAgency: (data: { lwpoCd: string; lwpoCdNm: string }) => void;
@@ -46,7 +45,7 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
lwpoCdNm: "",
});
const { data, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, pagination],
queryFn: () =>
LwpoCodeApi.getLwpoCodeList({
@@ -57,32 +56,45 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
placeholderData: keepPreviousData,
});
const table = useReactTable({
data: data?.content ?? [],
columns: [
{
accessorKey: "lwpoCd",
header: "기관코드",
const handleRowClick = (row: LwpoCode) => {
table.setRowSelection({
[row.lwpoCd.toString()]: true,
});
setSelectedAgency(row);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns: [
{
accessorKey: "lwpoCd",
header: "기관코드",
},
{
accessorKey: "lwpoCdNm",
header: "기관명",
},
],
state: {
pagination,
},
{
accessorKey: "lwpoCdNm",
header: "기관명",
},
],
state: {
pagination,
manualPagination: true,
enableMultiRowSelection: false,
getRowId: (row) => row.lwpoCd.toString(),
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
variant: "secondary",
},
rowCount: data?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: false,
getRowId: (row) => row.lwpoCd.toString(),
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
const handleSearch = async () => {
await refetch();
await query.refetch();
queryClient.removeQueries({
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE],
});
@@ -94,13 +106,6 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
setSelectedAgency(null);
};
const handleRowClick = (row: LwpoCode) => {
table.setRowSelection({
[row.lwpoCd.toString()]: true,
});
setSelectedAgency(row);
};
const handleSelectAgency = () => {
if (!selectedAgency) return;
onSelectAgency({
@@ -146,12 +151,7 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
</div>
</form>
<DataTable
table={table}
variant="secondary"
onRowClick={handleRowClick}
viewPagination
/>
{tableComponent}
<DialogFooter className="!justify-center">
<Button
+23 -24
View File
@@ -5,15 +5,10 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import {
Button,
Checkbox,
DataTable,
DateRangePicker,
Input,
Label,
@@ -30,6 +25,7 @@ import {
type UserSearchParams,
} from "@/entities/user";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
type DateRange = {
@@ -64,7 +60,7 @@ function UsersPage() {
queryFn: () => UserApi.selectUserSttsStats(),
});
const { data: users, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination, watch("sort")],
queryFn: () =>
UserApi.findPageUser({
@@ -79,25 +75,30 @@ function UsersPage() {
queryFn: () => CodeApi.getCode("USERSTTS"),
});
const table = useReactTable({
data: users?.content ?? [],
columns,
state: {
pagination,
const handleRowClick = (row: UserListResponse) => {
router.push(`/users/${row.userUnqId}`);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
rowCount: users?.totalElements ?? 0,
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: UserListResponse) => {
router.push(`/users/${row.userUnqId}`);
query.refetch();
};
const handlePresetDateRange = (value: string) => {
@@ -323,9 +324,7 @@ function UsersPage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -5,7 +5,6 @@ import {
useQueryClient,
} from "@tanstack/react-query";
import {
useReactTable,
getCoreRowModel,
getPaginationRowModel,
type PaginationState,
@@ -21,9 +20,9 @@ import {
Button,
Input,
Label,
DataTable,
} from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { ZipApi, type Zip, type ZipSearchParams } from "../api/zip-service";
interface ZipSearchDialogProps {
@@ -45,7 +44,7 @@ function ZipSearchDialog({ onSelectAddress }: ZipSearchDialogProps) {
});
const hasFetchedOnce = useRef(false);
const { data, refetch } = useQuery({
const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.ZIPS, pagination],
queryFn: () =>
ZipApi.searchZip({
@@ -57,32 +56,45 @@ function ZipSearchDialog({ onSelectAddress }: ZipSearchDialogProps) {
enabled: false,
});
const table = useReactTable({
data: data?.content ?? [],
columns: [
{
accessorKey: "zoneNo",
header: "우편번호",
const handleRowClick = (row: Zip) => {
table.setRowSelection({
[row.zipId.toString()]: true,
});
setSelectedAddress(row);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns: [
{
accessorKey: "zoneNo",
header: "우편번호",
},
{
accessorKey: "zipAddr",
header: "주소",
},
],
state: {
pagination,
},
{
accessorKey: "zipAddr",
header: "주소",
},
],
state: {
pagination,
manualPagination: true,
enableMultiRowSelection: false,
getRowId: (row) => row.zipId.toString(),
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
variant: "secondary",
},
rowCount: data?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: false,
getRowId: (row) => row.zipId.toString(),
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
const handleSearch = async () => {
await refetch();
await query.refetch();
queryClient.removeQueries({
queryKey: [ENDPOINT.CMS_SERVICE.ZIPS],
});
@@ -95,13 +107,6 @@ function ZipSearchDialog({ onSelectAddress }: ZipSearchDialogProps) {
hasFetchedOnce.current = true;
};
const handleRowClick = (row: Zip) => {
table.setRowSelection({
[row.zipId.toString()]: true,
});
setSelectedAddress(row);
};
const handleSelectAddress = () => {
if (!selectedAddress) return;
onSelectAddress({
@@ -112,8 +117,8 @@ function ZipSearchDialog({ onSelectAddress }: ZipSearchDialogProps) {
};
useEffect(() => {
if (hasFetchedOnce.current) refetch();
}, [pagination, refetch]);
if (hasFetchedOnce.current) query.refetch();
}, [pagination]);
return (
<Dialog open={open} onOpenChange={setOpen}>
@@ -195,14 +200,7 @@ function ZipSearchDialog({ onSelectAddress }: ZipSearchDialogProps) {
</div>
</form>
{data?.content.length && (
<DataTable
table={table}
variant="secondary"
onRowClick={handleRowClick}
viewPagination
/>
)}
{query.data?.content.length && tableComponent}
<DialogFooter className="!justify-center">
<Button
+2
View File
@@ -46,6 +46,8 @@ export const ENDPOINT = {
SURVEY_ARTICLE: "/cms-service/api/v1/srvy-artcl",
ZIPS: "/cms-service/api/v1/zips",
LOGINLOG: "/cms-service/api/v1/loginlog",
GROUPS: "/cms-service/api/v1/groups",
GROUP_USERS: "/cms-service/api/v1/group-users",
},
BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs",
+1
View File
@@ -1,6 +1,7 @@
export { getCookie, setCookie, removeCookie } from "./cookie";
export { useMutationWrapper } from "./use-mutation-wrapper";
export { useAlertStore } from "./use-alert-store";
export { useDataTable } from "./use-data-table";
export { useMenuStore } from "./use-menu-store";
export { useUserStore } from "./use-user-store";
export { usePrompt } from "./use-prompt";
@@ -0,0 +1,65 @@
import { type UseQueryResult } from "@tanstack/react-query";
import type { PageableList } from "../model";
/**
* Match the state of a query to a set of components.
*
* Useful for rendering different UI based on the state of a query.
*/
export function matchQueryStatus<T>(
query: UseQueryResult<PageableList<T>>,
options: {
Loading?: JSX.Element;
Errored?: JSX.Element | ((error: unknown) => JSX.Element);
Empty?: JSX.Element;
Success: (
data: UseQueryResult<PageableList<T>> & {
data: NonNullable<UseQueryResult<T>["data"]>;
},
) => JSX.Element;
},
): JSX.Element;
export function matchQueryStatus<T>(
query: UseQueryResult<PageableList<T>>,
options: {
Loading?: JSX.Element;
Errored?: JSX.Element | ((error: unknown) => JSX.Element);
Success: (data: UseQueryResult<PageableList<T>>) => JSX.Element;
},
): JSX.Element;
export function matchQueryStatus<T>(
query: UseQueryResult<PageableList<T>>,
{
Loading,
Errored,
Empty,
Success,
}: {
Loading?: JSX.Element;
Errored?: JSX.Element | ((error: unknown) => JSX.Element);
Empty?: JSX.Element;
Success: (data: UseQueryResult<PageableList<T>>) => JSX.Element;
},
): JSX.Element {
if (query.isLoading && Loading) {
return Loading;
}
if (query.isError && Errored) {
if (typeof Errored === "function") {
return Errored(query.error);
}
return Errored;
}
const isEmpty =
query.data?.content === undefined ||
query.data?.content === null ||
(Array.isArray(query.data?.content) && query.data?.content.length === 0);
if (isEmpty && Empty) {
return Empty;
}
return Success(query);
}
+42
View File
@@ -0,0 +1,42 @@
import type { HTMLAttributes } from "react";
import type { UseQueryResult } from "@tanstack/react-query";
import { useReactTable, type TableOptions } from "@tanstack/react-table";
import { DataTable } from "@common/components";
import { NoData } from "../ui/no-data";
import type { PageableList } from "../model";
import { matchQueryStatus } from "./match-query-status";
type UseDataTableOptions<T> = {
query: UseQueryResult<PageableList<T>>;
tableOptions: Omit<TableOptions<T>, "data" | "rowCount">;
dataTableOptions?: DataTableOptions<T>;
};
type DataTableOptions<T> = {
variant?: "default" | "secondary" | "line";
className?: HTMLAttributes<HTMLTableElement>["className"];
viewPagination?: boolean;
onRowClick?: (row: T) => void;
};
export const useDataTable = <T,>({
query,
tableOptions,
dataTableOptions,
}: UseDataTableOptions<T>) => {
const table = useReactTable({
...tableOptions,
data: query.data?.content ?? [],
rowCount: query.data?.totalElements ?? 0,
});
return {
tableComponent: matchQueryStatus(query, {
Empty: <NoData />,
Success: () => {
return <DataTable table={table} {...dataTableOptions} />;
},
}),
tableInstance: table,
};
};
+17
View File
@@ -0,0 +1,17 @@
import { cn } from "@common/components";
function NoData({ className }: { className?: string }) {
return (
<div
className={cn(
"flex h-96 flex-col items-center justify-center gap-4",
className,
)}
>
<i className="diveicon di-info-circle text-3xl" />
조회된 데이터가 없습니다.
</div>
);
}
export { NoData };
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useEffect, useRef } from "react";
import { useQuery } from "@tanstack/react-query";
import { ChevronRight } from "lucide-react";
import { getConfig } from "@common/config";
@@ -40,12 +40,47 @@ export function LNB({
});
const [openMenuIds, setOpenMenuIds] = useState<number[]>([]);
const userClosedMenusRef = useRef(new Set());
const prevPathRef = useRef(currentPath);
function isPathMatched(path: string) {
if (!path) return false;
return currentPath?.startsWith(path);
}
useEffect(() => {
if (!menuList || !currentPath) return;
const matchedMenuIds: number[] = [];
const findMatchedMenus = (items: MenuItem[]) => {
items.forEach((item) => {
if (isPathMatched(item.urlPath)) {
const parentIds = getParentIds(item.menuCd)(menuList);
matchedMenuIds.push(...parentIds, item.menuCd);
}
if (item.children) {
findMatchedMenus(item.children);
}
});
};
findMatchedMenus(menuList);
if (matchedMenuIds.length > 0) {
const filteredIds = matchedMenuIds.filter(
(id) => !userClosedMenusRef.current.has(id),
);
setOpenMenuIds((prev) => [...new Set([...prev, ...filteredIds])]);
}
if (prevPathRef.current !== currentPath) {
setOpenMenuIds(matchedMenuIds);
prevPathRef.current = currentPath;
}
}, [menuList, currentPath]);
function MenuItem({ item }: { item: MenuItem }) {
const isMatched = isPathMatched(item.urlPath);
@@ -76,20 +111,20 @@ export function LNB({
}
function MenuGroup({ item }: { item: MenuItem }) {
const hasMatchedChild = item.children?.some((child) =>
isPathMatched(child.urlPath),
);
const isOpen = openMenuIds.includes(item.menuCd) || hasMatchedChild;
const isOpen = openMenuIds.includes(item.menuCd);
const handleOpenChange = (open: boolean) => {
if (open) {
// 열 때는 부모들도 함께 열기
if (!openMenuIds.includes(item.menuCd)) {
const parentIds = getParentIds(item.menuCd)(menuList ?? []);
setOpenMenuIds([...parentIds, item.menuCd]);
setOpenMenuIds((prev) => [...prev, ...parentIds, item.menuCd]);
}
userClosedMenusRef.current.delete(item.menuCd);
} else {
// 닫을 때는 현재 메뉴만 닫기
setOpenMenuIds((prev) => prev.filter((id) => id !== item.menuCd));
userClosedMenusRef.current.add(item.menuCd);
}
};