조민수 조민수 05-15
공용 Model 생성 [viewModel]에 따른 board 수정 및 권한관리 권한별롤관리 생성
@ad3518496a6225e143c1cd39cd376428a46e8ef3
src/admin/component/ListSearchForm.tsx
--- src/admin/component/ListSearchForm.tsx
+++ src/admin/component/ListSearchForm.tsx
@@ -2,13 +2,9 @@
 import {SearchBar} from "./SearchBar.tsx";
 import type {SearchParams} from "../../type/searchParams.ts";
 import {PageSizeSelector} from "./pagination/PageSizeSelector.tsx";
+import type {SearchModel} from "../../type/viewModel.ts";
 
-interface ListSearchFormProps<T extends SearchParams> {
-    totalItems: number;
-    searchParams: T;
-    onChange: (params: T) => void;
-    searchOptions: { value: string; label: string; }[];
-    pageSizeOptions?: { value: string; label: string; }[];
+interface ListSearchFormProps<T extends SearchParams> extends SearchModel<T> {
     totalLabel?: string;
 }
 
@@ -45,7 +41,7 @@
         <div className="search_area">
             <div className="search_left">
                 <p className="total_number">
-                    {totalLabel} <b>{totalItems}</b>
+                    {totalLabel} <b>{totalItems}</b>건
                 </p>
             </div>
 
src/admin/component/PageHeader.tsx
--- src/admin/component/PageHeader.tsx
+++ src/admin/component/PageHeader.tsx
@@ -1,10 +1,6 @@
-interface PageHeaderProps {
-    title: string;
-    breadcrumb: any[];
-    homeUrl?: string;
-}
+import type {HeaderModel} from "../../type/viewModel.ts";
 
-export const PageHeader = ({title, breadcrumb, homeUrl}: PageHeaderProps) => {
+export const PageHeader = ({title, breadcrumb, homeUrl}: HeaderModel) => {
     return (
         <div className="content_title">
             <div className="left">
src/admin/component/SearchBar.tsx
--- src/admin/component/SearchBar.tsx
+++ src/admin/component/SearchBar.tsx
@@ -3,7 +3,7 @@
 interface SearchBarProps {
     searchCnd: string
     searchKeyword: string
-    options: { value: string; label: string }[]
+    options?: { value: string; label: string }[]
     onChange: (
         name: string,
         value: string
@@ -22,20 +22,24 @@
 
     return (
         <>
-            <select
-                id="searchCnd"
-                name="searchCnd"
-                className="search_select"
-                value={searchCnd}
-                onChange={handleChange}
-            >
-                {options.map((option) => (
-                    <option key={option.value} value={option.value}>
-                        {option.label}
-                    </option>
-                ))}
-            </select>
-
+            {options && (
+                <select
+                    id="searchCnd"
+                    name="searchCnd"
+                    className="search_select"
+                    value={searchCnd}
+                    onChange={handleChange}
+                >
+                    {options.map((option) => (
+                        <option
+                            key={option.value}
+                            value={option.value}
+                        >
+                            {option.label}
+                        </option>
+                    ))}
+                </select>
+            )}
             <div className="search_type input_type">
                 <input
                     type="text"
src/admin/component/pagination/Pagination.tsx
--- src/admin/component/pagination/Pagination.tsx
+++ src/admin/component/pagination/Pagination.tsx
@@ -1,10 +1,4 @@
-type PaginationProps = {
-    totalItems: number;
-    totalPages: number;
-    currentPage: number;
-    size: number;
-    onPageChange: (page: number) => void;
-};
+import type {PaginationModel} from "../../../type/viewModel.ts";
 
 export function Pagination({
                                totalItems,
@@ -12,7 +6,7 @@
                                currentPage,
                                size = 10,
                                onPageChange,
-                           }: PaginationProps) {
+                           }: PaginationModel) {
 
     if (totalItems === 0) {
         return null;
src/admin/feature/board/api/boardApi.ts
--- src/admin/feature/board/api/boardApi.ts
+++ src/admin/feature/board/api/boardApi.ts
@@ -3,7 +3,7 @@
 import type {
     BoardArticleExtra,
     BoardArticleListItem,
-    BoardArticleSearchParams,
+    BoardArticleSearchParams, BoardDeleteListItem,
     BoardDetailResponse,
     BoardFormItem,
     BoardListItem,
@@ -23,7 +23,7 @@
 }
 
 export async function createBoard(params: BoardFormItem) {
-    return apiClient.post(`/cop/bbs/insertBoardMasater.do`, params);
+    return apiClient.post(`/cop/bbs/insertBoardMaster.do`, params);
 }
 
 export async function updateBoard(params: BoardFormItem) {
@@ -34,6 +34,6 @@
     return apiClient.post(`/cop/bbs/deleteBoardMaster.do?bbsId=${bbsId}`);
 }
 
-export async function deleteBoardBatch() {
-    return apiClient.post(`/cop/bbs/deleteBoardBatch.do`, {});
+export async function deleteBoardBatch(bbsIds: BoardDeleteListItem[] ) {
+    return apiClient.post(`/cop/bbs/deleteBoardMasterBatch.do`, bbsIds);
 }
src/admin/feature/board/components/article/BoardArticleListTable.tsx
--- src/admin/feature/board/components/article/BoardArticleListTable.tsx
+++ src/admin/feature/board/components/article/BoardArticleListTable.tsx
@@ -2,35 +2,17 @@
 import {EmptyRow} from "../../../../component/EmptyRow.tsx";
 import {BoardArticleListTableHeader} from "./BoardArticleListTableHeader.tsx";
 import {BoardArticleListTableRow} from "./BoardArticleListTableRow.tsx";
+import type {CheckableTableModel} from "../../../../../type/viewModel.ts";
 
-type BoartArticleListTableProps = {
+type BoardArticleListTableProps = CheckableTableModel<BoardArticleListItem, BoardArticleSearchParams>;
 
-    items: BoardArticleListItem[];
-    params: BoardArticleSearchParams;
-    onChange: (params: BoardArticleSearchParams) => void;
-    isAllChecked: boolean;
-    isPartiallyChecked: boolean;
-    isChecked: (id: string) => boolean;
-    onCheck: (id: string, checked: boolean) => void;
-    onCheckAll: (checked: boolean) => void;
-    totalItems: number
-    currentPage: number
-    totalPages: number
-}
-
-export const BoartArticleListTable = ({
+export const BoardArticleListTable = ({
                                           items,
                                           params,
                                           onChange,
-                                          isAllChecked,
-                                          isPartiallyChecked,
-                                          isChecked,
-                                          onCheck,
-                                          onCheckAll,
-                                          totalItems,
-                                          currentPage,
-                                          totalPages
-                                      }: BoartArticleListTableProps) => {
+                                          check,
+                                          pagination
+                                      }: BoardArticleListTableProps) => {
 
     return (
         <div className={"table table_type_cols"}>
@@ -38,9 +20,9 @@
                 <BoardArticleListTableHeader
                     params={params}
                     onChange={onChange}
-                    checked={isAllChecked}
-                    indeterminate={isPartiallyChecked}
-                    onCheckAll={onCheckAll}
+                    checked={check.isAllChecked}
+                    indeterminate={check.isPartiallyChecked}
+                    onCheckAll={check.onCheckAll}
                 />
                 <tbody>
                 {items.length > 0 ?
@@ -50,11 +32,11 @@
                                 item={item}
                                 index={index}
                                 searchParams={params}
-                                totalItems={totalItems}
-                                currentPage={currentPage}
-                                totalPages={totalPages}
-                                checked={isChecked(item.nttId)}
-                                onCheck={onCheck}
+                                totalItems={pagination.totalItems}
+                                currentPage={pagination.currentPage}
+                                totalPages={pagination.totalPages}
+                                checked={check.isChecked(item.nttId)}
+                                onCheck={check.onCheck}
                             />
                         )) :
                     (<EmptyRow colSpan={8}/>)
src/admin/feature/board/components/master/BoardFormTable.tsx
--- src/admin/feature/board/components/master/BoardFormTable.tsx
+++ src/admin/feature/board/components/master/BoardFormTable.tsx
@@ -1,24 +1,21 @@
 import type {ChangeEvent} from "react";
 import type {BoardFormItem} from "../../type/board.types.ts";
-import type {CommonCodeItem} from "../../../../../type/code.ts";
 
 type BoardFormTableProps = {
     form: BoardFormItem;
-    typeList?: CommonCodeItem[];
     onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => void;
 };
 
 export const BoardFormTable = ({
                                    form,
-                                   typeList = [],
                                    onChange
                                }: BoardFormTableProps) => {
     return (
         <div className="table table_type_rows">
             <table>
                 <colgroup>
-                    <col style={{ width: '200px' }} />
-                    <col style={{ width: 'auto' }} />
+                    <col style={{width: '200px'}}/>
+                    <col style={{width: 'auto'}}/>
                 </colgroup>
 
                 <tbody>
@@ -44,20 +41,31 @@
                         <span className="required">*</span>
                         게시판유형
                     </th>
+
                     <td>
-                        {typeList.map((type) => (
-                            <label key={type.code}>
-                                <input
-                                    type="radio"
-                                    id={`bbsTyCode_${type.code}`}
-                                    name="bbsTyCode"
-                                    value={type.code}
-                                    checked={form.bbsTyCode === type.code}
-                                    onChange={onChange}
-                                />
-                                {type.codeNm}
-                            </label>
-                        ))}
+                        <label>
+                            <input
+                                id="bbsTyCode_BBST01"
+                                type="radio"
+                                value="BBST01"
+                                name="bbsTyCode"
+                                checked={form.bbsTyCode === 'BBST01'}
+                                onChange={onChange}
+                            />
+                            일반게시판
+                        </label>
+
+                        <label>
+                            <input
+                                id="bbsTyCode_BBST05"
+                                type="radio"
+                                value="BBST05"
+                                name="bbsTyCode"
+                                checked={form.bbsTyCode === 'BBST05'}
+                                onChange={onChange}
+                            />
+                            포토형게시판
+                        </label>
                     </td>
                 </tr>
 
src/admin/feature/board/components/master/BoardListTable.tsx
--- src/admin/feature/board/components/master/BoardListTable.tsx
+++ src/admin/feature/board/components/master/BoardListTable.tsx
@@ -3,39 +3,23 @@
 import {EmptyRow} from "../../../../component/EmptyRow.tsx";
 import {BoardListTableHeader} from "./BoardListTableHeader.tsx";
 import {BoardListTableRow} from "./BoardListTableRow.tsx";
+import type {CheckableTableModel, RowActionsModel} from "../../../../../type/viewModel.ts";
 
-interface BoardListTableProps {
-    items: BoardListItem[]
-    params: SearchParams
-    onChange: (params: SearchParams) => void
-    totalItems: number
-    currentPage: number
-    totalPages: number
-    isAllChecked: boolean
-    isPartiallyChecked: boolean
-    isChecked: (id: string) => boolean
-    onCheck: (id: string, checked: boolean) => void
-    onCheckAll: (checked: boolean) => void
-    onDetail: (bbsId: string) => void
-    onArticleList: (bbsId: string) => void
-    onPreview: (bbsId: string) => void
-}
+type BoardListTableProps =
+    CheckableTableModel<BoardListItem, SearchParams> &
+    RowActionsModel<{
+        onDetail: (bbsId: string) => void
+        onArticleList: (bbsId: string) => void
+        onPreview: (bbsId: string) => void
+    }>;
 
 export function BoardListTable({
                                    items,
                                    params,
                                    onChange,
-                                   totalItems,
-                                   currentPage,
-                                   totalPages,
-                                   isAllChecked,
-                                   isPartiallyChecked,
-                                   isChecked,
-                                   onCheck,
-                                   onCheckAll,
-                                   onDetail,
-                                   onArticleList,
-                                   onPreview
+                                   pagination,
+                                   check,
+                                   rowActions
                                }: BoardListTableProps) {
 
     return (
@@ -44,9 +28,9 @@
                 <BoardListTableHeader
                     params={params}
                     onChange={onChange}
-                    checked={isAllChecked}
-                    indeterminate={isPartiallyChecked}
-                    onCheckAll={onCheckAll}
+                    checked={check.isAllChecked}
+                    indeterminate={check.isPartiallyChecked}
+                    onCheckAll={check.onCheckAll}
                 />
                 <tbody>
                 {items.length > 0 ?
@@ -56,14 +40,14 @@
                             item={item}
                             index={index}
                             searchParams={params}
-                            totalItems={totalItems}
-                            currentPage={currentPage}
-                            totalPages={totalPages}
-                            checked={isChecked(item.bbsId)}
-                            onCheck={onCheck}
-                            onDetail={onDetail}
-                            onArticleList={onArticleList}
-                            onPreview={onPreview}
+                            totalItems={pagination.totalItems}
+                            currentPage={pagination.currentPage}
+                            totalPages={pagination.totalPages}
+                            checked={check.isChecked(item.bbsId)}
+                            onCheck={check.onCheck}
+                            onDetail={rowActions.onDetail}
+                            onArticleList={rowActions.onArticleList}
+                            onPreview={rowActions.onPreview}
                         />))
                     : (<EmptyRow colSpan={9}/>)
                 }
 
src/admin/feature/board/hook/mutation/useDeleteBatchBoard.ts (added)
+++ src/admin/feature/board/hook/mutation/useDeleteBatchBoard.ts
@@ -0,0 +1,15 @@
+import {useMutation, useQueryClient} from "@tanstack/react-query";
+import {deleteBoardBatch} from "../../api/boardApi.ts";
+
+export const useDeleteBatchBoard = () => {
+    const queryClient = useQueryClient();
+
+    return useMutation({
+        mutationFn: deleteBoardBatch,
+        onSuccess: () => {
+            queryClient.invalidateQueries({
+                queryKey: ['boardList'],
+            });
+        }
+    });
+}(No newline at end of file)
src/admin/feature/board/hook/page/useBoardArticleListPage.ts
--- src/admin/feature/board/hook/page/useBoardArticleListPage.ts
+++ src/admin/feature/board/hook/page/useBoardArticleListPage.ts
@@ -1,8 +1,25 @@
 import {useMemo, useState} from "react";
 import {ADMIN_BBS_MASTER_ROUTE} from "../../../../route/adminRouteMap.ts";
 import {useCheckedList} from "../../../../hook/useCheckedList.ts";
-import type {BoardArticleSearchParams} from "../../type/board.types.ts";
+import type {BoardArticleListItem, BoardArticleSearchParams} from "../../type/board.types.ts";
 import {useBoardArticleList} from "../query/useBoardArticleList.ts";
+import type {
+    CheckableTableModel,
+    HeaderModel,
+    PaginationModel,
+    SearchModel,
+    StatusModel,
+} from "../../../../../type/viewModel.ts";
+
+type BoardArticleListPageModel = {
+    header: HeaderModel;
+    status: StatusModel;
+    search: SearchModel<BoardArticleSearchParams>;
+    table: CheckableTableModel<BoardArticleListItem, BoardArticleSearchParams> & {
+        typeCode: string;
+    };
+    pagination: PaginationModel;
+};
 
 const initSearchParam: BoardArticleSearchParams = {
     pageIndex: 1,
@@ -20,7 +37,7 @@
     {value: '2', label: '작성자'},
 ];
 
-export const useBoardArticleListPage = (bbsId: string) => {
+export const useBoardArticleListPage = (bbsId: string): BoardArticleListPageModel => {
     const [searchDraft, setSearchDraft] = useState<BoardArticleSearchParams>(initSearchParam);
     const searchParams = useMemo(
         () => ({
@@ -45,13 +62,11 @@
         [list]
     );
     const {
-        checkedIds,
         isAllChecked,
         isPartiallyChecked,
         isChecked,
         handleCheck,
         handleCheckAll,
-        resetChecked,
     } = useCheckedList(articleIds);
     const bbsNm = extraData?.boardMaster?.bbsNm ?? '';
     const bbsTyCode = extraData?.boardMaster?.bbsTyCode ?? '';
@@ -79,27 +94,46 @@
     };
 
     return {
-        title,
-        breadcrumb,
-        searchOptions,
-        searchParams,
-        list,
-        bbsTyCode,
-        totalItems,
-        currentPage,
-        totalPages,
-        size,
-        isLoading,
-        error,
-        successMessage,
-        checkedIds,
-        isAllChecked,
-        isPartiallyChecked,
-        isChecked,
-        handleCheck,
-        handleCheckAll,
-        resetChecked,
-        handleSearchChange,
-        handlePageChange,
+        header: {
+            title,
+            breadcrumb,
+            homeUrl: "#",
+        },
+        status: {
+            isLoading,
+            error,
+            successMessage,
+        },
+        search: {
+            totalItems,
+            searchParams,
+            onChange: handleSearchChange,
+            searchOptions,
+        },
+        table: {
+            typeCode: bbsTyCode,
+            items: list,
+            params: searchParams,
+            onChange: handleSearchChange,
+            check: {
+                isAllChecked,
+                isPartiallyChecked,
+                isChecked,
+                onCheck: handleCheck,
+                onCheckAll: handleCheckAll,
+            },
+            pagination: {
+                totalItems,
+                currentPage,
+                totalPages,
+            },
+        },
+        pagination: {
+            totalItems,
+            totalPages,
+            currentPage,
+            size,
+            onPageChange: handlePageChange,
+        },
     };
 };
src/admin/feature/board/hook/page/useBoardFormPage.ts (Renamed from src/admin/feature/board/hook/page/useBoardForm.ts)
--- src/admin/feature/board/hook/page/useBoardForm.ts
+++ src/admin/feature/board/hook/page/useBoardFormPage.ts
@@ -8,8 +8,19 @@
 import {useCreateBoard} from "../mutation/useCreateBoard.ts";
 import {useDeleteBoard} from "../mutation/useDeleteBoard.ts";
 import {useUpdateBoard} from "../mutation/useUpdateBoard.ts";
+import type {FormActionsModel, HeaderModel, StatusModel} from "../../../../../type/viewModel.ts";
 
 export type BoardFormMode = 'create' | 'update';
+
+type BoardFormPageModel = {
+    header: HeaderModel;
+    status: StatusModel;
+    form: {
+        form: BoardFormItem;
+        onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => void;
+    };
+    actions: FormActionsModel<BoardFormMode>;
+};
 
 const initBoardFormData: BoardFormItem = {
     bbsId: '',
@@ -33,7 +44,7 @@
     ...item,
 });
 
-export const useBoardForm = (bbsId: string) => {
+export const useBoardFormPage = (bbsId: string): BoardFormPageModel => {
     const navigate = useNavigate();
     const mode: BoardFormMode = bbsId ? 'update' : 'create';
     const [formDraft, setFormDraft] = useState<Partial<BoardFormItem>>({});
@@ -137,18 +148,27 @@
     };
 
     return {
-        mode,
-        title,
-        breadcrumb,
-        form,
-        typeList: data?.typeList,
-        isLoading,
-        error,
-        isPending,
-        handleChange,
-        handleCreate,
-        handleUpdate,
-        handleDelete,
-        handleList,
+        header: {
+            title,
+            breadcrumb,
+            homeUrl: "#",
+        },
+        status: {
+            isLoading,
+            error,
+            successMessage: '데이터 조회가 완료되었습니다.',
+        },
+        form: {
+            form,
+            onChange: handleChange,
+        },
+        actions: {
+            mode,
+            disabled: isPending,
+            onCreate: handleCreate,
+            onUpdate: handleUpdate,
+            onDelete: handleDelete,
+            onList: handleList,
+        },
     };
 };
src/admin/feature/board/hook/page/useBoardListPage.ts
--- src/admin/feature/board/hook/page/useBoardListPage.ts
+++ src/admin/feature/board/hook/page/useBoardListPage.ts
@@ -1,10 +1,35 @@
 import {useMemo, useState} from "react";
-import type {BoardSearchParams} from "../../type/board.types.ts";
+import type {BoardDeleteListItem, BoardListItem, BoardSearchParams} from "../../type/board.types.ts";
 import {useBoardList} from "../query/useBoardList.ts";
-import {ADMIN_BBS_ARTICLE_DETAIL_ROUTE, ADMIN_BBS_ARTICLE_LIST_ROUTE} from "../../../../route/adminRouteMap.ts";
+import {ADMIN_BBS_ARTICLE_FORM_ROUTE, ADMIN_BBS_ARTICLE_LIST_ROUTE} from "../../../../route/adminRouteMap.ts";
 import {useNavigate} from "react-router-dom";
 import {useCheckedList} from "../../../../hook/useCheckedList.ts";
 import {toast} from "react-toastify";
+import {useDeleteBatchBoard} from "../mutation/useDeleteBatchBoard.ts";
+import type {
+    CheckableTableModel,
+    HeaderModel,
+    ListActionsModel,
+    PaginationModel,
+    RowActionsModel,
+    SearchModel,
+    StatusModel,
+} from "../../../../../type/viewModel.ts";
+
+type BoardListRowActions = {
+    onDetail: (bbsId: string) => void;
+    onArticleList: (bbsId: string) => void;
+    onPreview: (bbsId: string) => void;
+};
+
+type BoardListPageModel = {
+    header: HeaderModel;
+    status: StatusModel;
+    search: SearchModel<BoardSearchParams>;
+    table: CheckableTableModel<BoardListItem, BoardSearchParams> & RowActionsModel<BoardListRowActions>;
+    actions: ListActionsModel;
+    pagination: PaginationModel;
+};
 
 const initSearchParam: BoardSearchParams = {
     pageIndex: 1,
@@ -26,7 +51,7 @@
     {value: '30', label: '30건씩'},
 ]
 
-export const useBoardListPage = () => {
+export const useBoardListPage = (): BoardListPageModel => {
 
     const [searchParams, setSearchParams] = useState(initSearchParam);
     const {
@@ -49,8 +74,9 @@
         isChecked,
         handleCheck,
         handleCheckAll,
-        resetChecked,
-    } = useCheckedList(boardIds);
+    } = useCheckedList<string>(boardIds);
+    const {mutateAsync: deleteBoardBatch} = useDeleteBatchBoard();
+
     const title = '게시판 관리';
     const breadcrumb = [{label: '게시판 관리'}];
     const homeUrl = '#';
@@ -58,7 +84,7 @@
     const navigate = useNavigate();
 
     const handleDetail = (bbsId: string) => {
-        navigate(ADMIN_BBS_ARTICLE_DETAIL_ROUTE + bbsId);
+        navigate(ADMIN_BBS_ARTICLE_FORM_ROUTE + bbsId);
     }
     const handleArticleList = (bbsId: string) => {
         navigate(ADMIN_BBS_ARTICLE_LIST_ROUTE + bbsId);
@@ -72,46 +98,80 @@
             pageIndex,
         }));
     }
-    const handleDeleteBatch = () => {
+    const handleDeleteBatch = async () => {
         if (checkedIds.length === 0) {
             toast.warning('미사용 처리할 게시판을 선택해주세요.');
             return;
-        } else {
-            toast.info("삭제로직은 아직 미처리");
         }
+        const boardList: BoardDeleteListItem[] = checkedIds.map((bbsId) => ({
+            bbsId
+        }));
+
+        await toast.promise(
+            deleteBoardBatch(boardList),
+            {
+                pending: '미사용 처리 중...',
+                success: '미사용 완료',
+                error: '미사용 실패'
+            }
+        );
     }
+
+
     const handleCreate = () => {
-        navigate(ADMIN_BBS_ARTICLE_DETAIL_ROUTE);
+        navigate(ADMIN_BBS_ARTICLE_FORM_ROUTE);
     }
 
     return {
-        list,
-        totalItems,
-        totalPages,
-        currentPage,
-        size,
-        isLoading,
-        error,
-        title,
-        breadcrumb,
-        homeUrl,
-        searchOptions,
-        pageSizeOptions,
-        successMessage,
-        searchParams,
-        setSearchParams,
-        handleDetail,
-        handleArticleList,
-        handlePreview,
-        handlePageChange,
-        handleDeleteBatch,
-        handleCreate,
-        checkedIds,
-        isAllChecked,
-        isPartiallyChecked,
-        isChecked,
-        handleCheck,
-        handleCheckAll,
-        resetChecked,
+        header: {
+            title,
+            breadcrumb,
+            homeUrl,
+        },
+        status: {
+            isLoading,
+            error,
+            successMessage,
+        },
+        search: {
+            totalItems,
+            searchParams,
+            onChange: setSearchParams,
+            searchOptions,
+            pageSizeOptions,
+        },
+        table: {
+            items: list,
+            params: searchParams,
+            onChange: setSearchParams,
+            pagination: {
+                totalItems,
+                currentPage,
+                totalPages,
+            },
+            check: {
+                isAllChecked,
+                isPartiallyChecked,
+                isChecked,
+                onCheck: handleCheck,
+                onCheckAll: handleCheckAll,
+            },
+            rowActions: {
+                onDetail: handleDetail,
+                onArticleList: handleArticleList,
+                onPreview: handlePreview,
+            },
+        },
+        actions: {
+            onDelete: handleDeleteBatch,
+            onCreate: handleCreate,
+        },
+        pagination: {
+            totalItems,
+            totalPages,
+            currentPage,
+            size,
+            onPageChange: handlePageChange,
+        },
     }
 }
src/admin/feature/board/page/BoardArticleListPage.tsx
--- src/admin/feature/board/page/BoardArticleListPage.tsx
+++ src/admin/feature/board/page/BoardArticleListPage.tsx
@@ -3,78 +3,35 @@
 import {ListSearchForm} from "../../../component/ListSearchForm.tsx";
 import {Pagination} from "../../../component/pagination/Pagination.tsx";
 import {useLoadingToast} from "../../../hook/useLoadingToast.ts";
-import {BoartArticleListTable} from "../components/article/BoardArticleListTable.tsx";
+import {BoardArticleListTable} from "../components/article/BoardArticleListTable.tsx";
 import {BoardArticleImageListTable} from "../components/article/BoardArticleImageListTable.tsx";
 import {useBoardArticleListPage} from "../hook/page/useBoardArticleListPage.ts";
 
 export const BoardArticleListPage = () => {
     const {bbsId = ''} = useParams();
     const {
-        title,
-        breadcrumb,
-        searchOptions,
-        searchParams,
-        list,
-        bbsTyCode,
-        totalItems,
-        currentPage,
-        totalPages,
-        size,
-        isLoading,
-        error,
-        successMessage,
-        isAllChecked,
-        isPartiallyChecked,
-        isChecked,
-        handleCheck,
-        handleCheckAll,
-        handleSearchChange,
-        handlePageChange,
+        header,
+        status,
+        search,
+        table,
+        pagination,
     } = useBoardArticleListPage(bbsId);
 
-    useLoadingToast({
-        isLoading,
-        error,
-        successMessage
-    });
-
-    const homeUrl = '#'
+    useLoadingToast(status);
 
     return (
         <>
-            <PageHeader title={title} breadcrumb={breadcrumb} homeUrl={homeUrl}/>
+            <PageHeader {...header}/>
             <ListSearchForm
-                totalItems={totalItems}
-                searchParams={searchParams}
-                onChange={handleSearchChange}
-                searchOptions={searchOptions}
-                totalLabel={"게시글"}
+                {...search}
+                totalLabel="게시글"
             />
-            {bbsTyCode === "BBST05" ?
-                <BoardArticleImageListTable
-                    items={list}
-                /> :
-                <BoartArticleListTable
-                    items={list}
-                    params={searchParams}
-                    onChange={handleSearchChange}
-                    isAllChecked={isAllChecked}
-                    isPartiallyChecked={isPartiallyChecked}
-                    isChecked={isChecked}
-                    onCheck={handleCheck}
-                    onCheckAll={handleCheckAll}
-                    totalPages={totalPages}
-                    currentPage={currentPage}
-                    totalItems={totalItems}
-                />
-            }
-            <Pagination
-                totalItems={totalItems}
-                totalPages={totalPages}
-                currentPage={currentPage}
-                size={size}
-                onPageChange={handlePageChange}
-            />
+            {table.typeCode === "BBST05" ? (
+                <BoardArticleImageListTable items={table.items}/>
+            ) : (
+                <BoardArticleListTable {...table}/>
+            )}
+            <Pagination {...pagination}/>
         </>
     );
 }
src/admin/feature/board/page/BoardFormPage.tsx
--- src/admin/feature/board/page/BoardFormPage.tsx
+++ src/admin/feature/board/page/BoardFormPage.tsx
@@ -3,48 +3,24 @@
 import {useLoadingToast} from "../../../hook/useLoadingToast.ts";
 import {ActionButtonFormGroup} from "../../../component/button/ActionButtonFormGroup.tsx";
 import {BoardFormTable} from "../components/master/BoardFormTable.tsx";
-import {useBoardForm} from "../hook/page/useBoardForm.ts";
+import {useBoardFormPage} from "../hook/page/useBoardFormPage.ts";
 
 export const BoardFormPage = () => {
     const {bbsId = ''} = useParams();
     const {
-        mode,
-        title,
-        breadcrumb,
+        header,
+        status,
         form,
-        typeList,
-        isLoading,
-        error,
-        isPending,
-        handleChange,
-        handleCreate,
-        handleUpdate,
-        handleDelete,
-        handleList,
-    } = useBoardForm(bbsId);
+        actions,
+    } = useBoardFormPage(bbsId);
 
-    useLoadingToast({
-        isLoading,
-        error,
-        successMessage: '데이터 조회가 완료되었습니다.'
-    });
+    useLoadingToast(status);
 
     return (
         <>
-            <PageHeader title={title} breadcrumb={breadcrumb} homeUrl="#"/>
-            <BoardFormTable
-                form={form}
-                typeList={typeList}
-                onChange={handleChange}
-            />
-            <ActionButtonFormGroup
-                mode={mode}
-                disabled={isPending}
-                onDelete={handleDelete}
-                onCreate={handleCreate}
-                onUpdate={handleUpdate}
-                onList={handleList}
-            />
+            <PageHeader {...header}/>
+            <BoardFormTable {...form}/>
+            <ActionButtonFormGroup {...actions}/>
         </>
     );
 };
src/admin/feature/board/page/BoardListPage.tsx
--- src/admin/feature/board/page/BoardListPage.tsx
+++ src/admin/feature/board/page/BoardListPage.tsx
@@ -8,85 +8,31 @@
 
 export const BoardListPage = () => {
     const {
-        list,
-        totalItems,
-        totalPages,
-        currentPage,
-        size,
-        isLoading,
-        error,
-        title,
-        breadcrumb,
-        homeUrl,
-        searchOptions,
-        pageSizeOptions,
-        successMessage,
-        searchParams,
-        setSearchParams,
-        handleDetail,
-        handleArticleList,
-        handlePreview,
-        handlePageChange,
-        handleDeleteBatch,
-        handleCreate,
-        isAllChecked,
-        isPartiallyChecked,
-        isChecked,
-        handleCheck,
-        handleCheckAll,
+        header,
+        status,
+        search,
+        table,
+        actions,
+        pagination,
     } = useBoardListPage();
 
-    useLoadingToast({
-        isLoading,
-        error,
-        successMessage
-    });
+    useLoadingToast(status);
 
     return (
         <>
-            <PageHeader
-                title={title}
-                breadcrumb={breadcrumb}
-                homeUrl={homeUrl}
-            />
+            <PageHeader {...header}/>
             <ListSearchForm
-                totalItems={totalItems}
-                searchParams={searchParams}
-                onChange={setSearchParams}
-                searchOptions={searchOptions}
-                pageSizeOptions={pageSizeOptions}
-                totalLabel={"게시판"}
+                {...search}
+                totalLabel="게시판"
             />
-            {isLoading && <p>Loading...</p>}
+            {status.isLoading && <p>Loading...</p>}
 
-            <BoardListTable
-                items={list}
-                params={searchParams}
-                onChange={setSearchParams}
-                totalItems={totalItems}
-                currentPage={currentPage}
-                totalPages={totalPages}
-                isAllChecked={isAllChecked}
-                isPartiallyChecked={isPartiallyChecked}
-                isChecked={isChecked}
-                onCheck={handleCheck}
-                onCheckAll={handleCheckAll}
-                onDetail={handleDetail}
-                onArticleList={handleArticleList}
-                onPreview={handlePreview}
-            />
+            <BoardListTable {...table}/>
             <ActionButtonListGroup
-                onDelete={handleDeleteBatch}
-                onCreate={handleCreate}
-                deleteLabel={"미사용"}
+                {...actions}
+                deleteLabel="미사용"
             />
-            <Pagination
-                totalItems={totalItems}
-                totalPages={totalPages}
-                currentPage={currentPage}
-                size={size}
-                onPageChange={handlePageChange}
-            />
+            <Pagination {...pagination}/>
         </>
     )
 };
src/admin/feature/board/type/board.types.ts
--- src/admin/feature/board/type/board.types.ts
+++ src/admin/feature/board/type/board.types.ts
@@ -9,6 +9,10 @@
     bbsId: string;
 }
 
+export interface BoardDeleteListItem {
+    bbsId: string;
+}
+
 export interface BoardListItem {
     bbsId: string
     bbsNm: string
 
src/admin/feature/role/api/roleApi.ts (added)
+++ src/admin/feature/role/api/roleApi.ts
@@ -0,0 +1,49 @@
+import {apiClient} from "../../../../api/apiClient.ts";
+import type {
+    AuthorRoleListItem,
+    AuthorGroupSearchParams, AuthorListItem,
+    AuthorRoleMenuSearchParams,
+    AuthorRoleSearchParams,
+    AuthorSearchParams, RoleSearchParams, UpdateAuthorRoleParams, RoleDetailResponse
+} from "../type/role.types.ts";
+import type {PageResponse} from "../../../../type/pageResponse.ts";
+// 권한별롤관리
+export async function fetchAuthorList(params: AuthorSearchParams) {
+    return apiClient.get<PageResponse<AuthorListItem>>(`/sec/ram/list.do`, params);
+}
+
+export async function fetchAuthorDetail(authorCode: string) {
+    return apiClient.get(`/sec/ram/detail.do?authorCode=${authorCode}`);
+}
+
+// 권한롤관리
+export async function fetchAuthorRoleList(params: AuthorRoleSearchParams) {
+    return apiClient.get<PageResponse<AuthorRoleListItem>>(`/sec/ram/authorList.do`, params);
+}
+
+// 권한별메뉴관리
+export async function fetchAuthorRoleMenuList(params: AuthorRoleMenuSearchParams) {
+    return apiClient.get(`/sym/mnu/mcm/list.do`, params);
+}
+export async function fetchAuthorRoleMenuDetail(authorCode: string) {
+    return apiClient.get(`/sym/mnu/mcm/detail?authorCode=${authorCode}`);
+}
+
+// 관리자별권한관리
+export async function fetchAuthorGroupList(params: AuthorGroupSearchParams) {
+    return apiClient.get(`/sec/rgm/list.do`, params);
+}
+
+// 롤관리
+export async function fetchRoleList(params: RoleSearchParams) {
+    return apiClient.get(`/sec/rmt/list.do`, params);
+}
+
+export async function fetchRoleDetail(roleCode: string) {
+    return apiClient.get<RoleDetailResponse>(`/sec/rmt/detail.do?roleCode=${roleCode}`);
+}
+
+export async function updateAuthorRole(params: UpdateAuthorRoleParams) {
+    return apiClient.post(`/sec/ram/updateAuthorRole.do?authorCode=${params.authorCode}&roleCode=${params.roleCode}&regYn=${params.regYn}`);
+}
+
 
src/admin/feature/role/components/author/AuthorListTable.tsx (added)
+++ src/admin/feature/role/components/author/AuthorListTable.tsx
@@ -0,0 +1,60 @@
+import {AuthorListTableHeader} from "./AuthorListTableHeader.tsx";
+import type {SearchParams} from "../../../../../type/searchParams.ts";
+import type {AuthorListItem} from "../../type/role.types.ts";
+import {EmptyRow} from "../../../../component/EmptyRow.tsx";
+import {AuthorListTableRow} from "./AuthorListTableRow.tsx";
+import type {CheckableTableModel, RowActionsModel} from "../../../../../type/viewModel.ts";
+
+type AuthorListTableProps =
+    CheckableTableModel<AuthorListItem, SearchParams> &
+    RowActionsModel<{
+        onDetail: (authorCode: string) => void
+        onRoleMove: (authorCode: string, authorNm: string) => void
+    }>;
+
+
+export const AuthorListTable = ({
+                                    items,
+                                    params,
+                                    onChange,
+                                    pagination,
+                                    check,
+                                    rowActions,
+                                }: AuthorListTableProps) => {
+    return (
+        <div className="table table_type_cols">
+            <table>
+                <AuthorListTableHeader
+                    params={params}
+                    onChange={onChange}
+                    checked={check.isAllChecked}
+                    indeterminate={check.isPartiallyChecked}
+                    onCheckAll={check.onCheckAll}
+                />
+                <tbody>
+                {items.length > 0 ?
+                    items.map((item, index) => (
+                        <AuthorListTableRow
+                            key={index}
+                            item={item}
+                            index={index}
+                            searchParams={params}
+                            totalItems={pagination.totalItems}
+                            currentPage={pagination.currentPage}
+                            totalPages={pagination.totalPages}
+                            checked={check.isChecked(item.authorCode)}
+                            onCheck={check.onCheck}
+                            onDetail={rowActions.onDetail}
+                            onRoleMove={rowActions.onRoleMove}
+                        />
+                        )
+                    )
+                    : (
+                        <EmptyRow colSpan={7}/>
+                    )
+                }
+                </tbody>
+            </table>
+        </div>
+    );
+}
 
src/admin/feature/role/components/author/AuthorListTableHeader.tsx (added)
+++ src/admin/feature/role/components/author/AuthorListTableHeader.tsx
@@ -0,0 +1,113 @@
+import type {SearchParams} from "../../../../../type/searchParams.ts";
+import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx";
+
+interface AuthorListTableHeaderProps {
+    params: SearchParams;
+    onChange: (params: SearchParams) => void
+    checked: boolean
+    indeterminate: boolean
+    onCheckAll: (checked: boolean) => void
+}
+
+export const AuthorListTableHeader = ({
+                                          params,
+                                          onChange,
+                                          checked,
+                                          indeterminate,
+                                          onCheckAll
+                                      }: AuthorListTableHeaderProps) => {
+    const handleSort = (field: string) => {
+
+        const nextOrder =
+            params.searchSortCnd === field &&
+            params.searchSortOrd === 'ASC'
+                ? 'DESC'
+                : 'ASC';
+
+        onChange({
+            ...params,
+            searchSortCnd: field,
+            searchSortOrd: nextOrder,
+            pageIndex: 1,
+        });
+    };
+
+    const getSortIcon = (field: string) => {
+
+        if (params.searchSortCnd !== field) {
+            return '-';
+        }
+
+        return params.searchSortOrd === 'ASC'
+            ? '▲'
+            : '▼';
+    };
+
+    return (
+        <>
+            <colgroup>
+                <col style={{width: "40px"}}/>
+                <col style={{width: "6%"}}/>
+                <col style={{width: "25%"}}/>
+                <col style={{width: "30%"}}/>
+                <col style={{width: "30%"}}/>
+                <col style={{width: "15%"}}/>
+                <col style={{width: "120px"}}/>
+            </colgroup>
+            <thead>
+            <tr>
+                <th>
+                    <CheckBox
+                        id="authorCheckAll"
+                        name="checkAll"
+                        checked={checked}
+                        indeterminate={indeterminate}
+                        onChange={onCheckAll}
+                    />
+                </th>
+                <th scope="col">번호
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'SORT_NUM' ? 'active' : ''}`}
+                        onClick={() => handleSort('SORT_NUM')}
+                    >
+                        {getSortIcon('SORT_NUM')}
+                    </button>
+                </th>
+                <th scope="col">권한명
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'AUTHOR_NM' ? 'active' : ''}`}
+                        onClick={() => handleSort('AUTHOR_NM')}
+                    >
+                        {getSortIcon('AUTHOR_NM')}
+                    </button>
+                </th>
+                <th scope="col">권한코드
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'AUTHOR_CODE' ? 'active' : ''}`}
+                        onClick={() => handleSort('AUTHOR_CODE')}
+                    >
+                        {getSortIcon('AUTHOR_CODE')}
+                    </button>
+                </th>
+                <th scope="col">설명
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'AUTHOR_DC' ? 'active' : ''}`}
+                        onClick={() => handleSort('AUTHOR_DC')}
+                    >
+                        {getSortIcon('AUTHOR_DC')}
+                    </button>
+                </th>
+                <th scope="col">등록일자
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'AUTHOR_CREAT_DE' ? 'active' : ''}`}
+                        onClick={() => handleSort('AUTHOR_CREAT_DE')}
+                    >
+                        {getSortIcon('AUTHOR_CREAT_DE')}
+                    </button>
+                </th>
+                <th scope="col">롤 정보</th>
+            </tr>
+            </thead>
+        </>
+    );
+}(No newline at end of file)
 
src/admin/feature/role/components/author/AuthorListTableRow.tsx (added)
+++ src/admin/feature/role/components/author/AuthorListTableRow.tsx
@@ -0,0 +1,73 @@
+import type {SearchParams} from "../../../../../type/searchParams.ts";
+import type {AuthorListItem} from "../../type/role.types.ts";
+import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx";
+
+interface AuthorListTableRowProps {
+    item: AuthorListItem
+    index: number
+    searchParams: SearchParams
+    totalItems: number
+    currentPage: number
+    totalPages: number
+    checked: boolean
+    onCheck: (id: string, checked: boolean) => void
+    onDetail: (authorCode: string) => void
+    onRoleMove: (authorCode: string, authorNm: string) => void
+}
+
+
+export const AuthorListTableRow = ({
+                                       item,
+                                       index,
+                                       searchParams,
+                                       totalItems,
+                                       currentPage,
+                                       totalPages,
+                                       checked,
+                                       onCheck,
+                                       onDetail,
+                                       onRoleMove
+                                   }: AuthorListTableRowProps) => {
+    const rowNumber = searchParams.searchSortOrd === 'DESC'
+        ? totalItems - (currentPage - 1) * totalPages - index
+        : (currentPage - 1) * totalPages + (index + 1)
+
+    const authorCode = item.authorCode;
+
+    return (
+        <tr>
+            <td>
+                <CheckBox id={`authorCheckList_${authorCode}`}
+                          name={'checkList'}
+                          value={authorCode}
+                          checked={checked}
+                          onChange={(nextChecked => onCheck(authorCode, nextChecked))}/>
+            </td>
+            <td>
+                {rowNumber}
+            </td>
+            <td>
+                <button onClick={() => onDetail(authorCode)}>
+                    {item.authorNm}
+                </button>
+            </td>
+            <td>
+                {item.authorCode}
+            </td>
+            <td>
+                {item.authorDc}
+            </td>
+            <td>
+                {item.authorCreatDe}
+            </td>
+            <td>
+                <button
+                    name="btnRole"
+                    type="button"
+                    className="btn line lightgray small"
+                    onClick={() => onRoleMove(item.authorCode, item.authorNm)}>이동
+                </button>
+            </td>
+        </tr>
+    );
+};(No newline at end of file)
 
src/admin/feature/role/components/author/role/AuthorRoleListTable.tsx (added)
+++ src/admin/feature/role/components/author/role/AuthorRoleListTable.tsx
@@ -0,0 +1,54 @@
+import {AuthorRoleListTableHeader} from "./AuthorRoleListTableHeader.tsx";
+import type {SearchParams} from "../../../../../../type/searchParams.ts";
+import {EmptyRow} from "../../../../../component/EmptyRow.tsx";
+import {AuthorRoleListTableRow} from "./AuthorRoleListTableRow.tsx";
+import type {AuthorRoleListItem} from "../../../type/role.types.ts";
+import type {ListTableModel, RowActionsModel} from "../../../../../../type/viewModel.ts";
+
+type AuthorRoleListTableProps<T extends SearchParams = SearchParams> =
+    ListTableModel<AuthorRoleListItem, T> &
+    RowActionsModel<{
+        onDetail: (authorCode: string) => void
+        onSelectChange: (roleCode: string, value: string) => void
+    }>;
+
+
+export const AuthorRoleListTable = <T extends SearchParams = SearchParams>({
+                                        items,
+                                        params,
+                                        onChange,
+                                        pagination,
+                                        rowActions,
+                                    }: AuthorRoleListTableProps<T>) => {
+    return (
+        <div className="table table_type_cols">
+            <table>
+                <AuthorRoleListTableHeader
+                    params={params}
+                    onChange={onChange}
+                />
+                <tbody>
+                {items.length > 0 ?
+                    items.map((item, index) => (
+                            <AuthorRoleListTableRow
+                                key={index}
+                                item={item}
+                                index={index}
+                                searchParams={params}
+                                totalItems={pagination.totalItems}
+                                currentPage={pagination.currentPage}
+                                totalPages={pagination.totalPages}
+                                onDetail={rowActions.onDetail}
+                                onChange={rowActions.onSelectChange}
+                            />
+                        )
+                    )
+                    : (
+                        <EmptyRow colSpan={7}/>
+                    )
+                }
+                </tbody>
+            </table>
+        </div>
+    );
+}
 
src/admin/feature/role/components/author/role/AuthorRoleListTableHeader.tsx (added)
+++ src/admin/feature/role/components/author/role/AuthorRoleListTableHeader.tsx
@@ -0,0 +1,100 @@
+import type {SearchParams} from "../../../../../../type/searchParams.ts";
+
+interface AuthorRoleListTableHeaderProps<T extends SearchParams = SearchParams> {
+    params: T;
+    onChange: (params: T) => void
+}
+
+export const AuthorRoleListTableHeader = <T extends SearchParams = SearchParams>({
+                                              params,
+                                              onChange,
+                                          }: AuthorRoleListTableHeaderProps<T>) => {
+    const handleSort = (field: string) => {
+
+        const nextOrder =
+            params.searchSortCnd === field &&
+            params.searchSortOrd === 'ASC'
+                ? 'DESC'
+                : 'ASC';
+
+        onChange({
+            ...params,
+            searchSortCnd: field,
+            searchSortOrd: nextOrder,
+            pageIndex: 1,
+        });
+    };
+
+    const getSortIcon = (field: string) => {
+
+        if (params.searchSortCnd !== field) {
+            return '-';
+        }
+
+        return params.searchSortOrd === 'ASC'
+            ? '▲'
+            : '▼';
+    };
+
+    return (
+        <>
+            <colgroup>
+                <col style={{width: "6%"}}/>
+                <col style={{width: "25%"}}/>
+                <col style={{width: "30%"}}/>
+                <col style={{width: "30%"}}/>
+                <col style={{width: "15%"}}/>
+                <col style={{width: "120px"}}/>
+            </colgroup>
+            <thead>
+            <tr>
+                <th scope="col">번호
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'tempSortNum' ? 'active' : ''}`}
+                        onClick={() => handleSort('tempSortNum')}
+                    >
+                        {getSortIcon('tempSortNum')}
+                    </button>
+                </th>
+                <th scope="col">롤명
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'ROLE_NM' ? 'active' : ''}`}
+                        onClick={() => handleSort('ROLE_NM')}
+                    >
+                        {getSortIcon('ROLE_NM')}
+                    </button>
+                </th>
+                <th scope="col">롤패턴
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'ROLE_PTTRN' ? 'active' : ''}`}
+                        onClick={() => handleSort('ROLE_PTTRN')}
+                    >
+                        {getSortIcon('ROLE_PTTRN')}
+                    </button>
+                </th>
+                <th scope="col">롤설명
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'ROLE_SORT' ? 'active' : ''}`}
+                        onClick={() => handleSort('ROLE_SORT')}
+                    >
+                        {getSortIcon('ROLE_SORT')}
+                    </button>
+                </th>
+                <th scope="col">등록일자
+                    <button
+                        className={`sort sortBtn ${params.searchSortCnd === 'ROLE_DC' ? 'active' : ''}`}
+                        onClick={() => handleSort('ROLE_DC')}
+                    >
+                        {getSortIcon('ROLE_DC')}
+                    </button>
+                </th>
+                <th scope="col">등록여부
+                    <button className={`sort sortBtn ${params.searchSortCnd === 'REG_YN' ? 'active' : ''}`}
+                            onClick={() => handleSort('REG_YN')}
+                    >{getSortIcon('REG_YN')}</button>
+                </th>
+            </tr>
+            </thead>
+        </>
+    );
+}
 
src/admin/feature/role/components/author/role/AuthorRoleListTableRow.tsx (added)
+++ src/admin/feature/role/components/author/role/AuthorRoleListTableRow.tsx
@@ -0,0 +1,61 @@
+import type {SearchParams} from "../../../../../../type/searchParams.ts";
+import type {AuthorRoleListItem} from "../../../type/role.types.ts";
+
+
+interface AuthorRoleListTableRowProps {
+    item: AuthorRoleListItem
+    index: number
+    searchParams: SearchParams
+    totalItems: number
+    currentPage: number
+    totalPages: number
+    onDetail: (roleCode: string) => void
+    onChange: (roleCode: string,value: string) => void
+}
+
+
+export const AuthorRoleListTableRow = ({
+                                           item,
+                                           index,
+                                           searchParams,
+                                           totalItems,
+                                           currentPage,
+                                           totalPages,
+                                           onDetail,
+                                           onChange,
+                                       }: AuthorRoleListTableRowProps) => {
+    const rowNumber = searchParams.searchSortOrd === 'DESC'
+        ? totalItems - (currentPage - 1) * totalPages - index
+        : (currentPage - 1) * totalPages + (index + 1)
+
+    const roleCode = item.roleCode;
+
+
+    return (
+        <tr>
+            <td>
+                {rowNumber}
+            </td>
+            <td>
+                <button onClick={() => onDetail(roleCode)}>
+                    {item.roleNm}
+                </button>
+            </td>
+            <td>
+                {item.rolePtn}
+            </td>
+            <td>
+                {item.roleSort}
+            </td>
+            <td>
+                {item.creatDt}
+            </td>
+            <td>
+                <select name="regYn" className={"select"} value={item.regYn} onChange={(event) => onChange(roleCode, event.target.value)}>
+                    <option value="Y">등록</option>
+                    <option value="N">미등록</option>
+                </select>
+            </td>
+        </tr>
+    );
+};(No newline at end of file)
 
src/admin/feature/role/hook/mutation/useUpdateAuthorRole.ts (added)
+++ src/admin/feature/role/hook/mutation/useUpdateAuthorRole.ts
@@ -0,0 +1,15 @@
+import {useMutation, useQueryClient} from "@tanstack/react-query";
+import {updateAuthorRole} from "../../api/roleApi.ts";
+
+export const useUpdateAuthorRole = () => {
+    const queryClient = useQueryClient();
+
+    return useMutation({
+        mutationFn: updateAuthorRole,
+        onSuccess: () => {
+            queryClient.invalidateQueries({
+                queryKey: ['authorRoleList'],
+            })
+        }
+    })
+}(No newline at end of file)
 
src/admin/feature/role/hook/page/useAuthorGroupListPage.ts (added)
+++ src/admin/feature/role/hook/page/useAuthorGroupListPage.ts
@@ -0,0 +1,0 @@
 
src/admin/feature/role/hook/page/useAuthorListPage.ts (added)
+++ src/admin/feature/role/hook/page/useAuthorListPage.ts
@@ -0,0 +1,153 @@
+import {useNavigate} from "react-router-dom";
+import {useMemo, useState} from "react";
+import {useAuthorList} from "../query/useAuthorList.ts";
+import {useCheckedList} from "../../../../hook/useCheckedList.ts";
+import {ADMIN_AUTHOR_DETAIL_ROUTE, ADMIN_AUTHOR_ROLE_LIST_ROUTE} from "../../../../route/adminRouteMap.ts";
+import type {AuthorListItem, AuthorSearchParams} from "../../type/role.types.ts";
+import type {
+    CheckableTableModel,
+    HeaderModel,
+    ListActionsModel,
+    PaginationModel,
+    RowActionsModel,
+    SearchModel,
+    StatusModel,
+} from "../../../../../type/viewModel.ts";
+
+type AuthorListRowActions = {
+    onDetail: (authorCode: string) => void;
+    onRoleMove: (authorCode: string, authorNm: string) => void;
+};
+
+type AuthorListPageModel = {
+    header: HeaderModel;
+    status: StatusModel;
+    search: SearchModel<AuthorSearchParams>;
+    table: CheckableTableModel<AuthorListItem, AuthorSearchParams> & RowActionsModel<AuthorListRowActions>;
+    actions: ListActionsModel;
+    pagination: PaginationModel;
+};
+
+const initSearchParam: AuthorSearchParams = {
+    pageIndex: 1,
+    pageUnit: 10,
+    searchCnd: "0",
+    searchKeyword: "",
+    searchSortCnd: "",
+    searchSortOrd: ""
+}
+
+const searchOptions = [
+    {value: '0', label: '아이디'},
+    {value: '1', label: '관리자명'},
+]
+
+const pageSizeOptions = [
+    {value: '10', label: '10줄'},
+    {value: '20', label: '20줄'},
+    {value: '30', label: '30줄'},
+]
+
+export const useAuthorListPage = (): AuthorListPageModel => {
+    const [searchParams, setSearchParams] = useState(initSearchParam);
+    const {
+        list,
+        totalItems,
+        totalPages,
+        currentPage,
+        size,
+        isLoading,
+        error
+    } = useAuthorList(searchParams);
+    const authorIds = useMemo(() => list.map((item) => item.authorNm), [list]);
+
+    const {
+        isAllChecked,
+        isPartiallyChecked,
+        isChecked,
+        handleCheck,
+        handleCheckAll,
+    } = useCheckedList(authorIds);
+
+
+    const navigate = useNavigate();
+    const title = '권한별롤관리';
+    const breadcrumb = [
+        {label: '권한관리'},
+        {label: title}
+    ]
+    const homeUrl = '#';
+
+    const handleDetail = (authorCode: string) => {
+        navigate(`${ADMIN_AUTHOR_DETAIL_ROUTE}?authorCode=${authorCode}`);
+    }
+
+    const handleRoleMove = (authorCode: string, authorNm: string) => {
+        navigate(`${ADMIN_AUTHOR_ROLE_LIST_ROUTE}?authorCode=${authorCode}&authorNm=${authorNm}`);
+    }
+
+    const handlePageChange = (pageIndex: number) => {
+        setSearchParams((prev) => ({
+            ...prev,
+            pageIndex,
+        }));
+    };
+
+    const handleDelete = () => {
+    };
+
+    const handleCreate = () => {
+    };
+
+    return {
+        header: {
+            title,
+            breadcrumb,
+            homeUrl,
+        },
+        status: {
+            isLoading,
+            error,
+            successMessage: '데이터 조회가 완료되었습니다.',
+        },
+        search: {
+            totalItems,
+            searchParams,
+            onChange: setSearchParams,
+            searchOptions,
+            pageSizeOptions,
+        },
+        table: {
+            items: list,
+            params: searchParams,
+            onChange: setSearchParams,
+            pagination: {
+                totalItems,
+                currentPage,
+                totalPages,
+            },
+            check: {
+                isAllChecked,
+                isPartiallyChecked,
+                isChecked,
+                onCheck: handleCheck,
+                onCheckAll: handleCheckAll,
+            },
+            rowActions: {
+                onDetail: handleDetail,
+                onRoleMove: handleRoleMove,
+            },
+        },
+        actions: {
+            onDelete: handleDelete,
+            onCreate: handleCreate,
+        },
+        pagination: {
+            totalItems,
+            totalPages,
+            currentPage,
+            size,
+            onPageChange: handlePageChange,
+        },
+    };
+}
 
src/admin/feature/role/hook/page/useAuthorRoleListPage.ts (added)
+++ src/admin/feature/role/hook/page/useAuthorRoleListPage.ts
@@ -0,0 +1,124 @@
+import {useAuthorRoleList} from "../query/useAuthorRoleList.ts";
+import type {AuthorRoleListItem, AuthorRoleSearchParams} from "../../type/role.types.ts";
+import {useState} from "react";
+import {ADMIN_AUTHOR_LIST_ROUTE} from "../../../../route/adminRouteMap.ts";
+import {useUpdateAuthorRole} from "../mutation/useUpdateAuthorRole.ts";
+import {toast} from "react-toastify";
+import type {
+    HeaderModel,
+    ListTableModel,
+    PaginationModel,
+    RowActionsModel,
+    SearchModel,
+    StatusModel
+} from "../../../../../type/viewModel.ts";
+
+type AuthorRoleListRowActions = {
+    onDetail: (authorCode: string) => void;
+    onSelectChange: (roleCode: string, value: string) => void;
+};
+
+type AuthorRoleListPageModel = {
+    header: HeaderModel;
+    status: StatusModel;
+    search: SearchModel<AuthorRoleSearchParams>;
+    table: ListTableModel<AuthorRoleListItem, AuthorRoleSearchParams> & RowActionsModel<AuthorRoleListRowActions>;
+    pagination: PaginationModel;
+};
+
+const initSearchParam: AuthorRoleSearchParams = {
+    pageIndex: 1,
+    pageUnit: 10,
+    searchCnd: "0",
+    searchKeyword: "",
+    searchSortCnd: "",
+    searchSortOrd: "",
+    authorCode: "",
+};
+
+const pageSizeOptions = [
+    {value: '10', label: '10줄'},
+    {value: '20', label: '20줄'},
+    {value: '30', label: '30줄'},
+]
+
+export function useAuthorRoleListPage(authorCode: string): AuthorRoleListPageModel {
+    const [searchParams, setSearchParams] = useState({...initSearchParam, authorCode});
+    const {
+        currentPage,
+        totalPages,
+        list,
+        totalItems,
+        isLoading,
+        size,
+        error
+    } = useAuthorRoleList(searchParams);
+    const {mutateAsync: updateAuthorRole} = useUpdateAuthorRole();
+
+    const title = "권한롤관리";
+    const breadcrumb = [
+        {label: '권한관리'},
+        {label: '권한별롤관리', url: ADMIN_AUTHOR_LIST_ROUTE},
+        {label: '권한롤관리'},
+    ]
+
+    const handleUpdate = async (roleCode: string, value: string) => {
+        await toast.promise(
+            updateAuthorRole({roleCode: roleCode, authorCode: authorCode, regYn: value}),
+            {
+                pending: '변경 처리 중...',
+                success: '변경 완료',
+                error: '변경 실패'
+            }
+        )
+
+    }
+    const handleDetail = (roleCode: string) => {
+        console.log(roleCode)
+    }
+
+    const handlePageChange = (pageIndex: number) => {
+        setSearchParams((prev) => ({
+            ...prev,
+            pageIndex,
+        }));
+    };
+
+    return {
+        header: {
+            title,
+            breadcrumb,
+        },
+        status: {
+            isLoading,
+            error,
+        },
+        search: {
+            totalItems,
+            searchParams,
+            onChange: setSearchParams,
+            pageSizeOptions,
+        },
+        table: {
+            items: list,
+            params: searchParams,
+            onChange: setSearchParams,
+            pagination: {
+                totalItems,
+                currentPage,
+                totalPages,
+            },
+            rowActions: {
+                onDetail: handleDetail,
+                onSelectChange: handleUpdate,
+            },
+        },
+        pagination: {
+            totalItems,
+            totalPages,
+            currentPage,
+            size,
+            onPageChange: handlePageChange,
+        },
+    };
+}
 
src/admin/feature/role/hook/page/useRoleFormPage.ts (added)
+++ src/admin/feature/role/hook/page/useRoleFormPage.ts
@@ -0,0 +1,8 @@
+import {useRoleDetail} from "../query/useRoleDetail.ts";
+
+export const useRoleFormPage = (roleCode: string) => {
+    const mode = roleCode ? "update" : "create";
+    const {data, isLoading, error} = useRoleDetail(roleCode);
+
+    return {mode, data, isLoading, error}
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useAuthorDetail.ts (added)
+++ src/admin/feature/role/hook/query/useAuthorDetail.ts
@@ -0,0 +1,10 @@
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchAuthorDetail} from "../../api/roleApi.ts";
+
+export function useAuthorDetail(authorCode: string) {
+    return useQuery({
+        queryKey: ['authorDetail'],
+        queryFn: () => fetchAuthorDetail(authorCode),
+        placeholderData: keepPreviousData
+    })
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useAuthorGroupList.ts (added)
+++ src/admin/feature/role/hook/query/useAuthorGroupList.ts
@@ -0,0 +1,11 @@
+import type {AuthorGroupSearchParams} from "../../type/role.types.ts";
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchAuthorGroupList} from "../../api/roleApi.ts";
+
+export function useAuthorGroupList(searchParams: AuthorGroupSearchParams) {
+    return useQuery({
+        queryKey: ['authorGroupList', searchParams],
+        queryFn: () => fetchAuthorGroupList(searchParams),
+        placeholderData: keepPreviousData
+    });
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useAuthorList.ts (added)
+++ src/admin/feature/role/hook/query/useAuthorList.ts
@@ -0,0 +1,14 @@
+import type {AuthorSearchParams} from "../../type/role.types.ts";
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchAuthorList} from "../../api/roleApi.ts";
+import {createPageQueryResult} from "../../../../../type/pageResponse.ts";
+
+export function useAuthorList(searchParams: AuthorSearchParams) {
+    const query = useQuery({
+        queryKey: ['authorList', searchParams],
+        queryFn: () => fetchAuthorList(searchParams),
+        placeholderData: keepPreviousData
+    });
+
+    return createPageQueryResult(query);
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useAuthorRoleList.ts (added)
+++ src/admin/feature/role/hook/query/useAuthorRoleList.ts
@@ -0,0 +1,16 @@
+import type {AuthorRoleSearchParams} from "../../type/role.types.ts";
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchAuthorRoleList} from "../../api/roleApi.ts";
+import {createPageQueryResult} from "../../../../../type/pageResponse.ts";
+
+export function useAuthorRoleList(searchParams: AuthorRoleSearchParams) {
+    const query = useQuery({
+        queryKey: ['authorRoleList', searchParams],
+        queryFn: () => fetchAuthorRoleList(searchParams),
+        placeholderData: keepPreviousData
+    });
+
+    console.log(query);
+
+    return createPageQueryResult(query);
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useAuthorRoleMenuDetail.ts (added)
+++ src/admin/feature/role/hook/query/useAuthorRoleMenuDetail.ts
@@ -0,0 +1,10 @@
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchAuthorRoleMenuDetail} from "../../api/roleApi.ts";
+
+export function useAuthorRoleMenuDetail(authorCode: string) {
+    return useQuery({
+        queryKey : ['authorRoleMenuDetail', authorCode] ,
+        queryFn: () => fetchAuthorRoleMenuDetail(authorCode),
+        placeholderData: keepPreviousData
+    })
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useAuthorRoleMenuList.ts (added)
+++ src/admin/feature/role/hook/query/useAuthorRoleMenuList.ts
@@ -0,0 +1,11 @@
+import type {AuthorRoleSearchParams} from "../../type/role.types.ts";
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchAuthorRoleMenuList} from "../../api/roleApi.ts";
+
+export function useAuthorRoleMenuList(searchParams: AuthorRoleSearchParams) {
+    return useQuery({
+        queryKey: ['authorRoleMenuList', searchParams],
+        queryFn: () => fetchAuthorRoleMenuList(searchParams),
+        placeholderData: keepPreviousData
+    });
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useRoleDetail.ts (added)
+++ src/admin/feature/role/hook/query/useRoleDetail.ts
@@ -0,0 +1,11 @@
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import {fetchRoleDetail} from "../../api/roleApi.ts";
+
+export function useRoleDetail(roleCode: string) {
+    return useQuery({
+        queryKey: ['roleDetail', roleCode],
+        queryFn: () => fetchRoleDetail(roleCode),
+        placeholderData: keepPreviousData,
+        enabled: !!roleCode,
+    })
+}(No newline at end of file)
 
src/admin/feature/role/hook/query/useRoleList.ts (added)
+++ src/admin/feature/role/hook/query/useRoleList.ts
@@ -0,0 +1,11 @@
+import {keepPreviousData, useQuery} from "@tanstack/react-query";
+import type {RoleSearchParams} from "../../type/role.types.ts";
+import {fetchRoleList} from "../../api/roleApi.ts";
+
+export function useRoleList(searchParams: RoleSearchParams) {
+    return useQuery({
+        queryKey: ['roleList', searchParams],
+        queryFn: () => fetchRoleList(searchParams),
+        placeholderData: keepPreviousData
+    });
+}(No newline at end of file)
 
src/admin/feature/role/page/AuthorListPage.tsx (added)
+++ src/admin/feature/role/page/AuthorListPage.tsx
@@ -0,0 +1,38 @@
+import {useAuthorListPage} from "../hook/page/useAuthorListPage.ts";
+import {PageHeader} from "../../../component/PageHeader.tsx";
+import {ListSearchForm} from "../../../component/ListSearchForm.tsx";
+import {ActionButtonListGroup} from "../../../component/button/ActionButtonListGroup.tsx";
+import {Pagination} from "../../../component/pagination/Pagination.tsx";
+import {AuthorListTable} from "../components/author/AuthorListTable.tsx";
+import {useLoadingToast} from "../../../hook/useLoadingToast.ts";
+
+export const AuthorListPage = () => {
+    const {
+        header,
+        status,
+        search,
+        table,
+        actions,
+        pagination,
+    } = useAuthorListPage();
+
+    useLoadingToast(status);
+
+    return (
+        <>
+            <PageHeader {...header}/>
+            <ListSearchForm
+                {...search}
+                totalLabel="총 게시물"
+            />
+
+            <AuthorListTable {...table}/>
+
+            <ActionButtonListGroup
+                {...actions}
+                deleteLabel="삭제"
+            />
+            <Pagination {...pagination}/>
+        </>
+    )
+}
 
src/admin/feature/role/page/AuthorRoleFormPage.tsx (added)
+++ src/admin/feature/role/page/AuthorRoleFormPage.tsx
@@ -0,0 +1,7 @@
+export const AuthorRoleFormPage = () => {
+    return (
+
+        <>
+        </>
+    );
+}(No newline at end of file)
 
src/admin/feature/role/page/AuthorRoleListPage.tsx (added)
+++ src/admin/feature/role/page/AuthorRoleListPage.tsx
@@ -0,0 +1,34 @@
+import {useAuthorRoleListPage} from "../hook/page/useAuthorRoleListPage.ts";
+import {useSearchParams} from "react-router-dom";
+import {PageHeader} from "../../../component/PageHeader.tsx";
+import {ListSearchForm} from "../../../component/ListSearchForm.tsx";
+import {Pagination} from "../../../component/pagination/Pagination.tsx";
+import {AuthorRoleListTable} from "../components/author/role/AuthorRoleListTable.tsx";
+import {useLoadingToast} from "../../../hook/useLoadingToast.ts";
+
+export const AuthorRoleListPage = () => {
+    const [urlParams] = useSearchParams();
+    const authorCode = urlParams.get("authorCode") || "";
+    const authorNm = urlParams.get("authorNm") || "";
+    const {
+        header,
+        status,
+        search,
+        table,
+        pagination,
+    } = useAuthorRoleListPage(authorCode);
+
+    useLoadingToast(status);
+
+    return (
+        <>
+            <PageHeader {...header}/>
+            <ListSearchForm
+                {...search}
+                totalLabel={`(${authorCode}:${authorNm}) 총 건수`}
+            />
+            <AuthorRoleListTable {...table}/>
+            <Pagination {...pagination}/>
+        </>
+    );
+}
 
src/admin/feature/role/type/role.types.ts (added)
+++ src/admin/feature/role/type/role.types.ts
@@ -0,0 +1,51 @@
+import type {SearchParams} from "../../../../type/searchParams.ts";
+
+export interface AuthorSearchParams extends SearchParams {
+}
+
+export interface AuthorRoleSearchParams extends SearchParams {
+    authorCode: string;
+}
+
+export interface AuthorRoleMenuSearchParams extends SearchParams {
+}
+
+export interface AuthorGroupSearchParams extends SearchParams {
+}
+
+export interface RoleSearchParams extends SearchParams {
+}
+
+export interface UpdateAuthorRoleParams {
+    authorCode: string,
+    roleCode: string,
+    regYn: string
+}
+
+export interface RoleDetailResponse {
+    roleCode: string,
+    roleNm: string,
+    rolePtn: string,
+    roleDc: string,
+    roleSort: string,
+    roleCreatDe: string,
+}
+
+
+export interface AuthorListItem {
+    authorCode: string;
+    authorNm: string;
+    authorDc: string;
+    authorCreatDe: string;
+}
+
+export interface AuthorRoleListItem {
+    regYn: string;
+    roleNm: string;
+    roleCode: string;
+    rolePtn: string;
+    roleSort: string;
+    roleDc: string;
+    creatDt: string;
+    authorCode: string
+}(No newline at end of file)
src/admin/hook/useLoadingToast.ts
--- src/admin/hook/useLoadingToast.ts
+++ src/admin/hook/useLoadingToast.ts
@@ -1,11 +1,9 @@
 import {useEffect, useRef} from "react";
 import {type Id, toast} from "react-toastify";
+import type {StatusModel} from "../../type/viewModel.ts";
 
-type UseLoadingToastProps = {
-    isLoading: boolean;
-    error?: Error | null;
+type UseLoadingToastProps = StatusModel & {
     loadingMessage?: string;
-    successMessage?: string;
 }
 
 export const useLoadingToast = ({
@@ -26,10 +24,10 @@
             toastId.current = null;
 
             if (error) {
-                toast.error(error.message);
+                toast.error(error instanceof Error ? error.message : String(error));
             } else {
                 toast.success(successMessage);
             }
         }
     }, [isLoading, error, loadingMessage, successMessage]);
-}
(No newline at end of file)
+}
src/admin/route/AdminRoute.tsx
--- src/admin/route/AdminRoute.tsx
+++ src/admin/route/AdminRoute.tsx
@@ -1,8 +1,16 @@
 import {Navigate, Route, Routes} from "react-router-dom";
 import {BoardListPage} from "../feature/board/page/BoardListPage.tsx";
-import {ADMIN_BBS_MASTER_ROUTE} from "./adminRouteMap.ts";
+import {
+    ADMIN_AUTHOR_DETAIL_ROUTE,
+    ADMIN_AUTHOR_LIST_ROUTE,
+    ADMIN_AUTHOR_ROLE_LIST_ROUTE, ADMIN_BBS_ARTICLE_FORM_ROUTE,
+    ADMIN_BBS_MASTER_ROUTE, ADMIN_ROLE_FORM_ROUTE
+} from "./adminRouteMap.ts";
 import {BoardArticleListPage} from "../feature/board/page/BoardArticleListPage.tsx";
 import {BoardFormPage} from "../feature/board/page/BoardFormPage.tsx";
+import {AuthorListPage} from "../feature/role/page/AuthorListPage.tsx";
+import {AuthorRoleListPage} from "../feature/role/page/AuthorRoleListPage.tsx";
+import {AuthorRoleFormPage} from "../feature/role/page/AuthorRoleFormPage.tsx";
 
 const ReadyPage = () => {
     return <div>Preparing menu.</div>;
@@ -14,8 +22,13 @@
             <Route path="/" element={<Navigate to={ADMIN_BBS_MASTER_ROUTE} replace/>}/>
             <Route path={ADMIN_BBS_MASTER_ROUTE} element={<BoardListPage/>}/>
             <Route path={`/admin/cop/bbs/article/:bbsId`} element={<BoardArticleListPage/>}/>
-            <Route path={`/admin/cop/bbs/detail/:bbsId`} element={<BoardFormPage/>}/>
-            <Route path={`/admin/cop/bbs/detail`} element={<BoardFormPage/>}/>
+            <Route path={`${ADMIN_BBS_ARTICLE_FORM_ROUTE}:bbsId`} element={<BoardFormPage/>}/>
+            <Route path={ADMIN_BBS_ARTICLE_FORM_ROUTE} element={<BoardFormPage/>}/>
+
+            <Route path={ADMIN_AUTHOR_LIST_ROUTE} element={<AuthorListPage/>}/>
+            <Route path={ADMIN_AUTHOR_DETAIL_ROUTE} element={<ReadyPage/>}/>
+            <Route path={ADMIN_AUTHOR_ROLE_LIST_ROUTE} element={<AuthorRoleListPage/>}/>
+            <Route path={ADMIN_ROLE_FORM_ROUTE} element={<AuthorRoleFormPage/>}/>
             <Route path="*" element={<ReadyPage/>}/>
         </Routes>
     );
src/admin/route/adminRouteMap.ts
--- src/admin/route/adminRouteMap.ts
+++ src/admin/route/adminRouteMap.ts
@@ -2,9 +2,16 @@
 
 export const ADMIN_BBS_MASTER_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/SelectBBSMasterInfs.do`;
 export const ADMIN_BBS_ARTICLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/article/`;
-export const ADMIN_BBS_ARTICLE_DETAIL_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/detail/`;
+export const ADMIN_BBS_ARTICLE_FORM_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/detail/`;
 export const ADMIN_MENU_CREATE_TREE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/mnu/mcm/EgovMenuCreatSelectJtree.do`;
+
 export const ADMIN_AUTHOR_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/EgovAuthorList.do`;
+export const ADMIN_AUTHOR_DETAIL_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/authorDetail.do`;
+export const ADMIN_AUTHOR_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/authorRoleList.do`;
+
+export const ADMIN_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/EgovRoleList.do`;
+export const ADMIN_ROLE_FORM_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/detail.do`;
+
 export const ADMIN_MAIN_ZONE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/mainZoneList.do`;
 export const ADMIN_CONTENT_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/cnt/contentList.do`;
 export const ADMIN_MAIN_PAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/cmm/main/mainPage.do`;
@@ -17,7 +24,7 @@
 export const ADMIN_AUTHOR_GROUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rgm/EgovAuthorGroupList.do`;
 export const ADMIN_POPUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/egovPopupList.do`;
 export const ADMIN_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectWebLogList.do`;
-export const ADMIN_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/EgovRoleList.do`;
+
 export const ADMIN_BANNER_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/bnr/selectBannerList.do`;
 export const ADMIN_USER_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/NSelectWebLogList.do`;
 export const ADMIN_LOGIN_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectLoginLogList.do`;
src/type/pageResponse.ts
--- src/type/pageResponse.ts
+++ src/type/pageResponse.ts
@@ -1,3 +1,5 @@
+import type {UseQueryResult} from "@tanstack/react-query";
+
 export interface PageResponse<T, E = Record<string, unknown>> {
     list: T[];
     extraData: E;
@@ -5,4 +7,16 @@
     totalPages: number;
     currentPage: number;
     size: number;
-}
(No newline at end of file)
+}
+
+export const createPageQueryResult = <T>(
+    query: UseQueryResult<PageResponse<T>>
+) => ({
+    list: query.data?.list ?? [],
+    totalItems: query.data?.totalItems ?? 0,
+    totalPages: query.data?.totalPages ?? 0,
+    currentPage: query.data?.currentPage ?? 0,
+    size: query.data?.size ?? 0,
+    isLoading: query.isLoading,
+    error: query.error,
+});
(No newline at end of file)
 
src/type/viewModel.ts (added)
+++ src/type/viewModel.ts
@@ -0,0 +1,86 @@
+import type {SearchParams} from "./searchParams.ts";
+
+export type BreadcrumbItem = {
+    label: string;
+    url?: string;
+};
+
+export type Option = {
+    value: string;
+    label: string;
+};
+
+export type HeaderModel = {
+    title: string;
+    breadcrumb: BreadcrumbItem[];
+    homeUrl?: string;
+};
+
+export type StatusModel = {
+    isLoading: boolean;
+    error: unknown;
+    successMessage?: string;
+};
+
+export type SearchModel<TSearchParams extends SearchParams> = {
+    totalItems: number;
+    searchParams: TSearchParams;
+    onChange: (params: TSearchParams) => void;
+    searchOptions?: Option[];
+    pageSizeOptions?: Option[];
+};
+
+export type PaginationModel = {
+    totalItems: number;
+    totalPages: number;
+    currentPage: number;
+    size: number;
+    onPageChange: (pageIndex: number) => void;
+};
+
+export type TableCheckModel<TId extends string | number = string> = {
+    isAllChecked: boolean;
+    isPartiallyChecked: boolean;
+    isChecked: (id: TId) => boolean;
+    onCheck: (id: TId, checked: boolean) => void;
+    onCheckAll: (checked: boolean) => void;
+};
+
+export type TablePaginationModel = Pick<
+    PaginationModel,
+    "totalItems" | "currentPage" | "totalPages"
+>;
+
+export type ListTableModel<TItem, TSearchParams extends SearchParams> = {
+    items: TItem[];
+    params: TSearchParams;
+    onChange: (params: TSearchParams) => void;
+    pagination: TablePaginationModel;
+};
+
+export type CheckableTableModel<
+    TItem,
+    TSearchParams extends SearchParams,
+    TId extends string | number = string
+> = ListTableModel<TItem, TSearchParams> & {
+    check: TableCheckModel<TId>;
+};
+
+export type RowActionsModel<TActions> = {
+    rowActions: TActions;
+};
+
+export type ListActionsModel = {
+    disabled?: boolean;
+    onDelete?: () => void | Promise<void>;
+    onCreate?: () => void;
+};
+
+export type FormActionsModel<TMode extends string = "create" | "update"> = {
+    mode: TMode;
+    disabled?: boolean;
+    onCreate?: () => void | Promise<void>;
+    onUpdate?: () => void | Promise<void>;
+    onDelete?: () => void | Promise<void>;
+    onList?: () => void;
+};
Add a comment
List