--- eslint.config.js
+++ eslint.config.js
... | ... | @@ -19,5 +19,8 @@ |
| 19 | 19 |
ecmaVersion: 2020, |
| 20 | 20 |
globals: globals.browser, |
| 21 | 21 |
}, |
| 22 |
+ rules: {
|
|
| 23 |
+ '@typescript-eslint/no-unused-vars': 'off', |
|
| 24 |
+ } |
|
| 22 | 25 |
}, |
| 23 | 26 |
]) |
--- src/App.tsx
+++ src/App.tsx
... | ... | @@ -1,7 +1,6 @@ |
| 1 | 1 |
import { useEffect, useState } from 'react';
|
| 2 | 2 |
import { UserLayout } from './user/UserLayout';
|
| 3 | 3 |
import { UserListPage } from './user/UserListPage';
|
| 4 |
-import {AdminLayout} from "./admin/layout/AdminLayout.tsx";
|
|
| 5 | 4 |
import {AdminRoute} from "./admin/route/AdminRoute.tsx";
|
| 6 | 5 |
import {ToastContainer} from "react-toastify";
|
| 7 | 6 |
|
... | ... | @@ -55,9 +54,7 @@ |
| 55 | 54 |
</div> |
| 56 | 55 |
|
| 57 | 56 |
{skin === 'admin' ? (
|
| 58 |
- <AdminLayout> |
|
| 59 |
- <AdminRoute /> |
|
| 60 |
- </AdminLayout> |
|
| 57 |
+ <AdminRoute /> |
|
| 61 | 58 |
) : ( |
| 62 | 59 |
<UserLayout> |
| 63 | 60 |
<UserListPage /> |
+++ src/admin/component/table/SortableHeaderCell.tsx
... | ... | @@ -0,0 +1,29 @@ |
| 1 | +import type {ReactNode} from "react"; | |
| 2 | + | |
| 3 | +type SortableHeaderCellProps = { | |
| 4 | + field: string; | |
| 5 | + active: boolean; | |
| 6 | + icon: ReactNode; | |
| 7 | + onSort: (field: string) => void; | |
| 8 | + children: ReactNode; | |
| 9 | +}; | |
| 10 | + | |
| 11 | +export const SortableHeaderCell = ({ | |
| 12 | + field, | |
| 13 | + active, | |
| 14 | + icon, | |
| 15 | + onSort, | |
| 16 | + children, | |
| 17 | + }: SortableHeaderCellProps) => { | |
| 18 | + return ( | |
| 19 | + <th scope="col"> | |
| 20 | + {children} | |
| 21 | + <button | |
| 22 | + className={`sort sortBtn ${active ? 'active' : ''}`} | |
| 23 | + onClick={() => onSort(field)} | |
| 24 | + > | |
| 25 | + {icon} | |
| 26 | + </button> | |
| 27 | + </th> | |
| 28 | + ); | |
| 29 | +}; |
+++ src/admin/component/table/getTableRowNumber.ts
... | ... | @@ -0,0 +1,18 @@ |
| 1 | +import type {SearchParams} from "../../../type/searchParams.ts"; | |
| 2 | + | |
| 3 | +type GetTableRowNumberParams = { | |
| 4 | + searchParams: Pick<SearchParams, 'searchSortOrd' | 'pageUnit'>; | |
| 5 | + totalItems: number; | |
| 6 | + currentPage: number; | |
| 7 | + index: number; | |
| 8 | +}; | |
| 9 | + | |
| 10 | +export const getTableRowNumber = ({ | |
| 11 | + searchParams, | |
| 12 | + totalItems, | |
| 13 | + currentPage, | |
| 14 | + index, | |
| 15 | + }: GetTableRowNumberParams) => | |
| 16 | + searchParams.searchSortOrd === 'DESC' | |
| 17 | + ? totalItems - (currentPage - 1) * searchParams.pageUnit - index | |
| 18 | + : (currentPage - 1) * searchParams.pageUnit + (index + 1); |
--- src/admin/feature/board/article/components/BoardArticleListTable.tsx
+++ src/admin/feature/board/article/components/BoardArticleListTable.tsx
... | ... | @@ -34,7 +34,6 @@ |
| 34 | 34 |
searchParams={params}
|
| 35 | 35 |
totalItems={pagination.totalItems}
|
| 36 | 36 |
currentPage={pagination.currentPage}
|
| 37 |
- totalPages={pagination.totalPages}
|
|
| 38 | 37 |
checked={check.isChecked(item.nttId)}
|
| 39 | 38 |
onCheck={check.onCheck}
|
| 40 | 39 |
/> |
--- src/admin/feature/board/article/components/BoardArticleListTableHeader.tsx
+++ src/admin/feature/board/article/components/BoardArticleListTableHeader.tsx
... | ... | @@ -1,5 +1,7 @@ |
| 1 | 1 |
import type {BoardArticleSearchParams} from "../type/boardArticle.types.ts";
|
| 2 | 2 |
import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx";
|
| 3 |
+import {useTableSort} from "../../../../hook/useTableSort.ts";
|
|
| 4 |
+import {SortableHeaderCell} from "../../../../component/table/SortableHeaderCell.tsx";
|
|
| 3 | 5 |
|
| 4 | 6 |
type BoardArticleListTableHeaderProps = {
|
| 5 | 7 |
params: BoardArticleSearchParams; |
... | ... | @@ -16,31 +18,7 @@ |
| 16 | 18 |
indeterminate, |
| 17 | 19 |
onCheckAll, |
| 18 | 20 |
}: BoardArticleListTableHeaderProps) => {
|
| 19 |
- const handleSort = (field: string) => {
|
|
| 20 |
- const nextOrder = |
|
| 21 |
- params.searchSortCnd === field && |
|
| 22 |
- params.searchSortOrd === 'ASC' |
|
| 23 |
- ? 'DESC' |
|
| 24 |
- : 'ASC'; |
|
| 25 |
- |
|
| 26 |
- onChange({
|
|
| 27 |
- ...params, |
|
| 28 |
- searchSortCnd: field, |
|
| 29 |
- searchSortOrd: nextOrder, |
|
| 30 |
- pageIndex: 1, |
|
| 31 |
- }); |
|
| 32 |
- }; |
|
| 33 |
- |
|
| 34 |
- const getSortIcon = (field: string) => {
|
|
| 35 |
- |
|
| 36 |
- if (params.searchSortCnd !== field) {
|
|
| 37 |
- return '-'; |
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- return params.searchSortOrd === 'ASC' |
|
| 41 |
- ? '▲' |
|
| 42 |
- : '▼'; |
|
| 43 |
- }; |
|
| 21 |
+ const {handleSort, getSortIcon, isSorted} = useTableSort(params, onChange);
|
|
| 44 | 22 |
|
| 45 | 23 |
|
| 46 | 24 |
return ( |
... | ... | @@ -77,15 +55,15 @@ |
| 77 | 55 |
<th scope={"col"}>
|
| 78 | 56 |
공개여부 |
| 79 | 57 |
</th> |
| 80 |
- <th scope="col">작성자</th> |
|
| 81 |
- <th scope="col">작성일 |
|
| 82 |
- <button |
|
| 83 |
- className={`sort sortBtn ${params.searchSortCnd === 'FRST_REGIST_PNTTM' ? 'active' : ''}`}
|
|
| 84 |
- onClick={() => handleSort('FRST_REGIST_PNTTM')}
|
|
| 85 |
- > |
|
| 86 |
- {getSortIcon('FRST_REGIST_PNTTM')}
|
|
| 87 |
- </button> |
|
| 88 |
- </th> |
|
| 58 |
+ <th scope="col">???</th> |
|
| 59 |
+ <SortableHeaderCell |
|
| 60 |
+ field="FRST_REGIST_PNTTM" |
|
| 61 |
+ active={isSorted('FRST_REGIST_PNTTM')}
|
|
| 62 |
+ icon={getSortIcon('FRST_REGIST_PNTTM')}
|
|
| 63 |
+ onSort={handleSort}
|
|
| 64 |
+ > |
|
| 65 |
+ ??? |
|
| 66 |
+ </SortableHeaderCell> |
|
| 89 | 67 |
<th scope="col">조회수</th> |
| 90 | 68 |
</tr> |
| 91 | 69 |
</thead> |
--- src/admin/feature/board/article/components/BoardArticleListTableRow.tsx
+++ src/admin/feature/board/article/components/BoardArticleListTableRow.tsx
... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 |
import type {BoardArticleListItem} from "../type/boardArticle.types.ts";
|
| 2 | 2 |
import type {SearchParams} from "../../../../../type/searchParams.ts";
|
| 3 | 3 |
import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx";
|
| 4 |
+import {getTableRowNumber} from "../../../../component/table/getTableRowNumber.ts";
|
|
| 4 | 5 |
|
| 5 | 6 |
type BoardArticleListTableRowProps = {
|
| 6 | 7 |
item: BoardArticleListItem |
... | ... | @@ -8,7 +9,6 @@ |
| 8 | 9 |
searchParams: SearchParams |
| 9 | 10 |
totalItems: number |
| 10 | 11 |
currentPage: number |
| 11 |
- totalPages: number |
|
| 12 | 12 |
checked: boolean |
| 13 | 13 |
onCheck: (id: string, checked: boolean) => void |
| 14 | 14 |
} |
... | ... | @@ -19,14 +19,17 @@ |
| 19 | 19 |
searchParams, |
| 20 | 20 |
totalItems, |
| 21 | 21 |
currentPage, |
| 22 |
- totalPages, |
|
| 23 | 22 |
checked, |
| 24 | 23 |
onCheck, |
| 25 | 24 |
}: BoardArticleListTableRowProps) => {
|
| 26 | 25 |
|
| 27 |
- const rowNumber = searchParams.searchSortOrd === 'DESC' |
|
| 28 |
- ? totalItems - (currentPage - 1) * totalPages - index |
|
| 29 |
- : (currentPage - 1) * totalPages + (index + 1) |
|
| 26 |
+ const rowNumber = getTableRowNumber({
|
|
| 27 |
+ searchParams, |
|
| 28 |
+ totalItems, |
|
| 29 |
+ currentPage, |
|
| 30 |
+ index, |
|
| 31 |
+ }); |
|
| 32 |
+ |
|
| 30 | 33 |
return ( |
| 31 | 34 |
<tr> |
| 32 | 35 |
<td> |
--- src/admin/feature/board/master/components/BoardListTable.tsx
+++ src/admin/feature/board/master/components/BoardListTable.tsx
... | ... | @@ -42,7 +42,6 @@ |
| 42 | 42 |
searchParams={params}
|
| 43 | 43 |
totalItems={pagination.totalItems}
|
| 44 | 44 |
currentPage={pagination.currentPage}
|
| 45 |
- totalPages={pagination.totalPages}
|
|
| 46 | 45 |
checked={check.isChecked(item.bbsId)}
|
| 47 | 46 |
onCheck={check.onCheck}
|
| 48 | 47 |
onDetail={rowActions.onDetail}
|
--- src/admin/feature/board/master/components/BoardListTableHeader.tsx
+++ src/admin/feature/board/master/components/BoardListTableHeader.tsx
... | ... | @@ -1,5 +1,7 @@ |
| 1 | 1 |
import type {SearchParams} from "../../../../../type/searchParams.ts";
|
| 2 | 2 |
import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx";
|
| 3 |
+import {useTableSort} from "../../../../hook/useTableSort.ts";
|
|
| 4 |
+import {SortableHeaderCell} from "../../../../component/table/SortableHeaderCell.tsx";
|
|
| 3 | 5 |
|
| 4 | 6 |
interface BoardListTableHeaderProps {
|
| 5 | 7 |
params: SearchParams; |
... | ... | @@ -16,32 +18,7 @@ |
| 16 | 18 |
indeterminate, |
| 17 | 19 |
onCheckAll |
| 18 | 20 |
}: BoardListTableHeaderProps) {
|
| 19 |
- const handleSort = (field: string) => {
|
|
| 20 |
- |
|
| 21 |
- const nextOrder = |
|
| 22 |
- params.searchSortCnd === field && |
|
| 23 |
- params.searchSortOrd === 'ASC' |
|
| 24 |
- ? 'DESC' |
|
| 25 |
- : 'ASC'; |
|
| 26 |
- |
|
| 27 |
- onChange({
|
|
| 28 |
- ...params, |
|
| 29 |
- searchSortCnd: field, |
|
| 30 |
- searchSortOrd: nextOrder, |
|
| 31 |
- pageIndex: 1, |
|
| 32 |
- }); |
|
| 33 |
- }; |
|
| 34 |
- |
|
| 35 |
- const getSortIcon = (field: string) => {
|
|
| 36 |
- |
|
| 37 |
- if (params.searchSortCnd !== field) {
|
|
| 38 |
- return '-'; |
|
| 39 |
- } |
|
| 40 |
- |
|
| 41 |
- return params.searchSortOrd === 'ASC' |
|
| 42 |
- ? '▲' |
|
| 43 |
- : '▼'; |
|
| 44 |
- }; |
|
| 21 |
+ const {handleSort, getSortIcon, isSorted} = useTableSort(params, onChange);
|
|
| 45 | 22 |
|
| 46 | 23 |
return ( |
| 47 | 24 |
<> |
... | ... | @@ -69,60 +46,54 @@ |
| 69 | 46 |
/> |
| 70 | 47 |
</th> |
| 71 | 48 |
<th>번호</th> |
| 72 |
- <th> |
|
| 49 |
+ <SortableHeaderCell |
|
| 50 |
+ field="BBS_NM" |
|
| 51 |
+ active={isSorted('BBS_NM')}
|
|
| 52 |
+ icon={getSortIcon('BBS_NM')}
|
|
| 53 |
+ onSort={handleSort}
|
|
| 54 |
+ > |
|
| 73 | 55 |
게시판명 |
| 74 |
- <button |
|
| 75 |
- className={`sort sortBtn ${params.searchSortCnd === 'BBS_NM' ? 'active' : ''}`}
|
|
| 76 |
- onClick={() => handleSort('BBS_NM')}
|
|
| 77 |
- > |
|
| 78 |
- {getSortIcon('BBS_NM')}
|
|
| 79 |
- </button> |
|
| 80 |
- </th> |
|
| 81 |
- <th> |
|
| 56 |
+ </SortableHeaderCell> |
|
| 57 |
+ <SortableHeaderCell |
|
| 58 |
+ field="MENU_NM" |
|
| 59 |
+ active={isSorted('MENU_NM')}
|
|
| 60 |
+ icon={getSortIcon('MENU_NM')}
|
|
| 61 |
+ onSort={handleSort}
|
|
| 62 |
+ > |
|
| 82 | 63 |
연결 메뉴 |
| 83 |
- <button |
|
| 84 |
- className={`sort sortBtn ${params.searchSortCnd === 'MENU_NM' ? 'active' : ''}`}
|
|
| 85 |
- onClick={() => handleSort('MENU_NM')}
|
|
| 86 |
- > |
|
| 87 |
- {getSortIcon('MENU_NM')}
|
|
| 88 |
- </button> |
|
| 89 |
- </th> |
|
| 90 |
- <th> |
|
| 64 |
+ </SortableHeaderCell> |
|
| 65 |
+ <SortableHeaderCell |
|
| 66 |
+ field="TOTCNT" |
|
| 67 |
+ active={isSorted('TOTCNT')}
|
|
| 68 |
+ icon={getSortIcon('TOTCNT')}
|
|
| 69 |
+ onSort={handleSort}
|
|
| 70 |
+ > |
|
| 91 | 71 |
댓글 / 글수 |
| 92 |
- <button |
|
| 93 |
- className={`sort sortBtn ${params.searchSortCnd === 'TOTCNT' ? 'active' : ''}`}
|
|
| 94 |
- onClick={() => handleSort('TOTCNT')}
|
|
| 95 |
- > |
|
| 96 |
- {getSortIcon('TOTCNT')}
|
|
| 97 |
- </button> |
|
| 98 |
- </th> |
|
| 99 |
- <th> |
|
| 72 |
+ </SortableHeaderCell> |
|
| 73 |
+ <SortableHeaderCell |
|
| 74 |
+ field="BBS_TY_CODE_NM" |
|
| 75 |
+ active={isSorted('BBS_TY_CODE_NM')}
|
|
| 76 |
+ icon={getSortIcon('BBS_TY_CODE_NM')}
|
|
| 77 |
+ onSort={handleSort}
|
|
| 78 |
+ > |
|
| 100 | 79 |
게시판유형 |
| 101 |
- <button |
|
| 102 |
- className={`sort sortBtn ${params.searchSortCnd === 'BBS_TY_CODE_NM' ? 'active' : ''}`}
|
|
| 103 |
- onClick={() => handleSort('BBS_TY_CODE_NM')}
|
|
| 104 |
- > |
|
| 105 |
- {getSortIcon('BBS_TY_CODE_NM')}
|
|
| 106 |
- </button> |
|
| 107 |
- </th> |
|
| 108 |
- <th> |
|
| 80 |
+ </SortableHeaderCell> |
|
| 81 |
+ <SortableHeaderCell |
|
| 82 |
+ field="FRST_REGIST_PNTTM" |
|
| 83 |
+ active={isSorted('FRST_REGIST_PNTTM')}
|
|
| 84 |
+ icon={getSortIcon('FRST_REGIST_PNTTM')}
|
|
| 85 |
+ onSort={handleSort}
|
|
| 86 |
+ > |
|
| 109 | 87 |
생성일 |
| 110 |
- <button |
|
| 111 |
- className={`sort sortBtn ${params.searchSortCnd === 'FRST_REGIST_PNTTM' ? 'active' : ''}`}
|
|
| 112 |
- onClick={() => handleSort('FRST_REGIST_PNTTM')}
|
|
| 113 |
- > |
|
| 114 |
- {getSortIcon('FRST_REGIST_PNTTM')}
|
|
| 115 |
- </button> |
|
| 116 |
- </th> |
|
| 117 |
- <th> |
|
| 88 |
+ </SortableHeaderCell> |
|
| 89 |
+ <SortableHeaderCell |
|
| 90 |
+ field="USE_AT" |
|
| 91 |
+ active={isSorted('USE_AT')}
|
|
| 92 |
+ icon={getSortIcon('USE_AT')}
|
|
| 93 |
+ onSort={handleSort}
|
|
| 94 |
+ > |
|
| 118 | 95 |
사용여부 |
| 119 |
- <button |
|
| 120 |
- className={`sort sortBtn ${params.searchSortCnd === 'USE_AT' ? 'active' : ''}`}
|
|
| 121 |
- onClick={() => handleSort('USE_AT')}
|
|
| 122 |
- > |
|
| 123 |
- {getSortIcon('USE_AT')}
|
|
| 124 |
- </button> |
|
| 125 |
- </th> |
|
| 96 |
+ </SortableHeaderCell> |
|
| 126 | 97 |
<th>게시판 관리</th> |
| 127 | 98 |
</tr> |
| 128 | 99 |
</thead> |
--- src/admin/feature/board/master/components/BoardListTableRow.tsx
+++ src/admin/feature/board/master/components/BoardListTableRow.tsx
... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 |
import type {BoardListItem} from "../type/boardMaster.types.ts";
|
| 2 | 2 |
import type {SearchParams} from "../../../../../type/searchParams.ts";
|
| 3 | 3 |
import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx";
|
| 4 |
+import {getTableRowNumber} from "../../../../component/table/getTableRowNumber.ts";
|
|
| 4 | 5 |
|
| 5 | 6 |
interface BoardListTableRowProps {
|
| 6 | 7 |
item: BoardListItem |
... | ... | @@ -8,7 +9,6 @@ |
| 8 | 9 |
searchParams: SearchParams |
| 9 | 10 |
totalItems: number |
| 10 | 11 |
currentPage: number |
| 11 |
- totalPages: number |
|
| 12 | 12 |
checked: boolean |
| 13 | 13 |
onCheck: (id: string, checked: boolean) => void |
| 14 | 14 |
onDetail: (bbsId: string) => void |
... | ... | @@ -22,16 +22,19 @@ |
| 22 | 22 |
searchParams, |
| 23 | 23 |
totalItems, |
| 24 | 24 |
currentPage, |
| 25 |
- totalPages, |
|
| 26 | 25 |
checked, |
| 27 | 26 |
onCheck, |
| 28 | 27 |
onDetail, |
| 29 | 28 |
onArticleList, |
| 30 | 29 |
onPreview |
| 31 | 30 |
}: BoardListTableRowProps) {
|
| 32 |
- const rowNumber = searchParams.searchSortOrd === 'DESC' |
|
| 33 |
- ? totalItems - (currentPage - 1) * totalPages - index |
|
| 34 |
- : (currentPage - 1) * totalPages + (index + 1) |
|
| 31 |
+ const rowNumber = getTableRowNumber({
|
|
| 32 |
+ searchParams, |
|
| 33 |
+ totalItems, |
|
| 34 |
+ currentPage, |
|
| 35 |
+ index, |
|
| 36 |
+ }); |
|
| 37 |
+ |
|
| 35 | 38 |
const bbsId = item.bbsId; |
| 36 | 39 |
|
| 37 | 40 |
return ( |
+++ src/admin/hook/useTableSort.ts
... | ... | @@ -0,0 +1,40 @@ |
| 1 | +import type {SearchParams} from "../../type/searchParams.ts"; | |
| 2 | + | |
| 3 | +export const useTableSort = <T extends SearchParams>( | |
| 4 | + params: T, | |
| 5 | + onChange: (params: T) => void | |
| 6 | +) => { | |
| 7 | + const handleSort = (field: string) => { | |
| 8 | + const nextOrder = | |
| 9 | + params.searchSortCnd === field && | |
| 10 | + params.searchSortOrd === 'ASC' | |
| 11 | + ? 'DESC' | |
| 12 | + : 'ASC'; | |
| 13 | + | |
| 14 | + onChange({ | |
| 15 | + ...params, | |
| 16 | + searchSortCnd: field, | |
| 17 | + searchSortOrd: nextOrder, | |
| 18 | + pageIndex: 1, | |
| 19 | + }); | |
| 20 | + }; | |
| 21 | + | |
| 22 | + const getSortIcon = (field: string) => { | |
| 23 | + if (params.searchSortCnd !== field) { | |
| 24 | + return '-'; | |
| 25 | + } | |
| 26 | + | |
| 27 | + return params.searchSortOrd === 'ASC' | |
| 28 | + ? '▲' | |
| 29 | + : '▼'; | |
| 30 | + }; | |
| 31 | + | |
| 32 | + const isSorted = (field: string) => | |
| 33 | + params.searchSortCnd === field; | |
| 34 | + | |
| 35 | + return { | |
| 36 | + handleSort, | |
| 37 | + getSortIcon, | |
| 38 | + isSorted, | |
| 39 | + }; | |
| 40 | +}; |
--- src/admin/layout/AdminLayout.tsx
+++ src/admin/layout/AdminLayout.tsx
... | ... | @@ -1,12 +1,8 @@ |
| 1 |
-import type {ReactNode} from "react";
|
|
| 1 |
+import {Outlet} from "react-router-dom";
|
|
| 2 | 2 |
import {AdminSideBar} from "./AdminSideBar.tsx";
|
| 3 | 3 |
import {AdminTopBar} from "./AdminTopBar.tsx";
|
| 4 | 4 |
|
| 5 |
-type AdminLayoutProps = {
|
|
| 6 |
- children: ReactNode; |
|
| 7 |
-} |
|
| 8 |
- |
|
| 9 |
-export function AdminLayout({children}: AdminLayoutProps) {
|
|
| 5 |
+export function AdminLayout() {
|
|
| 10 | 6 |
return ( |
| 11 | 7 |
<div className="wrap"> |
| 12 | 8 |
<AdminSideBar/> |
... | ... | @@ -14,9 +10,9 @@ |
| 14 | 10 |
<div className="container sub"> |
| 15 | 11 |
<AdminTopBar/> |
| 16 | 12 |
<div className="content_wrap"> |
| 17 |
- {children}
|
|
| 13 |
+ <Outlet/> |
|
| 18 | 14 |
</div> |
| 19 | 15 |
</div> |
| 20 | 16 |
</div> |
| 21 | 17 |
); |
| 22 |
-}(No newline at end of file) |
|
| 18 |
+} |
--- src/admin/route/AdminRoute.tsx
+++ src/admin/route/AdminRoute.tsx
... | ... | @@ -1,16 +1,20 @@ |
| 1 | 1 |
import {Navigate, Route, Routes} from "react-router-dom";
|
| 2 | 2 |
import {BoardListPage} from "../feature/board/master/page/BoardListPage.tsx";
|
| 3 | 3 |
import {
|
| 4 |
- ADMIN_AUTHOR_DETAIL_ROUTE, |
|
| 4 |
+ ADMIN_AUTHOR_DETAIL_ROUTE, ADMIN_AUTHOR_GROUP_LIST_ROUTE, |
|
| 5 | 5 |
ADMIN_AUTHOR_LIST_ROUTE, |
| 6 | 6 |
ADMIN_AUTHOR_ROLE_LIST_ROUTE, ADMIN_BBS_ARTICLE_FORM_ROUTE, |
| 7 |
- ADMIN_BBS_MASTER_ROUTE, ADMIN_ROLE_FORM_ROUTE |
|
| 7 |
+ ADMIN_BBS_MASTER_ROUTE, ADMIN_MENU_CREATE_MANAGE_ROUTE, ADMIN_MENU_POPUP_ROUTE, ADMIN_ROLE_FORM_ROUTE |
|
| 8 | 8 |
} from "./adminRouteMap.ts"; |
| 9 | 9 |
import {BoardArticleListPage} from "../feature/board/article/page/BoardArticleListPage.tsx";
|
| 10 | 10 |
import {BoardFormPage} from "../feature/board/master/page/BoardFormPage.tsx";
|
| 11 | 11 |
import {AuthorListPage} from "../feature/role/author/page/AuthorListPage.tsx";
|
| 12 | 12 |
import {AuthorRoleListPage} from "../feature/role/authorRole/page/AuthorRoleListPage.tsx";
|
| 13 | 13 |
import {AuthorRoleFormPage} from "../feature/role/role/page/AuthorRoleFormPage.tsx";
|
| 14 |
+import {AuthorRoleMenuListPage} from "../feature/role/authorRoleMenu/page/AuthorRoleMenuListPage.tsx";
|
|
| 15 |
+import {AuthorRoleMenuPopupPage} from "../feature/role/authorRoleMenu/page/AuthorRoleMenuPopupPage.tsx";
|
|
| 16 |
+import {AdminLayout} from "../layout/AdminLayout.tsx";
|
|
| 17 |
+import {AuthorGroupListPage} from "../feature/role/authorGroup/page/AuthorGroupListPage.tsx";
|
|
| 14 | 18 |
|
| 15 | 19 |
const ReadyPage = () => {
|
| 16 | 20 |
return <div>Preparing menu.</div>; |
... | ... | @@ -19,17 +23,23 @@ |
| 19 | 23 |
export const AdminRoute = () => {
|
| 20 | 24 |
return ( |
| 21 | 25 |
<Routes> |
| 22 |
- <Route path="/" element={<Navigate to={ADMIN_BBS_MASTER_ROUTE} replace/>}/>
|
|
| 23 |
- <Route path={ADMIN_BBS_MASTER_ROUTE} element={<BoardListPage/>}/>
|
|
| 24 |
- <Route path={`/admin/cop/bbs/article/:bbsId`} element={<BoardArticleListPage/>}/>
|
|
| 25 |
- <Route path={`${ADMIN_BBS_ARTICLE_FORM_ROUTE}:bbsId`} element={<BoardFormPage/>}/>
|
|
| 26 |
- <Route path={ADMIN_BBS_ARTICLE_FORM_ROUTE} element={<BoardFormPage/>}/>
|
|
| 26 |
+ <Route path={`${ADMIN_MENU_POPUP_ROUTE}/:authorCode`} element={<AuthorRoleMenuPopupPage/>}/>
|
|
| 27 | 27 |
|
| 28 |
- <Route path={ADMIN_AUTHOR_LIST_ROUTE} element={<AuthorListPage/>}/>
|
|
| 29 |
- <Route path={ADMIN_AUTHOR_DETAIL_ROUTE} element={<ReadyPage/>}/>
|
|
| 30 |
- <Route path={ADMIN_AUTHOR_ROLE_LIST_ROUTE} element={<AuthorRoleListPage/>}/>
|
|
| 31 |
- <Route path={ADMIN_ROLE_FORM_ROUTE} element={<AuthorRoleFormPage/>}/>
|
|
| 32 |
- <Route path="*" element={<ReadyPage/>}/>
|
|
| 28 |
+ <Route element={<AdminLayout/>}>
|
|
| 29 |
+ <Route path="/" element={<Navigate to={ADMIN_BBS_MASTER_ROUTE} replace/>}/>
|
|
| 30 |
+ <Route path={ADMIN_BBS_MASTER_ROUTE} element={<BoardListPage/>}/>
|
|
| 31 |
+ <Route path={`/admin/cop/bbs/article/:bbsId`} element={<BoardArticleListPage/>}/>
|
|
| 32 |
+ <Route path={`${ADMIN_BBS_ARTICLE_FORM_ROUTE}:bbsId`} element={<BoardFormPage/>}/>
|
|
| 33 |
+ <Route path={ADMIN_BBS_ARTICLE_FORM_ROUTE} element={<BoardFormPage/>}/>
|
|
| 34 |
+ |
|
| 35 |
+ <Route path={ADMIN_AUTHOR_LIST_ROUTE} element={<AuthorListPage/>}/>
|
|
| 36 |
+ <Route path={ADMIN_AUTHOR_DETAIL_ROUTE} element={<ReadyPage/>}/>
|
|
| 37 |
+ <Route path={ADMIN_AUTHOR_ROLE_LIST_ROUTE} element={<AuthorRoleListPage/>}/>
|
|
| 38 |
+ <Route path={ADMIN_ROLE_FORM_ROUTE} element={<AuthorRoleFormPage/>}/>
|
|
| 39 |
+ <Route path={ADMIN_MENU_CREATE_MANAGE_ROUTE} element={<AuthorRoleMenuListPage/>}/>
|
|
| 40 |
+ <Route path={ADMIN_AUTHOR_GROUP_LIST_ROUTE} element={<AuthorGroupListPage/>}/>
|
|
| 41 |
+ <Route path="*" element={<ReadyPage/>}/>
|
|
| 42 |
+ </Route> |
|
| 33 | 43 |
</Routes> |
| 34 | 44 |
); |
| 35 | 45 |
}; |
--- src/admin/route/adminRouteMap.ts
+++ src/admin/route/adminRouteMap.ts
... | ... | @@ -18,10 +18,16 @@ |
| 18 | 18 |
export const ADMIN_LOGIN_GROUP_POLICY_ROUTE = `${ADMIN_ROUTE_PREFIX}/uat/uap/selectLoginGroupPolicyList.do`;
|
| 19 | 19 |
export const ADMIN_MEMBER_MANAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/umt/EgovMberManage.do`;
|
| 20 | 20 |
export const ADMIN_POPUP_ZONE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/popupZoneList.do`;
|
| 21 |
+ |
|
| 22 |
+ |
|
| 21 | 23 |
export const ADMIN_MENU_CREATE_MANAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/mnu/mcm/EgovMenuCreatManageSelect.do`;
|
| 24 |
+export const ADMIN_MENU_POPUP_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/mnu/mcm/detail.do`;
|
|
| 25 |
+ |
|
| 26 |
+export const ADMIN_AUTHOR_GROUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rgm/EgovAuthorGroupList.do`;
|
|
| 27 |
+ |
|
| 28 |
+ |
|
| 22 | 29 |
export const ADMIN_USER_MANAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/umt/user/EgovUserManage.do`;
|
| 23 | 30 |
export const ADMIN_COMMON_CODE_TREE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/ccm/ccc/EgovCcmCmmnCodeTree.do`;
|
| 24 |
-export const ADMIN_AUTHOR_GROUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rgm/EgovAuthorGroupList.do`;
|
|
| 25 | 31 |
export const ADMIN_POPUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/egovPopupList.do`;
|
| 26 | 32 |
export const ADMIN_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectWebLogList.do`;
|
| 27 | 33 |
|
--- src/styles/adm/popup.css
+++ src/styles/adm/popup.css
... | ... | @@ -57,7 +57,11 @@ |
| 57 | 57 |
.popup_window .tree .tree-node{display:flex;height:auto;font-size:14px;flex-direction:column;align-items:flex-start;padding:0 12px;}
|
| 58 | 58 |
.popup_window .tree .tree-node label{display:flex;min-height:35px;align-items:center;gap:4px;}
|
| 59 | 59 |
.popup_window .tree-node.folder .tree-label{padding:0 0 0 26px;}
|
| 60 |
-.popup_window .tree-node.folder .tree-label::before{width:24px;height:24px;top:50%;transform:translateY(-50%);}
|
|
| 60 |
+.popup_window .tree-node.folder .tree-label{position:relative;}
|
|
| 61 |
+.popup_window .tree-node.folder .tree-label::before{position:absolute;left:0;width:24px;height:24px;top:50%;content:"";background:url(/publish/adm/images/component/icon_folder.png) no-repeat center;transform:translateY(-50%);}
|
|
| 62 |
+.popup_window .tree-node.folder:has(.tree-children)>.tree-label::before{background-image:url(/publish/adm/images/component/icon_folder_open.png);}
|
|
| 61 | 63 |
.popup_window .tree-node.file label{min-height:25px;}
|
| 64 |
+.popup_window .tree-node.file .tree-label{position:relative;padding:0 0 0 26px;}
|
|
| 65 |
+.popup_window .tree-node.file .tree-label::before{position:absolute;left:0;width:24px;height:24px;top:50%;content:"";background:url(/publish/adm/images/component/icon_file.png) no-repeat center;transform:translateY(-50%);}
|
|
| 62 | 66 |
.popup_window .tree .tree-children{display:flex;width:calc(100% - 24px);font-size:14px;background:#f2f4f5;flex-direction:column;gap:4px;border-radius:4px;padding:8px 12px;margin:0 auto;}
|
| 63 | 67 |
.popup_window .tree .tree-children .tree-node{height:auto;}
|
--- src/type/pageResponse.ts
+++ src/type/pageResponse.ts
... | ... | @@ -9,14 +9,15 @@ |
| 9 | 9 |
size: number; |
| 10 | 10 |
} |
| 11 | 11 |
|
| 12 |
-export const createPageQueryResult = <T>( |
|
| 13 |
- query: UseQueryResult<PageResponse<T>> |
|
| 12 |
+export const createPageQueryResult = <T, E = Record<string, unknown>>( |
|
| 13 |
+ query: UseQueryResult<PageResponse<T, E>> |
|
| 14 | 14 |
) => ({
|
| 15 | 15 |
list: query.data?.list ?? [], |
| 16 |
+ extraData: query.data?.extraData ?? null, |
|
| 16 | 17 |
totalItems: query.data?.totalItems ?? 0, |
| 17 | 18 |
totalPages: query.data?.totalPages ?? 0, |
| 18 | 19 |
currentPage: query.data?.currentPage ?? 0, |
| 19 | 20 |
size: query.data?.size ?? 0, |
| 20 | 21 |
isLoading: query.isLoading, |
| 21 | 22 |
error: query.error, |
| 22 |
-});(No newline at end of file) |
|
| 23 |
+}); |
--- src/type/viewModel.ts
+++ src/type/viewModel.ts
... | ... | @@ -51,8 +51,13 @@ |
| 51 | 51 |
"totalItems" | "currentPage" | "totalPages" |
| 52 | 52 |
>; |
| 53 | 53 |
|
| 54 |
-export type ListTableModel<TItem, TSearchParams extends SearchParams> = {
|
|
| 54 |
+export type ListTableModel< |
|
| 55 |
+ TItem, |
|
| 56 |
+ TSearchParams extends SearchParams, |
|
| 57 |
+ TExtraData = unknown |
|
| 58 |
+> = {
|
|
| 55 | 59 |
items: TItem[]; |
| 60 |
+ extraData?: TExtraData | null; |
|
| 56 | 61 |
params: TSearchParams; |
| 57 | 62 |
onChange: (params: TSearchParams) => void; |
| 58 | 63 |
pagination: TablePaginationModel; |
... | ... | @@ -61,8 +66,9 @@ |
| 61 | 66 |
export type CheckableTableModel< |
| 62 | 67 |
TItem, |
| 63 | 68 |
TSearchParams extends SearchParams, |
| 64 |
- TId extends string | number = string |
|
| 65 |
-> = ListTableModel<TItem, TSearchParams> & {
|
|
| 69 |
+ TId extends string | number = string, |
|
| 70 |
+ TExtraData = unknown |
|
| 71 |
+> = ListTableModel<TItem, TSearchParams, TExtraData> & {
|
|
| 66 | 72 |
check: TableCheckModel<TId>; |
| 67 | 73 |
}; |
| 68 | 74 |
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?