공용 Model 생성 [viewModel]에 따른 board 수정 및 권한관리 권한별롤관리 생성
@ad3518496a6225e143c1cd39cd376428a46e8ef3
--- src/admin/component/ListSearchForm.tsx
+++ src/admin/component/ListSearchForm.tsx
... | ... | @@ -2,13 +2,9 @@ |
| 2 | 2 |
import {SearchBar} from "./SearchBar.tsx";
|
| 3 | 3 |
import type {SearchParams} from "../../type/searchParams.ts";
|
| 4 | 4 |
import {PageSizeSelector} from "./pagination/PageSizeSelector.tsx";
|
| 5 |
+import type {SearchModel} from "../../type/viewModel.ts";
|
|
| 5 | 6 |
|
| 6 |
-interface ListSearchFormProps<T extends SearchParams> {
|
|
| 7 |
- totalItems: number; |
|
| 8 |
- searchParams: T; |
|
| 9 |
- onChange: (params: T) => void; |
|
| 10 |
- searchOptions: { value: string; label: string; }[];
|
|
| 11 |
- pageSizeOptions?: { value: string; label: string; }[];
|
|
| 7 |
+interface ListSearchFormProps<T extends SearchParams> extends SearchModel<T> {
|
|
| 12 | 8 |
totalLabel?: string; |
| 13 | 9 |
} |
| 14 | 10 |
|
... | ... | @@ -45,7 +41,7 @@ |
| 45 | 41 |
<div className="search_area"> |
| 46 | 42 |
<div className="search_left"> |
| 47 | 43 |
<p className="total_number"> |
| 48 |
- {totalLabel} <b>{totalItems}</b>
|
|
| 44 |
+ {totalLabel} <b>{totalItems}</b>건
|
|
| 49 | 45 |
</p> |
| 50 | 46 |
</div> |
| 51 | 47 |
|
--- src/admin/component/PageHeader.tsx
+++ src/admin/component/PageHeader.tsx
... | ... | @@ -1,10 +1,6 @@ |
| 1 |
-interface PageHeaderProps {
|
|
| 2 |
- title: string; |
|
| 3 |
- breadcrumb: any[]; |
|
| 4 |
- homeUrl?: string; |
|
| 5 |
-} |
|
| 1 |
+import type {HeaderModel} from "../../type/viewModel.ts";
|
|
| 6 | 2 |
|
| 7 |
-export const PageHeader = ({title, breadcrumb, homeUrl}: PageHeaderProps) => {
|
|
| 3 |
+export const PageHeader = ({title, breadcrumb, homeUrl}: HeaderModel) => {
|
|
| 8 | 4 |
return ( |
| 9 | 5 |
<div className="content_title"> |
| 10 | 6 |
<div className="left"> |
--- src/admin/component/SearchBar.tsx
+++ src/admin/component/SearchBar.tsx
... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 |
interface SearchBarProps {
|
| 4 | 4 |
searchCnd: string |
| 5 | 5 |
searchKeyword: string |
| 6 |
- options: { value: string; label: string }[]
|
|
| 6 |
+ options?: { value: string; label: string }[]
|
|
| 7 | 7 |
onChange: ( |
| 8 | 8 |
name: string, |
| 9 | 9 |
value: string |
... | ... | @@ -22,20 +22,24 @@ |
| 22 | 22 |
|
| 23 | 23 |
return ( |
| 24 | 24 |
<> |
| 25 |
- <select |
|
| 26 |
- id="searchCnd" |
|
| 27 |
- name="searchCnd" |
|
| 28 |
- className="search_select" |
|
| 29 |
- value={searchCnd}
|
|
| 30 |
- onChange={handleChange}
|
|
| 31 |
- > |
|
| 32 |
- {options.map((option) => (
|
|
| 33 |
- <option key={option.value} value={option.value}>
|
|
| 34 |
- {option.label}
|
|
| 35 |
- </option> |
|
| 36 |
- ))} |
|
| 37 |
- </select> |
|
| 38 |
- |
|
| 25 |
+ {options && (
|
|
| 26 |
+ <select |
|
| 27 |
+ id="searchCnd" |
|
| 28 |
+ name="searchCnd" |
|
| 29 |
+ className="search_select" |
|
| 30 |
+ value={searchCnd}
|
|
| 31 |
+ onChange={handleChange}
|
|
| 32 |
+ > |
|
| 33 |
+ {options.map((option) => (
|
|
| 34 |
+ <option |
|
| 35 |
+ key={option.value}
|
|
| 36 |
+ value={option.value}
|
|
| 37 |
+ > |
|
| 38 |
+ {option.label}
|
|
| 39 |
+ </option> |
|
| 40 |
+ ))} |
|
| 41 |
+ </select> |
|
| 42 |
+ )} |
|
| 39 | 43 |
<div className="search_type input_type"> |
| 40 | 44 |
<input |
| 41 | 45 |
type="text" |
--- src/admin/component/pagination/Pagination.tsx
+++ src/admin/component/pagination/Pagination.tsx
... | ... | @@ -1,10 +1,4 @@ |
| 1 |
-type PaginationProps = {
|
|
| 2 |
- totalItems: number; |
|
| 3 |
- totalPages: number; |
|
| 4 |
- currentPage: number; |
|
| 5 |
- size: number; |
|
| 6 |
- onPageChange: (page: number) => void; |
|
| 7 |
-}; |
|
| 1 |
+import type {PaginationModel} from "../../../type/viewModel.ts";
|
|
| 8 | 2 |
|
| 9 | 3 |
export function Pagination({
|
| 10 | 4 |
totalItems, |
... | ... | @@ -12,7 +6,7 @@ |
| 12 | 6 |
currentPage, |
| 13 | 7 |
size = 10, |
| 14 | 8 |
onPageChange, |
| 15 |
- }: PaginationProps) {
|
|
| 9 |
+ }: PaginationModel) {
|
|
| 16 | 10 |
|
| 17 | 11 |
if (totalItems === 0) {
|
| 18 | 12 |
return null; |
--- src/admin/feature/board/api/boardApi.ts
+++ src/admin/feature/board/api/boardApi.ts
... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 |
import type {
|
| 4 | 4 |
BoardArticleExtra, |
| 5 | 5 |
BoardArticleListItem, |
| 6 |
- BoardArticleSearchParams, |
|
| 6 |
+ BoardArticleSearchParams, BoardDeleteListItem, |
|
| 7 | 7 |
BoardDetailResponse, |
| 8 | 8 |
BoardFormItem, |
| 9 | 9 |
BoardListItem, |
... | ... | @@ -23,7 +23,7 @@ |
| 23 | 23 |
} |
| 24 | 24 |
|
| 25 | 25 |
export async function createBoard(params: BoardFormItem) {
|
| 26 |
- return apiClient.post(`/cop/bbs/insertBoardMasater.do`, params); |
|
| 26 |
+ return apiClient.post(`/cop/bbs/insertBoardMaster.do`, params); |
|
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 | 29 |
export async function updateBoard(params: BoardFormItem) {
|
... | ... | @@ -34,6 +34,6 @@ |
| 34 | 34 |
return apiClient.post(`/cop/bbs/deleteBoardMaster.do?bbsId=${bbsId}`);
|
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 |
-export async function deleteBoardBatch() {
|
|
| 38 |
- return apiClient.post(`/cop/bbs/deleteBoardBatch.do`, {});
|
|
| 37 |
+export async function deleteBoardBatch(bbsIds: BoardDeleteListItem[] ) {
|
|
| 38 |
+ return apiClient.post(`/cop/bbs/deleteBoardMasterBatch.do`, bbsIds); |
|
| 39 | 39 |
} |
--- src/admin/feature/board/components/article/BoardArticleListTable.tsx
+++ src/admin/feature/board/components/article/BoardArticleListTable.tsx
... | ... | @@ -2,35 +2,17 @@ |
| 2 | 2 |
import {EmptyRow} from "../../../../component/EmptyRow.tsx";
|
| 3 | 3 |
import {BoardArticleListTableHeader} from "./BoardArticleListTableHeader.tsx";
|
| 4 | 4 |
import {BoardArticleListTableRow} from "./BoardArticleListTableRow.tsx";
|
| 5 |
+import type {CheckableTableModel} from "../../../../../type/viewModel.ts";
|
|
| 5 | 6 |
|
| 6 |
-type BoartArticleListTableProps = {
|
|
| 7 |
+type BoardArticleListTableProps = CheckableTableModel<BoardArticleListItem, BoardArticleSearchParams>; |
|
| 7 | 8 |
|
| 8 |
- items: BoardArticleListItem[]; |
|
| 9 |
- params: BoardArticleSearchParams; |
|
| 10 |
- onChange: (params: BoardArticleSearchParams) => void; |
|
| 11 |
- isAllChecked: boolean; |
|
| 12 |
- isPartiallyChecked: boolean; |
|
| 13 |
- isChecked: (id: string) => boolean; |
|
| 14 |
- onCheck: (id: string, checked: boolean) => void; |
|
| 15 |
- onCheckAll: (checked: boolean) => void; |
|
| 16 |
- totalItems: number |
|
| 17 |
- currentPage: number |
|
| 18 |
- totalPages: number |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-export const BoartArticleListTable = ({
|
|
| 9 |
+export const BoardArticleListTable = ({
|
|
| 22 | 10 |
items, |
| 23 | 11 |
params, |
| 24 | 12 |
onChange, |
| 25 |
- isAllChecked, |
|
| 26 |
- isPartiallyChecked, |
|
| 27 |
- isChecked, |
|
| 28 |
- onCheck, |
|
| 29 |
- onCheckAll, |
|
| 30 |
- totalItems, |
|
| 31 |
- currentPage, |
|
| 32 |
- totalPages |
|
| 33 |
- }: BoartArticleListTableProps) => {
|
|
| 13 |
+ check, |
|
| 14 |
+ pagination |
|
| 15 |
+ }: BoardArticleListTableProps) => {
|
|
| 34 | 16 |
|
| 35 | 17 |
return ( |
| 36 | 18 |
<div className={"table table_type_cols"}>
|
... | ... | @@ -38,9 +20,9 @@ |
| 38 | 20 |
<BoardArticleListTableHeader |
| 39 | 21 |
params={params}
|
| 40 | 22 |
onChange={onChange}
|
| 41 |
- checked={isAllChecked}
|
|
| 42 |
- indeterminate={isPartiallyChecked}
|
|
| 43 |
- onCheckAll={onCheckAll}
|
|
| 23 |
+ checked={check.isAllChecked}
|
|
| 24 |
+ indeterminate={check.isPartiallyChecked}
|
|
| 25 |
+ onCheckAll={check.onCheckAll}
|
|
| 44 | 26 |
/> |
| 45 | 27 |
<tbody> |
| 46 | 28 |
{items.length > 0 ?
|
... | ... | @@ -50,11 +32,11 @@ |
| 50 | 32 |
item={item}
|
| 51 | 33 |
index={index}
|
| 52 | 34 |
searchParams={params}
|
| 53 |
- totalItems={totalItems}
|
|
| 54 |
- currentPage={currentPage}
|
|
| 55 |
- totalPages={totalPages}
|
|
| 56 |
- checked={isChecked(item.nttId)}
|
|
| 57 |
- onCheck={onCheck}
|
|
| 35 |
+ totalItems={pagination.totalItems}
|
|
| 36 |
+ currentPage={pagination.currentPage}
|
|
| 37 |
+ totalPages={pagination.totalPages}
|
|
| 38 |
+ checked={check.isChecked(item.nttId)}
|
|
| 39 |
+ onCheck={check.onCheck}
|
|
| 58 | 40 |
/> |
| 59 | 41 |
)) : |
| 60 | 42 |
(<EmptyRow colSpan={8}/>)
|
--- src/admin/feature/board/components/master/BoardFormTable.tsx
+++ src/admin/feature/board/components/master/BoardFormTable.tsx
... | ... | @@ -1,24 +1,21 @@ |
| 1 | 1 |
import type {ChangeEvent} from "react";
|
| 2 | 2 |
import type {BoardFormItem} from "../../type/board.types.ts";
|
| 3 |
-import type {CommonCodeItem} from "../../../../../type/code.ts";
|
|
| 4 | 3 |
|
| 5 | 4 |
type BoardFormTableProps = {
|
| 6 | 5 |
form: BoardFormItem; |
| 7 |
- typeList?: CommonCodeItem[]; |
|
| 8 | 6 |
onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => void; |
| 9 | 7 |
}; |
| 10 | 8 |
|
| 11 | 9 |
export const BoardFormTable = ({
|
| 12 | 10 |
form, |
| 13 |
- typeList = [], |
|
| 14 | 11 |
onChange |
| 15 | 12 |
}: BoardFormTableProps) => {
|
| 16 | 13 |
return ( |
| 17 | 14 |
<div className="table table_type_rows"> |
| 18 | 15 |
<table> |
| 19 | 16 |
<colgroup> |
| 20 |
- <col style={{ width: '200px' }} />
|
|
| 21 |
- <col style={{ width: 'auto' }} />
|
|
| 17 |
+ <col style={{width: '200px'}}/>
|
|
| 18 |
+ <col style={{width: 'auto'}}/>
|
|
| 22 | 19 |
</colgroup> |
| 23 | 20 |
|
| 24 | 21 |
<tbody> |
... | ... | @@ -44,20 +41,31 @@ |
| 44 | 41 |
<span className="required">*</span> |
| 45 | 42 |
게시판유형 |
| 46 | 43 |
</th> |
| 44 |
+ |
|
| 47 | 45 |
<td> |
| 48 |
- {typeList.map((type) => (
|
|
| 49 |
- <label key={type.code}>
|
|
| 50 |
- <input |
|
| 51 |
- type="radio" |
|
| 52 |
- id={`bbsTyCode_${type.code}`}
|
|
| 53 |
- name="bbsTyCode" |
|
| 54 |
- value={type.code}
|
|
| 55 |
- checked={form.bbsTyCode === type.code}
|
|
| 56 |
- onChange={onChange}
|
|
| 57 |
- /> |
|
| 58 |
- {type.codeNm}
|
|
| 59 |
- </label> |
|
| 60 |
- ))} |
|
| 46 |
+ <label> |
|
| 47 |
+ <input |
|
| 48 |
+ id="bbsTyCode_BBST01" |
|
| 49 |
+ type="radio" |
|
| 50 |
+ value="BBST01" |
|
| 51 |
+ name="bbsTyCode" |
|
| 52 |
+ checked={form.bbsTyCode === 'BBST01'}
|
|
| 53 |
+ onChange={onChange}
|
|
| 54 |
+ /> |
|
| 55 |
+ 일반게시판 |
|
| 56 |
+ </label> |
|
| 57 |
+ |
|
| 58 |
+ <label> |
|
| 59 |
+ <input |
|
| 60 |
+ id="bbsTyCode_BBST05" |
|
| 61 |
+ type="radio" |
|
| 62 |
+ value="BBST05" |
|
| 63 |
+ name="bbsTyCode" |
|
| 64 |
+ checked={form.bbsTyCode === 'BBST05'}
|
|
| 65 |
+ onChange={onChange}
|
|
| 66 |
+ /> |
|
| 67 |
+ 포토형게시판 |
|
| 68 |
+ </label> |
|
| 61 | 69 |
</td> |
| 62 | 70 |
</tr> |
| 63 | 71 |
|
--- src/admin/feature/board/components/master/BoardListTable.tsx
+++ src/admin/feature/board/components/master/BoardListTable.tsx
... | ... | @@ -3,39 +3,23 @@ |
| 3 | 3 |
import {EmptyRow} from "../../../../component/EmptyRow.tsx";
|
| 4 | 4 |
import {BoardListTableHeader} from "./BoardListTableHeader.tsx";
|
| 5 | 5 |
import {BoardListTableRow} from "./BoardListTableRow.tsx";
|
| 6 |
+import type {CheckableTableModel, RowActionsModel} from "../../../../../type/viewModel.ts";
|
|
| 6 | 7 |
|
| 7 |
-interface BoardListTableProps {
|
|
| 8 |
- items: BoardListItem[] |
|
| 9 |
- params: SearchParams |
|
| 10 |
- onChange: (params: SearchParams) => void |
|
| 11 |
- totalItems: number |
|
| 12 |
- currentPage: number |
|
| 13 |
- totalPages: number |
|
| 14 |
- isAllChecked: boolean |
|
| 15 |
- isPartiallyChecked: boolean |
|
| 16 |
- isChecked: (id: string) => boolean |
|
| 17 |
- onCheck: (id: string, checked: boolean) => void |
|
| 18 |
- onCheckAll: (checked: boolean) => void |
|
| 19 |
- onDetail: (bbsId: string) => void |
|
| 20 |
- onArticleList: (bbsId: string) => void |
|
| 21 |
- onPreview: (bbsId: string) => void |
|
| 22 |
-} |
|
| 8 |
+type BoardListTableProps = |
|
| 9 |
+ CheckableTableModel<BoardListItem, SearchParams> & |
|
| 10 |
+ RowActionsModel<{
|
|
| 11 |
+ onDetail: (bbsId: string) => void |
|
| 12 |
+ onArticleList: (bbsId: string) => void |
|
| 13 |
+ onPreview: (bbsId: string) => void |
|
| 14 |
+ }>; |
|
| 23 | 15 |
|
| 24 | 16 |
export function BoardListTable({
|
| 25 | 17 |
items, |
| 26 | 18 |
params, |
| 27 | 19 |
onChange, |
| 28 |
- totalItems, |
|
| 29 |
- currentPage, |
|
| 30 |
- totalPages, |
|
| 31 |
- isAllChecked, |
|
| 32 |
- isPartiallyChecked, |
|
| 33 |
- isChecked, |
|
| 34 |
- onCheck, |
|
| 35 |
- onCheckAll, |
|
| 36 |
- onDetail, |
|
| 37 |
- onArticleList, |
|
| 38 |
- onPreview |
|
| 20 |
+ pagination, |
|
| 21 |
+ check, |
|
| 22 |
+ rowActions |
|
| 39 | 23 |
}: BoardListTableProps) {
|
| 40 | 24 |
|
| 41 | 25 |
return ( |
... | ... | @@ -44,9 +28,9 @@ |
| 44 | 28 |
<BoardListTableHeader |
| 45 | 29 |
params={params}
|
| 46 | 30 |
onChange={onChange}
|
| 47 |
- checked={isAllChecked}
|
|
| 48 |
- indeterminate={isPartiallyChecked}
|
|
| 49 |
- onCheckAll={onCheckAll}
|
|
| 31 |
+ checked={check.isAllChecked}
|
|
| 32 |
+ indeterminate={check.isPartiallyChecked}
|
|
| 33 |
+ onCheckAll={check.onCheckAll}
|
|
| 50 | 34 |
/> |
| 51 | 35 |
<tbody> |
| 52 | 36 |
{items.length > 0 ?
|
... | ... | @@ -56,14 +40,14 @@ |
| 56 | 40 |
item={item}
|
| 57 | 41 |
index={index}
|
| 58 | 42 |
searchParams={params}
|
| 59 |
- totalItems={totalItems}
|
|
| 60 |
- currentPage={currentPage}
|
|
| 61 |
- totalPages={totalPages}
|
|
| 62 |
- checked={isChecked(item.bbsId)}
|
|
| 63 |
- onCheck={onCheck}
|
|
| 64 |
- onDetail={onDetail}
|
|
| 65 |
- onArticleList={onArticleList}
|
|
| 66 |
- onPreview={onPreview}
|
|
| 43 |
+ totalItems={pagination.totalItems}
|
|
| 44 |
+ currentPage={pagination.currentPage}
|
|
| 45 |
+ totalPages={pagination.totalPages}
|
|
| 46 |
+ checked={check.isChecked(item.bbsId)}
|
|
| 47 |
+ onCheck={check.onCheck}
|
|
| 48 |
+ onDetail={rowActions.onDetail}
|
|
| 49 |
+ onArticleList={rowActions.onArticleList}
|
|
| 50 |
+ onPreview={rowActions.onPreview}
|
|
| 67 | 51 |
/>)) |
| 68 | 52 |
: (<EmptyRow colSpan={9}/>)
|
| 69 | 53 |
} |
+++ src/admin/feature/board/hook/mutation/useDeleteBatchBoard.ts
... | ... | @@ -0,0 +1,15 @@ |
| 1 | +import {useMutation, useQueryClient} from "@tanstack/react-query"; | |
| 2 | +import {deleteBoardBatch} from "../../api/boardApi.ts"; | |
| 3 | + | |
| 4 | +export const useDeleteBatchBoard = () => { | |
| 5 | + const queryClient = useQueryClient(); | |
| 6 | + | |
| 7 | + return useMutation({ | |
| 8 | + mutationFn: deleteBoardBatch, | |
| 9 | + onSuccess: () => { | |
| 10 | + queryClient.invalidateQueries({ | |
| 11 | + queryKey: ['boardList'], | |
| 12 | + }); | |
| 13 | + } | |
| 14 | + }); | |
| 15 | +}(No newline at end of file) |
--- src/admin/feature/board/hook/page/useBoardArticleListPage.ts
+++ src/admin/feature/board/hook/page/useBoardArticleListPage.ts
... | ... | @@ -1,8 +1,25 @@ |
| 1 | 1 |
import {useMemo, useState} from "react";
|
| 2 | 2 |
import {ADMIN_BBS_MASTER_ROUTE} from "../../../../route/adminRouteMap.ts";
|
| 3 | 3 |
import {useCheckedList} from "../../../../hook/useCheckedList.ts";
|
| 4 |
-import type {BoardArticleSearchParams} from "../../type/board.types.ts";
|
|
| 4 |
+import type {BoardArticleListItem, BoardArticleSearchParams} from "../../type/board.types.ts";
|
|
| 5 | 5 |
import {useBoardArticleList} from "../query/useBoardArticleList.ts";
|
| 6 |
+import type {
|
|
| 7 |
+ CheckableTableModel, |
|
| 8 |
+ HeaderModel, |
|
| 9 |
+ PaginationModel, |
|
| 10 |
+ SearchModel, |
|
| 11 |
+ StatusModel, |
|
| 12 |
+} from "../../../../../type/viewModel.ts"; |
|
| 13 |
+ |
|
| 14 |
+type BoardArticleListPageModel = {
|
|
| 15 |
+ header: HeaderModel; |
|
| 16 |
+ status: StatusModel; |
|
| 17 |
+ search: SearchModel<BoardArticleSearchParams>; |
|
| 18 |
+ table: CheckableTableModel<BoardArticleListItem, BoardArticleSearchParams> & {
|
|
| 19 |
+ typeCode: string; |
|
| 20 |
+ }; |
|
| 21 |
+ pagination: PaginationModel; |
|
| 22 |
+}; |
|
| 6 | 23 |
|
| 7 | 24 |
const initSearchParam: BoardArticleSearchParams = {
|
| 8 | 25 |
pageIndex: 1, |
... | ... | @@ -20,7 +37,7 @@ |
| 20 | 37 |
{value: '2', label: '작성자'},
|
| 21 | 38 |
]; |
| 22 | 39 |
|
| 23 |
-export const useBoardArticleListPage = (bbsId: string) => {
|
|
| 40 |
+export const useBoardArticleListPage = (bbsId: string): BoardArticleListPageModel => {
|
|
| 24 | 41 |
const [searchDraft, setSearchDraft] = useState<BoardArticleSearchParams>(initSearchParam); |
| 25 | 42 |
const searchParams = useMemo( |
| 26 | 43 |
() => ({
|
... | ... | @@ -45,13 +62,11 @@ |
| 45 | 62 |
[list] |
| 46 | 63 |
); |
| 47 | 64 |
const {
|
| 48 |
- checkedIds, |
|
| 49 | 65 |
isAllChecked, |
| 50 | 66 |
isPartiallyChecked, |
| 51 | 67 |
isChecked, |
| 52 | 68 |
handleCheck, |
| 53 | 69 |
handleCheckAll, |
| 54 |
- resetChecked, |
|
| 55 | 70 |
} = useCheckedList(articleIds); |
| 56 | 71 |
const bbsNm = extraData?.boardMaster?.bbsNm ?? ''; |
| 57 | 72 |
const bbsTyCode = extraData?.boardMaster?.bbsTyCode ?? ''; |
... | ... | @@ -79,27 +94,46 @@ |
| 79 | 94 |
}; |
| 80 | 95 |
|
| 81 | 96 |
return {
|
| 82 |
- title, |
|
| 83 |
- breadcrumb, |
|
| 84 |
- searchOptions, |
|
| 85 |
- searchParams, |
|
| 86 |
- list, |
|
| 87 |
- bbsTyCode, |
|
| 88 |
- totalItems, |
|
| 89 |
- currentPage, |
|
| 90 |
- totalPages, |
|
| 91 |
- size, |
|
| 92 |
- isLoading, |
|
| 93 |
- error, |
|
| 94 |
- successMessage, |
|
| 95 |
- checkedIds, |
|
| 96 |
- isAllChecked, |
|
| 97 |
- isPartiallyChecked, |
|
| 98 |
- isChecked, |
|
| 99 |
- handleCheck, |
|
| 100 |
- handleCheckAll, |
|
| 101 |
- resetChecked, |
|
| 102 |
- handleSearchChange, |
|
| 103 |
- handlePageChange, |
|
| 97 |
+ header: {
|
|
| 98 |
+ title, |
|
| 99 |
+ breadcrumb, |
|
| 100 |
+ homeUrl: "#", |
|
| 101 |
+ }, |
|
| 102 |
+ status: {
|
|
| 103 |
+ isLoading, |
|
| 104 |
+ error, |
|
| 105 |
+ successMessage, |
|
| 106 |
+ }, |
|
| 107 |
+ search: {
|
|
| 108 |
+ totalItems, |
|
| 109 |
+ searchParams, |
|
| 110 |
+ onChange: handleSearchChange, |
|
| 111 |
+ searchOptions, |
|
| 112 |
+ }, |
|
| 113 |
+ table: {
|
|
| 114 |
+ typeCode: bbsTyCode, |
|
| 115 |
+ items: list, |
|
| 116 |
+ params: searchParams, |
|
| 117 |
+ onChange: handleSearchChange, |
|
| 118 |
+ check: {
|
|
| 119 |
+ isAllChecked, |
|
| 120 |
+ isPartiallyChecked, |
|
| 121 |
+ isChecked, |
|
| 122 |
+ onCheck: handleCheck, |
|
| 123 |
+ onCheckAll: handleCheckAll, |
|
| 124 |
+ }, |
|
| 125 |
+ pagination: {
|
|
| 126 |
+ totalItems, |
|
| 127 |
+ currentPage, |
|
| 128 |
+ totalPages, |
|
| 129 |
+ }, |
|
| 130 |
+ }, |
|
| 131 |
+ pagination: {
|
|
| 132 |
+ totalItems, |
|
| 133 |
+ totalPages, |
|
| 134 |
+ currentPage, |
|
| 135 |
+ size, |
|
| 136 |
+ onPageChange: handlePageChange, |
|
| 137 |
+ }, |
|
| 104 | 138 |
}; |
| 105 | 139 |
}; |
--- src/admin/feature/board/hook/page/useBoardForm.ts
+++ src/admin/feature/board/hook/page/useBoardFormPage.ts
... | ... | @@ -8,8 +8,19 @@ |
| 8 | 8 |
import {useCreateBoard} from "../mutation/useCreateBoard.ts";
|
| 9 | 9 |
import {useDeleteBoard} from "../mutation/useDeleteBoard.ts";
|
| 10 | 10 |
import {useUpdateBoard} from "../mutation/useUpdateBoard.ts";
|
| 11 |
+import type {FormActionsModel, HeaderModel, StatusModel} from "../../../../../type/viewModel.ts";
|
|
| 11 | 12 |
|
| 12 | 13 |
export type BoardFormMode = 'create' | 'update'; |
| 14 |
+ |
|
| 15 |
+type BoardFormPageModel = {
|
|
| 16 |
+ header: HeaderModel; |
|
| 17 |
+ status: StatusModel; |
|
| 18 |
+ form: {
|
|
| 19 |
+ form: BoardFormItem; |
|
| 20 |
+ onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => void; |
|
| 21 |
+ }; |
|
| 22 |
+ actions: FormActionsModel<BoardFormMode>; |
|
| 23 |
+}; |
|
| 13 | 24 |
|
| 14 | 25 |
const initBoardFormData: BoardFormItem = {
|
| 15 | 26 |
bbsId: '', |
... | ... | @@ -33,7 +44,7 @@ |
| 33 | 44 |
...item, |
| 34 | 45 |
}); |
| 35 | 46 |
|
| 36 |
-export const useBoardForm = (bbsId: string) => {
|
|
| 47 |
+export const useBoardFormPage = (bbsId: string): BoardFormPageModel => {
|
|
| 37 | 48 |
const navigate = useNavigate(); |
| 38 | 49 |
const mode: BoardFormMode = bbsId ? 'update' : 'create'; |
| 39 | 50 |
const [formDraft, setFormDraft] = useState<Partial<BoardFormItem>>({});
|
... | ... | @@ -137,18 +148,27 @@ |
| 137 | 148 |
}; |
| 138 | 149 |
|
| 139 | 150 |
return {
|
| 140 |
- mode, |
|
| 141 |
- title, |
|
| 142 |
- breadcrumb, |
|
| 143 |
- form, |
|
| 144 |
- typeList: data?.typeList, |
|
| 145 |
- isLoading, |
|
| 146 |
- error, |
|
| 147 |
- isPending, |
|
| 148 |
- handleChange, |
|
| 149 |
- handleCreate, |
|
| 150 |
- handleUpdate, |
|
| 151 |
- handleDelete, |
|
| 152 |
- handleList, |
|
| 151 |
+ header: {
|
|
| 152 |
+ title, |
|
| 153 |
+ breadcrumb, |
|
| 154 |
+ homeUrl: "#", |
|
| 155 |
+ }, |
|
| 156 |
+ status: {
|
|
| 157 |
+ isLoading, |
|
| 158 |
+ error, |
|
| 159 |
+ successMessage: '데이터 조회가 완료되었습니다.', |
|
| 160 |
+ }, |
|
| 161 |
+ form: {
|
|
| 162 |
+ form, |
|
| 163 |
+ onChange: handleChange, |
|
| 164 |
+ }, |
|
| 165 |
+ actions: {
|
|
| 166 |
+ mode, |
|
| 167 |
+ disabled: isPending, |
|
| 168 |
+ onCreate: handleCreate, |
|
| 169 |
+ onUpdate: handleUpdate, |
|
| 170 |
+ onDelete: handleDelete, |
|
| 171 |
+ onList: handleList, |
|
| 172 |
+ }, |
|
| 153 | 173 |
}; |
| 154 | 174 |
}; |
--- src/admin/feature/board/hook/page/useBoardListPage.ts
+++ src/admin/feature/board/hook/page/useBoardListPage.ts
... | ... | @@ -1,10 +1,35 @@ |
| 1 | 1 |
import {useMemo, useState} from "react";
|
| 2 |
-import type {BoardSearchParams} from "../../type/board.types.ts";
|
|
| 2 |
+import type {BoardDeleteListItem, BoardListItem, BoardSearchParams} from "../../type/board.types.ts";
|
|
| 3 | 3 |
import {useBoardList} from "../query/useBoardList.ts";
|
| 4 |
-import {ADMIN_BBS_ARTICLE_DETAIL_ROUTE, ADMIN_BBS_ARTICLE_LIST_ROUTE} from "../../../../route/adminRouteMap.ts";
|
|
| 4 |
+import {ADMIN_BBS_ARTICLE_FORM_ROUTE, ADMIN_BBS_ARTICLE_LIST_ROUTE} from "../../../../route/adminRouteMap.ts";
|
|
| 5 | 5 |
import {useNavigate} from "react-router-dom";
|
| 6 | 6 |
import {useCheckedList} from "../../../../hook/useCheckedList.ts";
|
| 7 | 7 |
import {toast} from "react-toastify";
|
| 8 |
+import {useDeleteBatchBoard} from "../mutation/useDeleteBatchBoard.ts";
|
|
| 9 |
+import type {
|
|
| 10 |
+ CheckableTableModel, |
|
| 11 |
+ HeaderModel, |
|
| 12 |
+ ListActionsModel, |
|
| 13 |
+ PaginationModel, |
|
| 14 |
+ RowActionsModel, |
|
| 15 |
+ SearchModel, |
|
| 16 |
+ StatusModel, |
|
| 17 |
+} from "../../../../../type/viewModel.ts"; |
|
| 18 |
+ |
|
| 19 |
+type BoardListRowActions = {
|
|
| 20 |
+ onDetail: (bbsId: string) => void; |
|
| 21 |
+ onArticleList: (bbsId: string) => void; |
|
| 22 |
+ onPreview: (bbsId: string) => void; |
|
| 23 |
+}; |
|
| 24 |
+ |
|
| 25 |
+type BoardListPageModel = {
|
|
| 26 |
+ header: HeaderModel; |
|
| 27 |
+ status: StatusModel; |
|
| 28 |
+ search: SearchModel<BoardSearchParams>; |
|
| 29 |
+ table: CheckableTableModel<BoardListItem, BoardSearchParams> & RowActionsModel<BoardListRowActions>; |
|
| 30 |
+ actions: ListActionsModel; |
|
| 31 |
+ pagination: PaginationModel; |
|
| 32 |
+}; |
|
| 8 | 33 |
|
| 9 | 34 |
const initSearchParam: BoardSearchParams = {
|
| 10 | 35 |
pageIndex: 1, |
... | ... | @@ -26,7 +51,7 @@ |
| 26 | 51 |
{value: '30', label: '30건씩'},
|
| 27 | 52 |
] |
| 28 | 53 |
|
| 29 |
-export const useBoardListPage = () => {
|
|
| 54 |
+export const useBoardListPage = (): BoardListPageModel => {
|
|
| 30 | 55 |
|
| 31 | 56 |
const [searchParams, setSearchParams] = useState(initSearchParam); |
| 32 | 57 |
const {
|
... | ... | @@ -49,8 +74,9 @@ |
| 49 | 74 |
isChecked, |
| 50 | 75 |
handleCheck, |
| 51 | 76 |
handleCheckAll, |
| 52 |
- resetChecked, |
|
| 53 |
- } = useCheckedList(boardIds); |
|
| 77 |
+ } = useCheckedList<string>(boardIds); |
|
| 78 |
+ const {mutateAsync: deleteBoardBatch} = useDeleteBatchBoard();
|
|
| 79 |
+ |
|
| 54 | 80 |
const title = '게시판 관리'; |
| 55 | 81 |
const breadcrumb = [{label: '게시판 관리'}];
|
| 56 | 82 |
const homeUrl = '#'; |
... | ... | @@ -58,7 +84,7 @@ |
| 58 | 84 |
const navigate = useNavigate(); |
| 59 | 85 |
|
| 60 | 86 |
const handleDetail = (bbsId: string) => {
|
| 61 |
- navigate(ADMIN_BBS_ARTICLE_DETAIL_ROUTE + bbsId); |
|
| 87 |
+ navigate(ADMIN_BBS_ARTICLE_FORM_ROUTE + bbsId); |
|
| 62 | 88 |
} |
| 63 | 89 |
const handleArticleList = (bbsId: string) => {
|
| 64 | 90 |
navigate(ADMIN_BBS_ARTICLE_LIST_ROUTE + bbsId); |
... | ... | @@ -72,46 +98,80 @@ |
| 72 | 98 |
pageIndex, |
| 73 | 99 |
})); |
| 74 | 100 |
} |
| 75 |
- const handleDeleteBatch = () => {
|
|
| 101 |
+ const handleDeleteBatch = async () => {
|
|
| 76 | 102 |
if (checkedIds.length === 0) {
|
| 77 | 103 |
toast.warning('미사용 처리할 게시판을 선택해주세요.');
|
| 78 | 104 |
return; |
| 79 |
- } else {
|
|
| 80 |
- toast.info("삭제로직은 아직 미처리");
|
|
| 81 | 105 |
} |
| 106 |
+ const boardList: BoardDeleteListItem[] = checkedIds.map((bbsId) => ({
|
|
| 107 |
+ bbsId |
|
| 108 |
+ })); |
|
| 109 |
+ |
|
| 110 |
+ await toast.promise( |
|
| 111 |
+ deleteBoardBatch(boardList), |
|
| 112 |
+ {
|
|
| 113 |
+ pending: '미사용 처리 중...', |
|
| 114 |
+ success: '미사용 완료', |
|
| 115 |
+ error: '미사용 실패' |
|
| 116 |
+ } |
|
| 117 |
+ ); |
|
| 82 | 118 |
} |
| 119 |
+ |
|
| 120 |
+ |
|
| 83 | 121 |
const handleCreate = () => {
|
| 84 |
- navigate(ADMIN_BBS_ARTICLE_DETAIL_ROUTE); |
|
| 122 |
+ navigate(ADMIN_BBS_ARTICLE_FORM_ROUTE); |
|
| 85 | 123 |
} |
| 86 | 124 |
|
| 87 | 125 |
return {
|
| 88 |
- list, |
|
| 89 |
- totalItems, |
|
| 90 |
- totalPages, |
|
| 91 |
- currentPage, |
|
| 92 |
- size, |
|
| 93 |
- isLoading, |
|
| 94 |
- error, |
|
| 95 |
- title, |
|
| 96 |
- breadcrumb, |
|
| 97 |
- homeUrl, |
|
| 98 |
- searchOptions, |
|
| 99 |
- pageSizeOptions, |
|
| 100 |
- successMessage, |
|
| 101 |
- searchParams, |
|
| 102 |
- setSearchParams, |
|
| 103 |
- handleDetail, |
|
| 104 |
- handleArticleList, |
|
| 105 |
- handlePreview, |
|
| 106 |
- handlePageChange, |
|
| 107 |
- handleDeleteBatch, |
|
| 108 |
- handleCreate, |
|
| 109 |
- checkedIds, |
|
| 110 |
- isAllChecked, |
|
| 111 |
- isPartiallyChecked, |
|
| 112 |
- isChecked, |
|
| 113 |
- handleCheck, |
|
| 114 |
- handleCheckAll, |
|
| 115 |
- resetChecked, |
|
| 126 |
+ header: {
|
|
| 127 |
+ title, |
|
| 128 |
+ breadcrumb, |
|
| 129 |
+ homeUrl, |
|
| 130 |
+ }, |
|
| 131 |
+ status: {
|
|
| 132 |
+ isLoading, |
|
| 133 |
+ error, |
|
| 134 |
+ successMessage, |
|
| 135 |
+ }, |
|
| 136 |
+ search: {
|
|
| 137 |
+ totalItems, |
|
| 138 |
+ searchParams, |
|
| 139 |
+ onChange: setSearchParams, |
|
| 140 |
+ searchOptions, |
|
| 141 |
+ pageSizeOptions, |
|
| 142 |
+ }, |
|
| 143 |
+ table: {
|
|
| 144 |
+ items: list, |
|
| 145 |
+ params: searchParams, |
|
| 146 |
+ onChange: setSearchParams, |
|
| 147 |
+ pagination: {
|
|
| 148 |
+ totalItems, |
|
| 149 |
+ currentPage, |
|
| 150 |
+ totalPages, |
|
| 151 |
+ }, |
|
| 152 |
+ check: {
|
|
| 153 |
+ isAllChecked, |
|
| 154 |
+ isPartiallyChecked, |
|
| 155 |
+ isChecked, |
|
| 156 |
+ onCheck: handleCheck, |
|
| 157 |
+ onCheckAll: handleCheckAll, |
|
| 158 |
+ }, |
|
| 159 |
+ rowActions: {
|
|
| 160 |
+ onDetail: handleDetail, |
|
| 161 |
+ onArticleList: handleArticleList, |
|
| 162 |
+ onPreview: handlePreview, |
|
| 163 |
+ }, |
|
| 164 |
+ }, |
|
| 165 |
+ actions: {
|
|
| 166 |
+ onDelete: handleDeleteBatch, |
|
| 167 |
+ onCreate: handleCreate, |
|
| 168 |
+ }, |
|
| 169 |
+ pagination: {
|
|
| 170 |
+ totalItems, |
|
| 171 |
+ totalPages, |
|
| 172 |
+ currentPage, |
|
| 173 |
+ size, |
|
| 174 |
+ onPageChange: handlePageChange, |
|
| 175 |
+ }, |
|
| 116 | 176 |
} |
| 117 | 177 |
} |
--- src/admin/feature/board/page/BoardArticleListPage.tsx
+++ src/admin/feature/board/page/BoardArticleListPage.tsx
... | ... | @@ -3,78 +3,35 @@ |
| 3 | 3 |
import {ListSearchForm} from "../../../component/ListSearchForm.tsx";
|
| 4 | 4 |
import {Pagination} from "../../../component/pagination/Pagination.tsx";
|
| 5 | 5 |
import {useLoadingToast} from "../../../hook/useLoadingToast.ts";
|
| 6 |
-import {BoartArticleListTable} from "../components/article/BoardArticleListTable.tsx";
|
|
| 6 |
+import {BoardArticleListTable} from "../components/article/BoardArticleListTable.tsx";
|
|
| 7 | 7 |
import {BoardArticleImageListTable} from "../components/article/BoardArticleImageListTable.tsx";
|
| 8 | 8 |
import {useBoardArticleListPage} from "../hook/page/useBoardArticleListPage.ts";
|
| 9 | 9 |
|
| 10 | 10 |
export const BoardArticleListPage = () => {
|
| 11 | 11 |
const {bbsId = ''} = useParams();
|
| 12 | 12 |
const {
|
| 13 |
- title, |
|
| 14 |
- breadcrumb, |
|
| 15 |
- searchOptions, |
|
| 16 |
- searchParams, |
|
| 17 |
- list, |
|
| 18 |
- bbsTyCode, |
|
| 19 |
- totalItems, |
|
| 20 |
- currentPage, |
|
| 21 |
- totalPages, |
|
| 22 |
- size, |
|
| 23 |
- isLoading, |
|
| 24 |
- error, |
|
| 25 |
- successMessage, |
|
| 26 |
- isAllChecked, |
|
| 27 |
- isPartiallyChecked, |
|
| 28 |
- isChecked, |
|
| 29 |
- handleCheck, |
|
| 30 |
- handleCheckAll, |
|
| 31 |
- handleSearchChange, |
|
| 32 |
- handlePageChange, |
|
| 13 |
+ header, |
|
| 14 |
+ status, |
|
| 15 |
+ search, |
|
| 16 |
+ table, |
|
| 17 |
+ pagination, |
|
| 33 | 18 |
} = useBoardArticleListPage(bbsId); |
| 34 | 19 |
|
| 35 |
- useLoadingToast({
|
|
| 36 |
- isLoading, |
|
| 37 |
- error, |
|
| 38 |
- successMessage |
|
| 39 |
- }); |
|
| 40 |
- |
|
| 41 |
- const homeUrl = '#' |
|
| 20 |
+ useLoadingToast(status); |
|
| 42 | 21 |
|
| 43 | 22 |
return ( |
| 44 | 23 |
<> |
| 45 |
- <PageHeader title={title} breadcrumb={breadcrumb} homeUrl={homeUrl}/>
|
|
| 24 |
+ <PageHeader {...header}/>
|
|
| 46 | 25 |
<ListSearchForm |
| 47 |
- totalItems={totalItems}
|
|
| 48 |
- searchParams={searchParams}
|
|
| 49 |
- onChange={handleSearchChange}
|
|
| 50 |
- searchOptions={searchOptions}
|
|
| 51 |
- totalLabel={"게시글"}
|
|
| 26 |
+ {...search}
|
|
| 27 |
+ totalLabel="게시글" |
|
| 52 | 28 |
/> |
| 53 |
- {bbsTyCode === "BBST05" ?
|
|
| 54 |
- <BoardArticleImageListTable |
|
| 55 |
- items={list}
|
|
| 56 |
- /> : |
|
| 57 |
- <BoartArticleListTable |
|
| 58 |
- items={list}
|
|
| 59 |
- params={searchParams}
|
|
| 60 |
- onChange={handleSearchChange}
|
|
| 61 |
- isAllChecked={isAllChecked}
|
|
| 62 |
- isPartiallyChecked={isPartiallyChecked}
|
|
| 63 |
- isChecked={isChecked}
|
|
| 64 |
- onCheck={handleCheck}
|
|
| 65 |
- onCheckAll={handleCheckAll}
|
|
| 66 |
- totalPages={totalPages}
|
|
| 67 |
- currentPage={currentPage}
|
|
| 68 |
- totalItems={totalItems}
|
|
| 69 |
- /> |
|
| 70 |
- } |
|
| 71 |
- <Pagination |
|
| 72 |
- totalItems={totalItems}
|
|
| 73 |
- totalPages={totalPages}
|
|
| 74 |
- currentPage={currentPage}
|
|
| 75 |
- size={size}
|
|
| 76 |
- onPageChange={handlePageChange}
|
|
| 77 |
- /> |
|
| 29 |
+ {table.typeCode === "BBST05" ? (
|
|
| 30 |
+ <BoardArticleImageListTable items={table.items}/>
|
|
| 31 |
+ ) : ( |
|
| 32 |
+ <BoardArticleListTable {...table}/>
|
|
| 33 |
+ )} |
|
| 34 |
+ <Pagination {...pagination}/>
|
|
| 78 | 35 |
</> |
| 79 | 36 |
); |
| 80 | 37 |
} |
--- src/admin/feature/board/page/BoardFormPage.tsx
+++ src/admin/feature/board/page/BoardFormPage.tsx
... | ... | @@ -3,48 +3,24 @@ |
| 3 | 3 |
import {useLoadingToast} from "../../../hook/useLoadingToast.ts";
|
| 4 | 4 |
import {ActionButtonFormGroup} from "../../../component/button/ActionButtonFormGroup.tsx";
|
| 5 | 5 |
import {BoardFormTable} from "../components/master/BoardFormTable.tsx";
|
| 6 |
-import {useBoardForm} from "../hook/page/useBoardForm.ts";
|
|
| 6 |
+import {useBoardFormPage} from "../hook/page/useBoardFormPage.ts";
|
|
| 7 | 7 |
|
| 8 | 8 |
export const BoardFormPage = () => {
|
| 9 | 9 |
const {bbsId = ''} = useParams();
|
| 10 | 10 |
const {
|
| 11 |
- mode, |
|
| 12 |
- title, |
|
| 13 |
- breadcrumb, |
|
| 11 |
+ header, |
|
| 12 |
+ status, |
|
| 14 | 13 |
form, |
| 15 |
- typeList, |
|
| 16 |
- isLoading, |
|
| 17 |
- error, |
|
| 18 |
- isPending, |
|
| 19 |
- handleChange, |
|
| 20 |
- handleCreate, |
|
| 21 |
- handleUpdate, |
|
| 22 |
- handleDelete, |
|
| 23 |
- handleList, |
|
| 24 |
- } = useBoardForm(bbsId); |
|
| 14 |
+ actions, |
|
| 15 |
+ } = useBoardFormPage(bbsId); |
|
| 25 | 16 |
|
| 26 |
- useLoadingToast({
|
|
| 27 |
- isLoading, |
|
| 28 |
- error, |
|
| 29 |
- successMessage: '데이터 조회가 완료되었습니다.' |
|
| 30 |
- }); |
|
| 17 |
+ useLoadingToast(status); |
|
| 31 | 18 |
|
| 32 | 19 |
return ( |
| 33 | 20 |
<> |
| 34 |
- <PageHeader title={title} breadcrumb={breadcrumb} homeUrl="#"/>
|
|
| 35 |
- <BoardFormTable |
|
| 36 |
- form={form}
|
|
| 37 |
- typeList={typeList}
|
|
| 38 |
- onChange={handleChange}
|
|
| 39 |
- /> |
|
| 40 |
- <ActionButtonFormGroup |
|
| 41 |
- mode={mode}
|
|
| 42 |
- disabled={isPending}
|
|
| 43 |
- onDelete={handleDelete}
|
|
| 44 |
- onCreate={handleCreate}
|
|
| 45 |
- onUpdate={handleUpdate}
|
|
| 46 |
- onList={handleList}
|
|
| 47 |
- /> |
|
| 21 |
+ <PageHeader {...header}/>
|
|
| 22 |
+ <BoardFormTable {...form}/>
|
|
| 23 |
+ <ActionButtonFormGroup {...actions}/>
|
|
| 48 | 24 |
</> |
| 49 | 25 |
); |
| 50 | 26 |
}; |
--- src/admin/feature/board/page/BoardListPage.tsx
+++ src/admin/feature/board/page/BoardListPage.tsx
... | ... | @@ -8,85 +8,31 @@ |
| 8 | 8 |
|
| 9 | 9 |
export const BoardListPage = () => {
|
| 10 | 10 |
const {
|
| 11 |
- list, |
|
| 12 |
- totalItems, |
|
| 13 |
- totalPages, |
|
| 14 |
- currentPage, |
|
| 15 |
- size, |
|
| 16 |
- isLoading, |
|
| 17 |
- error, |
|
| 18 |
- title, |
|
| 19 |
- breadcrumb, |
|
| 20 |
- homeUrl, |
|
| 21 |
- searchOptions, |
|
| 22 |
- pageSizeOptions, |
|
| 23 |
- successMessage, |
|
| 24 |
- searchParams, |
|
| 25 |
- setSearchParams, |
|
| 26 |
- handleDetail, |
|
| 27 |
- handleArticleList, |
|
| 28 |
- handlePreview, |
|
| 29 |
- handlePageChange, |
|
| 30 |
- handleDeleteBatch, |
|
| 31 |
- handleCreate, |
|
| 32 |
- isAllChecked, |
|
| 33 |
- isPartiallyChecked, |
|
| 34 |
- isChecked, |
|
| 35 |
- handleCheck, |
|
| 36 |
- handleCheckAll, |
|
| 11 |
+ header, |
|
| 12 |
+ status, |
|
| 13 |
+ search, |
|
| 14 |
+ table, |
|
| 15 |
+ actions, |
|
| 16 |
+ pagination, |
|
| 37 | 17 |
} = useBoardListPage(); |
| 38 | 18 |
|
| 39 |
- useLoadingToast({
|
|
| 40 |
- isLoading, |
|
| 41 |
- error, |
|
| 42 |
- successMessage |
|
| 43 |
- }); |
|
| 19 |
+ useLoadingToast(status); |
|
| 44 | 20 |
|
| 45 | 21 |
return ( |
| 46 | 22 |
<> |
| 47 |
- <PageHeader |
|
| 48 |
- title={title}
|
|
| 49 |
- breadcrumb={breadcrumb}
|
|
| 50 |
- homeUrl={homeUrl}
|
|
| 51 |
- /> |
|
| 23 |
+ <PageHeader {...header}/>
|
|
| 52 | 24 |
<ListSearchForm |
| 53 |
- totalItems={totalItems}
|
|
| 54 |
- searchParams={searchParams}
|
|
| 55 |
- onChange={setSearchParams}
|
|
| 56 |
- searchOptions={searchOptions}
|
|
| 57 |
- pageSizeOptions={pageSizeOptions}
|
|
| 58 |
- totalLabel={"게시판"}
|
|
| 25 |
+ {...search}
|
|
| 26 |
+ totalLabel="게시판" |
|
| 59 | 27 |
/> |
| 60 |
- {isLoading && <p>Loading...</p>}
|
|
| 28 |
+ {status.isLoading && <p>Loading...</p>}
|
|
| 61 | 29 |
|
| 62 |
- <BoardListTable |
|
| 63 |
- items={list}
|
|
| 64 |
- params={searchParams}
|
|
| 65 |
- onChange={setSearchParams}
|
|
| 66 |
- totalItems={totalItems}
|
|
| 67 |
- currentPage={currentPage}
|
|
| 68 |
- totalPages={totalPages}
|
|
| 69 |
- isAllChecked={isAllChecked}
|
|
| 70 |
- isPartiallyChecked={isPartiallyChecked}
|
|
| 71 |
- isChecked={isChecked}
|
|
| 72 |
- onCheck={handleCheck}
|
|
| 73 |
- onCheckAll={handleCheckAll}
|
|
| 74 |
- onDetail={handleDetail}
|
|
| 75 |
- onArticleList={handleArticleList}
|
|
| 76 |
- onPreview={handlePreview}
|
|
| 77 |
- /> |
|
| 30 |
+ <BoardListTable {...table}/>
|
|
| 78 | 31 |
<ActionButtonListGroup |
| 79 |
- onDelete={handleDeleteBatch}
|
|
| 80 |
- onCreate={handleCreate}
|
|
| 81 |
- deleteLabel={"미사용"}
|
|
| 32 |
+ {...actions}
|
|
| 33 |
+ deleteLabel="미사용" |
|
| 82 | 34 |
/> |
| 83 |
- <Pagination |
|
| 84 |
- totalItems={totalItems}
|
|
| 85 |
- totalPages={totalPages}
|
|
| 86 |
- currentPage={currentPage}
|
|
| 87 |
- size={size}
|
|
| 88 |
- onPageChange={handlePageChange}
|
|
| 89 |
- /> |
|
| 35 |
+ <Pagination {...pagination}/>
|
|
| 90 | 36 |
</> |
| 91 | 37 |
) |
| 92 | 38 |
}; |
--- src/admin/feature/board/type/board.types.ts
+++ src/admin/feature/board/type/board.types.ts
... | ... | @@ -9,6 +9,10 @@ |
| 9 | 9 |
bbsId: string; |
| 10 | 10 |
} |
| 11 | 11 |
|
| 12 |
+export interface BoardDeleteListItem {
|
|
| 13 |
+ bbsId: string; |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 12 | 16 |
export interface BoardListItem {
|
| 13 | 17 |
bbsId: string |
| 14 | 18 |
bbsNm: string |
+++ src/admin/feature/role/api/roleApi.ts
... | ... | @@ -0,0 +1,49 @@ |
| 1 | +import {apiClient} from "../../../../api/apiClient.ts"; | |
| 2 | +import type { | |
| 3 | + AuthorRoleListItem, | |
| 4 | + AuthorGroupSearchParams, AuthorListItem, | |
| 5 | + AuthorRoleMenuSearchParams, | |
| 6 | + AuthorRoleSearchParams, | |
| 7 | + AuthorSearchParams, RoleSearchParams, UpdateAuthorRoleParams, RoleDetailResponse | |
| 8 | +} from "../type/role.types.ts"; | |
| 9 | +import type {PageResponse} from "../../../../type/pageResponse.ts"; | |
| 10 | +// 권한별롤관리 | |
| 11 | +export async function fetchAuthorList(params: AuthorSearchParams) { | |
| 12 | + return apiClient.get<PageResponse<AuthorListItem>>(`/sec/ram/list.do`, params); | |
| 13 | +} | |
| 14 | + | |
| 15 | +export async function fetchAuthorDetail(authorCode: string) { | |
| 16 | + return apiClient.get(`/sec/ram/detail.do?authorCode=${authorCode}`); | |
| 17 | +} | |
| 18 | + | |
| 19 | +// 권한롤관리 | |
| 20 | +export async function fetchAuthorRoleList(params: AuthorRoleSearchParams) { | |
| 21 | + return apiClient.get<PageResponse<AuthorRoleListItem>>(`/sec/ram/authorList.do`, params); | |
| 22 | +} | |
| 23 | + | |
| 24 | +// 권한별메뉴관리 | |
| 25 | +export async function fetchAuthorRoleMenuList(params: AuthorRoleMenuSearchParams) { | |
| 26 | + return apiClient.get(`/sym/mnu/mcm/list.do`, params); | |
| 27 | +} | |
| 28 | +export async function fetchAuthorRoleMenuDetail(authorCode: string) { | |
| 29 | + return apiClient.get(`/sym/mnu/mcm/detail?authorCode=${authorCode}`); | |
| 30 | +} | |
| 31 | + | |
| 32 | +// 관리자별권한관리 | |
| 33 | +export async function fetchAuthorGroupList(params: AuthorGroupSearchParams) { | |
| 34 | + return apiClient.get(`/sec/rgm/list.do`, params); | |
| 35 | +} | |
| 36 | + | |
| 37 | +// 롤관리 | |
| 38 | +export async function fetchRoleList(params: RoleSearchParams) { | |
| 39 | + return apiClient.get(`/sec/rmt/list.do`, params); | |
| 40 | +} | |
| 41 | + | |
| 42 | +export async function fetchRoleDetail(roleCode: string) { | |
| 43 | + return apiClient.get<RoleDetailResponse>(`/sec/rmt/detail.do?roleCode=${roleCode}`); | |
| 44 | +} | |
| 45 | + | |
| 46 | +export async function updateAuthorRole(params: UpdateAuthorRoleParams) { | |
| 47 | + return apiClient.post(`/sec/ram/updateAuthorRole.do?authorCode=${params.authorCode}&roleCode=${params.roleCode}®Yn=${params.regYn}`); | |
| 48 | +} | |
| 49 | + |
+++ src/admin/feature/role/hook/mutation/useUpdateAuthorRole.ts
... | ... | @@ -0,0 +1,15 @@ |
| 1 | +import {useMutation, useQueryClient} from "@tanstack/react-query"; | |
| 2 | +import {updateAuthorRole} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export const useUpdateAuthorRole = () => { | |
| 5 | + const queryClient = useQueryClient(); | |
| 6 | + | |
| 7 | + return useMutation({ | |
| 8 | + mutationFn: updateAuthorRole, | |
| 9 | + onSuccess: () => { | |
| 10 | + queryClient.invalidateQueries({ | |
| 11 | + queryKey: ['authorRoleList'], | |
| 12 | + }) | |
| 13 | + } | |
| 14 | + }) | |
| 15 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/page/useAuthorGroupListPage.ts
... | ... | @@ -0,0 +1,0 @@ |
+++ src/admin/feature/role/hook/page/useAuthorListPage.ts
... | ... | @@ -0,0 +1,153 @@ |
| 1 | +import {useNavigate} from "react-router-dom"; | |
| 2 | +import {useMemo, useState} from "react"; | |
| 3 | +import {useAuthorList} from "../query/useAuthorList.ts"; | |
| 4 | +import {useCheckedList} from "../../../../hook/useCheckedList.ts"; | |
| 5 | +import {ADMIN_AUTHOR_DETAIL_ROUTE, ADMIN_AUTHOR_ROLE_LIST_ROUTE} from "../../../../route/adminRouteMap.ts"; | |
| 6 | +import type {AuthorListItem, AuthorSearchParams} from "../../type/role.types.ts"; | |
| 7 | +import type { | |
| 8 | + CheckableTableModel, | |
| 9 | + HeaderModel, | |
| 10 | + ListActionsModel, | |
| 11 | + PaginationModel, | |
| 12 | + RowActionsModel, | |
| 13 | + SearchModel, | |
| 14 | + StatusModel, | |
| 15 | +} from "../../../../../type/viewModel.ts"; | |
| 16 | + | |
| 17 | +type AuthorListRowActions = { | |
| 18 | + onDetail: (authorCode: string) => void; | |
| 19 | + onRoleMove: (authorCode: string, authorNm: string) => void; | |
| 20 | +}; | |
| 21 | + | |
| 22 | +type AuthorListPageModel = { | |
| 23 | + header: HeaderModel; | |
| 24 | + status: StatusModel; | |
| 25 | + search: SearchModel<AuthorSearchParams>; | |
| 26 | + table: CheckableTableModel<AuthorListItem, AuthorSearchParams> & RowActionsModel<AuthorListRowActions>; | |
| 27 | + actions: ListActionsModel; | |
| 28 | + pagination: PaginationModel; | |
| 29 | +}; | |
| 30 | + | |
| 31 | +const initSearchParam: AuthorSearchParams = { | |
| 32 | + pageIndex: 1, | |
| 33 | + pageUnit: 10, | |
| 34 | + searchCnd: "0", | |
| 35 | + searchKeyword: "", | |
| 36 | + searchSortCnd: "", | |
| 37 | + searchSortOrd: "" | |
| 38 | +} | |
| 39 | + | |
| 40 | +const searchOptions = [ | |
| 41 | + {value: '0', label: '아이디'}, | |
| 42 | + {value: '1', label: '관리자명'}, | |
| 43 | +] | |
| 44 | + | |
| 45 | +const pageSizeOptions = [ | |
| 46 | + {value: '10', label: '10줄'}, | |
| 47 | + {value: '20', label: '20줄'}, | |
| 48 | + {value: '30', label: '30줄'}, | |
| 49 | +] | |
| 50 | + | |
| 51 | +export const useAuthorListPage = (): AuthorListPageModel => { | |
| 52 | + const [searchParams, setSearchParams] = useState(initSearchParam); | |
| 53 | + const { | |
| 54 | + list, | |
| 55 | + totalItems, | |
| 56 | + totalPages, | |
| 57 | + currentPage, | |
| 58 | + size, | |
| 59 | + isLoading, | |
| 60 | + error | |
| 61 | + } = useAuthorList(searchParams); | |
| 62 | + const authorIds = useMemo(() => list.map((item) => item.authorNm), [list]); | |
| 63 | + | |
| 64 | + const { | |
| 65 | + isAllChecked, | |
| 66 | + isPartiallyChecked, | |
| 67 | + isChecked, | |
| 68 | + handleCheck, | |
| 69 | + handleCheckAll, | |
| 70 | + } = useCheckedList(authorIds); | |
| 71 | + | |
| 72 | + | |
| 73 | + const navigate = useNavigate(); | |
| 74 | + const title = '권한별롤관리'; | |
| 75 | + const breadcrumb = [ | |
| 76 | + {label: '권한관리'}, | |
| 77 | + {label: title} | |
| 78 | + ] | |
| 79 | + const homeUrl = '#'; | |
| 80 | + | |
| 81 | + const handleDetail = (authorCode: string) => { | |
| 82 | + navigate(`${ADMIN_AUTHOR_DETAIL_ROUTE}?authorCode=${authorCode}`); | |
| 83 | + } | |
| 84 | + | |
| 85 | + const handleRoleMove = (authorCode: string, authorNm: string) => { | |
| 86 | + navigate(`${ADMIN_AUTHOR_ROLE_LIST_ROUTE}?authorCode=${authorCode}&authorNm=${authorNm}`); | |
| 87 | + } | |
| 88 | + | |
| 89 | + const handlePageChange = (pageIndex: number) => { | |
| 90 | + setSearchParams((prev) => ({ | |
| 91 | + ...prev, | |
| 92 | + pageIndex, | |
| 93 | + })); | |
| 94 | + }; | |
| 95 | + | |
| 96 | + const handleDelete = () => { | |
| 97 | + }; | |
| 98 | + | |
| 99 | + const handleCreate = () => { | |
| 100 | + }; | |
| 101 | + | |
| 102 | + return { | |
| 103 | + header: { | |
| 104 | + title, | |
| 105 | + breadcrumb, | |
| 106 | + homeUrl, | |
| 107 | + }, | |
| 108 | + status: { | |
| 109 | + isLoading, | |
| 110 | + error, | |
| 111 | + successMessage: '데이터 조회가 완료되었습니다.', | |
| 112 | + }, | |
| 113 | + search: { | |
| 114 | + totalItems, | |
| 115 | + searchParams, | |
| 116 | + onChange: setSearchParams, | |
| 117 | + searchOptions, | |
| 118 | + pageSizeOptions, | |
| 119 | + }, | |
| 120 | + table: { | |
| 121 | + items: list, | |
| 122 | + params: searchParams, | |
| 123 | + onChange: setSearchParams, | |
| 124 | + pagination: { | |
| 125 | + totalItems, | |
| 126 | + currentPage, | |
| 127 | + totalPages, | |
| 128 | + }, | |
| 129 | + check: { | |
| 130 | + isAllChecked, | |
| 131 | + isPartiallyChecked, | |
| 132 | + isChecked, | |
| 133 | + onCheck: handleCheck, | |
| 134 | + onCheckAll: handleCheckAll, | |
| 135 | + }, | |
| 136 | + rowActions: { | |
| 137 | + onDetail: handleDetail, | |
| 138 | + onRoleMove: handleRoleMove, | |
| 139 | + }, | |
| 140 | + }, | |
| 141 | + actions: { | |
| 142 | + onDelete: handleDelete, | |
| 143 | + onCreate: handleCreate, | |
| 144 | + }, | |
| 145 | + pagination: { | |
| 146 | + totalItems, | |
| 147 | + totalPages, | |
| 148 | + currentPage, | |
| 149 | + size, | |
| 150 | + onPageChange: handlePageChange, | |
| 151 | + }, | |
| 152 | + }; | |
| 153 | +} |
+++ src/admin/feature/role/hook/page/useAuthorRoleListPage.ts
... | ... | @@ -0,0 +1,124 @@ |
| 1 | +import {useAuthorRoleList} from "../query/useAuthorRoleList.ts"; | |
| 2 | +import type {AuthorRoleListItem, AuthorRoleSearchParams} from "../../type/role.types.ts"; | |
| 3 | +import {useState} from "react"; | |
| 4 | +import {ADMIN_AUTHOR_LIST_ROUTE} from "../../../../route/adminRouteMap.ts"; | |
| 5 | +import {useUpdateAuthorRole} from "../mutation/useUpdateAuthorRole.ts"; | |
| 6 | +import {toast} from "react-toastify"; | |
| 7 | +import type { | |
| 8 | + HeaderModel, | |
| 9 | + ListTableModel, | |
| 10 | + PaginationModel, | |
| 11 | + RowActionsModel, | |
| 12 | + SearchModel, | |
| 13 | + StatusModel | |
| 14 | +} from "../../../../../type/viewModel.ts"; | |
| 15 | + | |
| 16 | +type AuthorRoleListRowActions = { | |
| 17 | + onDetail: (authorCode: string) => void; | |
| 18 | + onSelectChange: (roleCode: string, value: string) => void; | |
| 19 | +}; | |
| 20 | + | |
| 21 | +type AuthorRoleListPageModel = { | |
| 22 | + header: HeaderModel; | |
| 23 | + status: StatusModel; | |
| 24 | + search: SearchModel<AuthorRoleSearchParams>; | |
| 25 | + table: ListTableModel<AuthorRoleListItem, AuthorRoleSearchParams> & RowActionsModel<AuthorRoleListRowActions>; | |
| 26 | + pagination: PaginationModel; | |
| 27 | +}; | |
| 28 | + | |
| 29 | +const initSearchParam: AuthorRoleSearchParams = { | |
| 30 | + pageIndex: 1, | |
| 31 | + pageUnit: 10, | |
| 32 | + searchCnd: "0", | |
| 33 | + searchKeyword: "", | |
| 34 | + searchSortCnd: "", | |
| 35 | + searchSortOrd: "", | |
| 36 | + authorCode: "", | |
| 37 | +}; | |
| 38 | + | |
| 39 | +const pageSizeOptions = [ | |
| 40 | + {value: '10', label: '10줄'}, | |
| 41 | + {value: '20', label: '20줄'}, | |
| 42 | + {value: '30', label: '30줄'}, | |
| 43 | +] | |
| 44 | + | |
| 45 | +export function useAuthorRoleListPage(authorCode: string): AuthorRoleListPageModel { | |
| 46 | + const [searchParams, setSearchParams] = useState({...initSearchParam, authorCode}); | |
| 47 | + const { | |
| 48 | + currentPage, | |
| 49 | + totalPages, | |
| 50 | + list, | |
| 51 | + totalItems, | |
| 52 | + isLoading, | |
| 53 | + size, | |
| 54 | + error | |
| 55 | + } = useAuthorRoleList(searchParams); | |
| 56 | + const {mutateAsync: updateAuthorRole} = useUpdateAuthorRole(); | |
| 57 | + | |
| 58 | + const title = "권한롤관리"; | |
| 59 | + const breadcrumb = [ | |
| 60 | + {label: '권한관리'}, | |
| 61 | + {label: '권한별롤관리', url: ADMIN_AUTHOR_LIST_ROUTE}, | |
| 62 | + {label: '권한롤관리'}, | |
| 63 | + ] | |
| 64 | + | |
| 65 | + const handleUpdate = async (roleCode: string, value: string) => { | |
| 66 | + await toast.promise( | |
| 67 | + updateAuthorRole({roleCode: roleCode, authorCode: authorCode, regYn: value}), | |
| 68 | + { | |
| 69 | + pending: '변경 처리 중...', | |
| 70 | + success: '변경 완료', | |
| 71 | + error: '변경 실패' | |
| 72 | + } | |
| 73 | + ) | |
| 74 | + | |
| 75 | + } | |
| 76 | + const handleDetail = (roleCode: string) => { | |
| 77 | + console.log(roleCode) | |
| 78 | + } | |
| 79 | + | |
| 80 | + const handlePageChange = (pageIndex: number) => { | |
| 81 | + setSearchParams((prev) => ({ | |
| 82 | + ...prev, | |
| 83 | + pageIndex, | |
| 84 | + })); | |
| 85 | + }; | |
| 86 | + | |
| 87 | + return { | |
| 88 | + header: { | |
| 89 | + title, | |
| 90 | + breadcrumb, | |
| 91 | + }, | |
| 92 | + status: { | |
| 93 | + isLoading, | |
| 94 | + error, | |
| 95 | + }, | |
| 96 | + search: { | |
| 97 | + totalItems, | |
| 98 | + searchParams, | |
| 99 | + onChange: setSearchParams, | |
| 100 | + pageSizeOptions, | |
| 101 | + }, | |
| 102 | + table: { | |
| 103 | + items: list, | |
| 104 | + params: searchParams, | |
| 105 | + onChange: setSearchParams, | |
| 106 | + pagination: { | |
| 107 | + totalItems, | |
| 108 | + currentPage, | |
| 109 | + totalPages, | |
| 110 | + }, | |
| 111 | + rowActions: { | |
| 112 | + onDetail: handleDetail, | |
| 113 | + onSelectChange: handleUpdate, | |
| 114 | + }, | |
| 115 | + }, | |
| 116 | + pagination: { | |
| 117 | + totalItems, | |
| 118 | + totalPages, | |
| 119 | + currentPage, | |
| 120 | + size, | |
| 121 | + onPageChange: handlePageChange, | |
| 122 | + }, | |
| 123 | + }; | |
| 124 | +} |
+++ src/admin/feature/role/hook/page/useRoleFormPage.ts
... | ... | @@ -0,0 +1,8 @@ |
| 1 | +import {useRoleDetail} from "../query/useRoleDetail.ts"; | |
| 2 | + | |
| 3 | +export const useRoleFormPage = (roleCode: string) => { | |
| 4 | + const mode = roleCode ? "update" : "create"; | |
| 5 | + const {data, isLoading, error} = useRoleDetail(roleCode); | |
| 6 | + | |
| 7 | + return {mode, data, isLoading, error} | |
| 8 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useAuthorDetail.ts
... | ... | @@ -0,0 +1,10 @@ |
| 1 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 2 | +import {fetchAuthorDetail} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export function useAuthorDetail(authorCode: string) { | |
| 5 | + return useQuery({ | |
| 6 | + queryKey: ['authorDetail'], | |
| 7 | + queryFn: () => fetchAuthorDetail(authorCode), | |
| 8 | + placeholderData: keepPreviousData | |
| 9 | + }) | |
| 10 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useAuthorGroupList.ts
... | ... | @@ -0,0 +1,11 @@ |
| 1 | +import type {AuthorGroupSearchParams} from "../../type/role.types.ts"; | |
| 2 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 3 | +import {fetchAuthorGroupList} from "../../api/roleApi.ts"; | |
| 4 | + | |
| 5 | +export function useAuthorGroupList(searchParams: AuthorGroupSearchParams) { | |
| 6 | + return useQuery({ | |
| 7 | + queryKey: ['authorGroupList', searchParams], | |
| 8 | + queryFn: () => fetchAuthorGroupList(searchParams), | |
| 9 | + placeholderData: keepPreviousData | |
| 10 | + }); | |
| 11 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useAuthorList.ts
... | ... | @@ -0,0 +1,14 @@ |
| 1 | +import type {AuthorSearchParams} from "../../type/role.types.ts"; | |
| 2 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 3 | +import {fetchAuthorList} from "../../api/roleApi.ts"; | |
| 4 | +import {createPageQueryResult} from "../../../../../type/pageResponse.ts"; | |
| 5 | + | |
| 6 | +export function useAuthorList(searchParams: AuthorSearchParams) { | |
| 7 | + const query = useQuery({ | |
| 8 | + queryKey: ['authorList', searchParams], | |
| 9 | + queryFn: () => fetchAuthorList(searchParams), | |
| 10 | + placeholderData: keepPreviousData | |
| 11 | + }); | |
| 12 | + | |
| 13 | + return createPageQueryResult(query); | |
| 14 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useAuthorRoleList.ts
... | ... | @@ -0,0 +1,16 @@ |
| 1 | +import type {AuthorRoleSearchParams} from "../../type/role.types.ts"; | |
| 2 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 3 | +import {fetchAuthorRoleList} from "../../api/roleApi.ts"; | |
| 4 | +import {createPageQueryResult} from "../../../../../type/pageResponse.ts"; | |
| 5 | + | |
| 6 | +export function useAuthorRoleList(searchParams: AuthorRoleSearchParams) { | |
| 7 | + const query = useQuery({ | |
| 8 | + queryKey: ['authorRoleList', searchParams], | |
| 9 | + queryFn: () => fetchAuthorRoleList(searchParams), | |
| 10 | + placeholderData: keepPreviousData | |
| 11 | + }); | |
| 12 | + | |
| 13 | + console.log(query); | |
| 14 | + | |
| 15 | + return createPageQueryResult(query); | |
| 16 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useAuthorRoleMenuDetail.ts
... | ... | @@ -0,0 +1,10 @@ |
| 1 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 2 | +import {fetchAuthorRoleMenuDetail} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export function useAuthorRoleMenuDetail(authorCode: string) { | |
| 5 | + return useQuery({ | |
| 6 | + queryKey : ['authorRoleMenuDetail', authorCode] , | |
| 7 | + queryFn: () => fetchAuthorRoleMenuDetail(authorCode), | |
| 8 | + placeholderData: keepPreviousData | |
| 9 | + }) | |
| 10 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useAuthorRoleMenuList.ts
... | ... | @@ -0,0 +1,11 @@ |
| 1 | +import type {AuthorRoleSearchParams} from "../../type/role.types.ts"; | |
| 2 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 3 | +import {fetchAuthorRoleMenuList} from "../../api/roleApi.ts"; | |
| 4 | + | |
| 5 | +export function useAuthorRoleMenuList(searchParams: AuthorRoleSearchParams) { | |
| 6 | + return useQuery({ | |
| 7 | + queryKey: ['authorRoleMenuList', searchParams], | |
| 8 | + queryFn: () => fetchAuthorRoleMenuList(searchParams), | |
| 9 | + placeholderData: keepPreviousData | |
| 10 | + }); | |
| 11 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useRoleDetail.ts
... | ... | @@ -0,0 +1,11 @@ |
| 1 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 2 | +import {fetchRoleDetail} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export function useRoleDetail(roleCode: string) { | |
| 5 | + return useQuery({ | |
| 6 | + queryKey: ['roleDetail', roleCode], | |
| 7 | + queryFn: () => fetchRoleDetail(roleCode), | |
| 8 | + placeholderData: keepPreviousData, | |
| 9 | + enabled: !!roleCode, | |
| 10 | + }) | |
| 11 | +}(No newline at end of file) |
+++ src/admin/feature/role/hook/query/useRoleList.ts
... | ... | @@ -0,0 +1,11 @@ |
| 1 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 2 | +import type {RoleSearchParams} from "../../type/role.types.ts"; | |
| 3 | +import {fetchRoleList} from "../../api/roleApi.ts"; | |
| 4 | + | |
| 5 | +export function useRoleList(searchParams: RoleSearchParams) { | |
| 6 | + return useQuery({ | |
| 7 | + queryKey: ['roleList', searchParams], | |
| 8 | + queryFn: () => fetchRoleList(searchParams), | |
| 9 | + placeholderData: keepPreviousData | |
| 10 | + }); | |
| 11 | +}(No newline at end of file) |
+++ src/admin/feature/role/page/AuthorListPage.tsx
... | ... | @@ -0,0 +1,38 @@ |
| 1 | +import {useAuthorListPage} from "../hook/page/useAuthorListPage.ts"; | |
| 2 | +import {PageHeader} from "../../../component/PageHeader.tsx"; | |
| 3 | +import {ListSearchForm} from "../../../component/ListSearchForm.tsx"; | |
| 4 | +import {ActionButtonListGroup} from "../../../component/button/ActionButtonListGroup.tsx"; | |
| 5 | +import {Pagination} from "../../../component/pagination/Pagination.tsx"; | |
| 6 | +import {AuthorListTable} from "../components/author/AuthorListTable.tsx"; | |
| 7 | +import {useLoadingToast} from "../../../hook/useLoadingToast.ts"; | |
| 8 | + | |
| 9 | +export const AuthorListPage = () => { | |
| 10 | + const { | |
| 11 | + header, | |
| 12 | + status, | |
| 13 | + search, | |
| 14 | + table, | |
| 15 | + actions, | |
| 16 | + pagination, | |
| 17 | + } = useAuthorListPage(); | |
| 18 | + | |
| 19 | + useLoadingToast(status); | |
| 20 | + | |
| 21 | + return ( | |
| 22 | + <> | |
| 23 | + <PageHeader {...header}/> | |
| 24 | + <ListSearchForm | |
| 25 | + {...search} | |
| 26 | + totalLabel="총 게시물" | |
| 27 | + /> | |
| 28 | + | |
| 29 | + <AuthorListTable {...table}/> | |
| 30 | + | |
| 31 | + <ActionButtonListGroup | |
| 32 | + {...actions} | |
| 33 | + deleteLabel="삭제" | |
| 34 | + /> | |
| 35 | + <Pagination {...pagination}/> | |
| 36 | + </> | |
| 37 | + ) | |
| 38 | +} |
+++ src/admin/feature/role/page/AuthorRoleFormPage.tsx
... | ... | @@ -0,0 +1,7 @@ |
| 1 | +export const AuthorRoleFormPage = () => { | |
| 2 | + return ( | |
| 3 | + | |
| 4 | + <> | |
| 5 | + </> | |
| 6 | + ); | |
| 7 | +}(No newline at end of file) |
+++ src/admin/feature/role/page/AuthorRoleListPage.tsx
... | ... | @@ -0,0 +1,34 @@ |
| 1 | +import {useAuthorRoleListPage} from "../hook/page/useAuthorRoleListPage.ts"; | |
| 2 | +import {useSearchParams} from "react-router-dom"; | |
| 3 | +import {PageHeader} from "../../../component/PageHeader.tsx"; | |
| 4 | +import {ListSearchForm} from "../../../component/ListSearchForm.tsx"; | |
| 5 | +import {Pagination} from "../../../component/pagination/Pagination.tsx"; | |
| 6 | +import {AuthorRoleListTable} from "../components/author/role/AuthorRoleListTable.tsx"; | |
| 7 | +import {useLoadingToast} from "../../../hook/useLoadingToast.ts"; | |
| 8 | + | |
| 9 | +export const AuthorRoleListPage = () => { | |
| 10 | + const [urlParams] = useSearchParams(); | |
| 11 | + const authorCode = urlParams.get("authorCode") || ""; | |
| 12 | + const authorNm = urlParams.get("authorNm") || ""; | |
| 13 | + const { | |
| 14 | + header, | |
| 15 | + status, | |
| 16 | + search, | |
| 17 | + table, | |
| 18 | + pagination, | |
| 19 | + } = useAuthorRoleListPage(authorCode); | |
| 20 | + | |
| 21 | + useLoadingToast(status); | |
| 22 | + | |
| 23 | + return ( | |
| 24 | + <> | |
| 25 | + <PageHeader {...header}/> | |
| 26 | + <ListSearchForm | |
| 27 | + {...search} | |
| 28 | + totalLabel={`(${authorCode}:${authorNm}) 총 건수`} | |
| 29 | + /> | |
| 30 | + <AuthorRoleListTable {...table}/> | |
| 31 | + <Pagination {...pagination}/> | |
| 32 | + </> | |
| 33 | + ); | |
| 34 | +} |
+++ src/admin/feature/role/type/role.types.ts
... | ... | @@ -0,0 +1,51 @@ |
| 1 | +import type {SearchParams} from "../../../../type/searchParams.ts"; | |
| 2 | + | |
| 3 | +export interface AuthorSearchParams extends SearchParams { | |
| 4 | +} | |
| 5 | + | |
| 6 | +export interface AuthorRoleSearchParams extends SearchParams { | |
| 7 | + authorCode: string; | |
| 8 | +} | |
| 9 | + | |
| 10 | +export interface AuthorRoleMenuSearchParams extends SearchParams { | |
| 11 | +} | |
| 12 | + | |
| 13 | +export interface AuthorGroupSearchParams extends SearchParams { | |
| 14 | +} | |
| 15 | + | |
| 16 | +export interface RoleSearchParams extends SearchParams { | |
| 17 | +} | |
| 18 | + | |
| 19 | +export interface UpdateAuthorRoleParams { | |
| 20 | + authorCode: string, | |
| 21 | + roleCode: string, | |
| 22 | + regYn: string | |
| 23 | +} | |
| 24 | + | |
| 25 | +export interface RoleDetailResponse { | |
| 26 | + roleCode: string, | |
| 27 | + roleNm: string, | |
| 28 | + rolePtn: string, | |
| 29 | + roleDc: string, | |
| 30 | + roleSort: string, | |
| 31 | + roleCreatDe: string, | |
| 32 | +} | |
| 33 | + | |
| 34 | + | |
| 35 | +export interface AuthorListItem { | |
| 36 | + authorCode: string; | |
| 37 | + authorNm: string; | |
| 38 | + authorDc: string; | |
| 39 | + authorCreatDe: string; | |
| 40 | +} | |
| 41 | + | |
| 42 | +export interface AuthorRoleListItem { | |
| 43 | + regYn: string; | |
| 44 | + roleNm: string; | |
| 45 | + roleCode: string; | |
| 46 | + rolePtn: string; | |
| 47 | + roleSort: string; | |
| 48 | + roleDc: string; | |
| 49 | + creatDt: string; | |
| 50 | + authorCode: string | |
| 51 | +}(No newline at end of file) |
--- src/admin/hook/useLoadingToast.ts
+++ src/admin/hook/useLoadingToast.ts
... | ... | @@ -1,11 +1,9 @@ |
| 1 | 1 |
import {useEffect, useRef} from "react";
|
| 2 | 2 |
import {type Id, toast} from "react-toastify";
|
| 3 |
+import type {StatusModel} from "../../type/viewModel.ts";
|
|
| 3 | 4 |
|
| 4 |
-type UseLoadingToastProps = {
|
|
| 5 |
- isLoading: boolean; |
|
| 6 |
- error?: Error | null; |
|
| 5 |
+type UseLoadingToastProps = StatusModel & {
|
|
| 7 | 6 |
loadingMessage?: string; |
| 8 |
- successMessage?: string; |
|
| 9 | 7 |
} |
| 10 | 8 |
|
| 11 | 9 |
export const useLoadingToast = ({
|
... | ... | @@ -26,10 +24,10 @@ |
| 26 | 24 |
toastId.current = null; |
| 27 | 25 |
|
| 28 | 26 |
if (error) {
|
| 29 |
- toast.error(error.message); |
|
| 27 |
+ toast.error(error instanceof Error ? error.message : String(error)); |
|
| 30 | 28 |
} else {
|
| 31 | 29 |
toast.success(successMessage); |
| 32 | 30 |
} |
| 33 | 31 |
} |
| 34 | 32 |
}, [isLoading, error, loadingMessage, successMessage]); |
| 35 |
-}(No newline at end of file) |
|
| 33 |
+} |
--- src/admin/route/AdminRoute.tsx
+++ src/admin/route/AdminRoute.tsx
... | ... | @@ -1,8 +1,16 @@ |
| 1 | 1 |
import {Navigate, Route, Routes} from "react-router-dom";
|
| 2 | 2 |
import {BoardListPage} from "../feature/board/page/BoardListPage.tsx";
|
| 3 |
-import {ADMIN_BBS_MASTER_ROUTE} from "./adminRouteMap.ts";
|
|
| 3 |
+import {
|
|
| 4 |
+ ADMIN_AUTHOR_DETAIL_ROUTE, |
|
| 5 |
+ ADMIN_AUTHOR_LIST_ROUTE, |
|
| 6 |
+ ADMIN_AUTHOR_ROLE_LIST_ROUTE, ADMIN_BBS_ARTICLE_FORM_ROUTE, |
|
| 7 |
+ ADMIN_BBS_MASTER_ROUTE, ADMIN_ROLE_FORM_ROUTE |
|
| 8 |
+} from "./adminRouteMap.ts"; |
|
| 4 | 9 |
import {BoardArticleListPage} from "../feature/board/page/BoardArticleListPage.tsx";
|
| 5 | 10 |
import {BoardFormPage} from "../feature/board/page/BoardFormPage.tsx";
|
| 11 |
+import {AuthorListPage} from "../feature/role/page/AuthorListPage.tsx";
|
|
| 12 |
+import {AuthorRoleListPage} from "../feature/role/page/AuthorRoleListPage.tsx";
|
|
| 13 |
+import {AuthorRoleFormPage} from "../feature/role/page/AuthorRoleFormPage.tsx";
|
|
| 6 | 14 |
|
| 7 | 15 |
const ReadyPage = () => {
|
| 8 | 16 |
return <div>Preparing menu.</div>; |
... | ... | @@ -14,8 +22,13 @@ |
| 14 | 22 |
<Route path="/" element={<Navigate to={ADMIN_BBS_MASTER_ROUTE} replace/>}/>
|
| 15 | 23 |
<Route path={ADMIN_BBS_MASTER_ROUTE} element={<BoardListPage/>}/>
|
| 16 | 24 |
<Route path={`/admin/cop/bbs/article/:bbsId`} element={<BoardArticleListPage/>}/>
|
| 17 |
- <Route path={`/admin/cop/bbs/detail/:bbsId`} element={<BoardFormPage/>}/>
|
|
| 18 |
- <Route path={`/admin/cop/bbs/detail`} element={<BoardFormPage/>}/>
|
|
| 25 |
+ <Route path={`${ADMIN_BBS_ARTICLE_FORM_ROUTE}:bbsId`} element={<BoardFormPage/>}/>
|
|
| 26 |
+ <Route path={ADMIN_BBS_ARTICLE_FORM_ROUTE} element={<BoardFormPage/>}/>
|
|
| 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/>}/>
|
|
| 19 | 32 |
<Route path="*" element={<ReadyPage/>}/>
|
| 20 | 33 |
</Routes> |
| 21 | 34 |
); |
--- src/admin/route/adminRouteMap.ts
+++ src/admin/route/adminRouteMap.ts
... | ... | @@ -2,9 +2,16 @@ |
| 2 | 2 |
|
| 3 | 3 |
export const ADMIN_BBS_MASTER_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/SelectBBSMasterInfs.do`;
|
| 4 | 4 |
export const ADMIN_BBS_ARTICLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/article/`;
|
| 5 |
-export const ADMIN_BBS_ARTICLE_DETAIL_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/detail/`;
|
|
| 5 |
+export const ADMIN_BBS_ARTICLE_FORM_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/detail/`;
|
|
| 6 | 6 |
export const ADMIN_MENU_CREATE_TREE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/mnu/mcm/EgovMenuCreatSelectJtree.do`;
|
| 7 |
+ |
|
| 7 | 8 |
export const ADMIN_AUTHOR_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/EgovAuthorList.do`;
|
| 9 |
+export const ADMIN_AUTHOR_DETAIL_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/authorDetail.do`;
|
|
| 10 |
+export const ADMIN_AUTHOR_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/authorRoleList.do`;
|
|
| 11 |
+ |
|
| 12 |
+export const ADMIN_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/EgovRoleList.do`;
|
|
| 13 |
+export const ADMIN_ROLE_FORM_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/detail.do`;
|
|
| 14 |
+ |
|
| 8 | 15 |
export const ADMIN_MAIN_ZONE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/mainZoneList.do`;
|
| 9 | 16 |
export const ADMIN_CONTENT_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/cnt/contentList.do`;
|
| 10 | 17 |
export const ADMIN_MAIN_PAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/cmm/main/mainPage.do`;
|
... | ... | @@ -17,7 +24,7 @@ |
| 17 | 24 |
export const ADMIN_AUTHOR_GROUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rgm/EgovAuthorGroupList.do`;
|
| 18 | 25 |
export const ADMIN_POPUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/egovPopupList.do`;
|
| 19 | 26 |
export const ADMIN_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectWebLogList.do`;
|
| 20 |
-export const ADMIN_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/EgovRoleList.do`;
|
|
| 27 |
+ |
|
| 21 | 28 |
export const ADMIN_BANNER_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/bnr/selectBannerList.do`;
|
| 22 | 29 |
export const ADMIN_USER_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/NSelectWebLogList.do`;
|
| 23 | 30 |
export const ADMIN_LOGIN_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectLoginLogList.do`;
|
--- src/type/pageResponse.ts
+++ src/type/pageResponse.ts
... | ... | @@ -1,3 +1,5 @@ |
| 1 |
+import type {UseQueryResult} from "@tanstack/react-query";
|
|
| 2 |
+ |
|
| 1 | 3 |
export interface PageResponse<T, E = Record<string, unknown>> {
|
| 2 | 4 |
list: T[]; |
| 3 | 5 |
extraData: E; |
... | ... | @@ -5,4 +7,16 @@ |
| 5 | 7 |
totalPages: number; |
| 6 | 8 |
currentPage: number; |
| 7 | 9 |
size: number; |
| 8 |
-}(No newline at end of file) |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+export const createPageQueryResult = <T>( |
|
| 13 |
+ query: UseQueryResult<PageResponse<T>> |
|
| 14 |
+) => ({
|
|
| 15 |
+ list: query.data?.list ?? [], |
|
| 16 |
+ totalItems: query.data?.totalItems ?? 0, |
|
| 17 |
+ totalPages: query.data?.totalPages ?? 0, |
|
| 18 |
+ currentPage: query.data?.currentPage ?? 0, |
|
| 19 |
+ size: query.data?.size ?? 0, |
|
| 20 |
+ isLoading: query.isLoading, |
|
| 21 |
+ error: query.error, |
|
| 22 |
+});(No newline at end of file) |
+++ src/type/viewModel.ts
... | ... | @@ -0,0 +1,86 @@ |
| 1 | +import type {SearchParams} from "./searchParams.ts"; | |
| 2 | + | |
| 3 | +export type BreadcrumbItem = { | |
| 4 | + label: string; | |
| 5 | + url?: string; | |
| 6 | +}; | |
| 7 | + | |
| 8 | +export type Option = { | |
| 9 | + value: string; | |
| 10 | + label: string; | |
| 11 | +}; | |
| 12 | + | |
| 13 | +export type HeaderModel = { | |
| 14 | + title: string; | |
| 15 | + breadcrumb: BreadcrumbItem[]; | |
| 16 | + homeUrl?: string; | |
| 17 | +}; | |
| 18 | + | |
| 19 | +export type StatusModel = { | |
| 20 | + isLoading: boolean; | |
| 21 | + error: unknown; | |
| 22 | + successMessage?: string; | |
| 23 | +}; | |
| 24 | + | |
| 25 | +export type SearchModel<TSearchParams extends SearchParams> = { | |
| 26 | + totalItems: number; | |
| 27 | + searchParams: TSearchParams; | |
| 28 | + onChange: (params: TSearchParams) => void; | |
| 29 | + searchOptions?: Option[]; | |
| 30 | + pageSizeOptions?: Option[]; | |
| 31 | +}; | |
| 32 | + | |
| 33 | +export type PaginationModel = { | |
| 34 | + totalItems: number; | |
| 35 | + totalPages: number; | |
| 36 | + currentPage: number; | |
| 37 | + size: number; | |
| 38 | + onPageChange: (pageIndex: number) => void; | |
| 39 | +}; | |
| 40 | + | |
| 41 | +export type TableCheckModel<TId extends string | number = string> = { | |
| 42 | + isAllChecked: boolean; | |
| 43 | + isPartiallyChecked: boolean; | |
| 44 | + isChecked: (id: TId) => boolean; | |
| 45 | + onCheck: (id: TId, checked: boolean) => void; | |
| 46 | + onCheckAll: (checked: boolean) => void; | |
| 47 | +}; | |
| 48 | + | |
| 49 | +export type TablePaginationModel = Pick< | |
| 50 | + PaginationModel, | |
| 51 | + "totalItems" | "currentPage" | "totalPages" | |
| 52 | +>; | |
| 53 | + | |
| 54 | +export type ListTableModel<TItem, TSearchParams extends SearchParams> = { | |
| 55 | + items: TItem[]; | |
| 56 | + params: TSearchParams; | |
| 57 | + onChange: (params: TSearchParams) => void; | |
| 58 | + pagination: TablePaginationModel; | |
| 59 | +}; | |
| 60 | + | |
| 61 | +export type CheckableTableModel< | |
| 62 | + TItem, | |
| 63 | + TSearchParams extends SearchParams, | |
| 64 | + TId extends string | number = string | |
| 65 | +> = ListTableModel<TItem, TSearchParams> & { | |
| 66 | + check: TableCheckModel<TId>; | |
| 67 | +}; | |
| 68 | + | |
| 69 | +export type RowActionsModel<TActions> = { | |
| 70 | + rowActions: TActions; | |
| 71 | +}; | |
| 72 | + | |
| 73 | +export type ListActionsModel = { | |
| 74 | + disabled?: boolean; | |
| 75 | + onDelete?: () => void | Promise<void>; | |
| 76 | + onCreate?: () => void; | |
| 77 | +}; | |
| 78 | + | |
| 79 | +export type FormActionsModel<TMode extends string = "create" | "update"> = { | |
| 80 | + mode: TMode; | |
| 81 | + disabled?: boolean; | |
| 82 | + onCreate?: () => void | Promise<void>; | |
| 83 | + onUpdate?: () => void | Promise<void>; | |
| 84 | + onDelete?: () => void | Promise<void>; | |
| 85 | + onList?: () => void; | |
| 86 | +}; |
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?