--- src/admin/feature/board/master/hook/page/useBoardFormPage.ts
+++ src/admin/feature/board/master/hook/page/useBoardFormPage.ts
... | ... | @@ -8,9 +8,7 @@ |
| 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";
|
|
| 12 |
- |
|
| 13 |
-export type BoardFormMode = 'create' | 'update'; |
|
| 11 |
+import type {FormActionsModel, FormMode, HeaderModel, StatusModel} from "../../../../../../type/viewModel.ts";
|
|
| 14 | 12 |
|
| 15 | 13 |
type BoardFormPageModel = {
|
| 16 | 14 |
header: HeaderModel; |
... | ... | @@ -19,7 +17,7 @@ |
| 19 | 17 |
form: BoardFormItem; |
| 20 | 18 |
onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => void; |
| 21 | 19 |
}; |
| 22 |
- actions: FormActionsModel<BoardFormMode>; |
|
| 20 |
+ actions: FormActionsModel<FormMode>; |
|
| 23 | 21 |
}; |
| 24 | 22 |
|
| 25 | 23 |
const initBoardFormData: BoardFormItem = {
|
... | ... | @@ -46,7 +44,7 @@ |
| 46 | 44 |
|
| 47 | 45 |
export const useBoardFormPage = (bbsId: string): BoardFormPageModel => {
|
| 48 | 46 |
const navigate = useNavigate(); |
| 49 |
- const mode: BoardFormMode = bbsId ? 'update' : 'create'; |
|
| 47 |
+ const mode: FormMode = bbsId ? 'update' : 'create'; |
|
| 50 | 48 |
const [formDraft, setFormDraft] = useState<Partial<BoardFormItem>>({});
|
| 51 | 49 |
|
| 52 | 50 |
const {data, isLoading, error} = useBoardDetail(bbsId, {enabled: !!bbsId});
|
... | ... | @@ -106,7 +104,7 @@ |
| 106 | 104 |
} |
| 107 | 105 |
); |
| 108 | 106 |
|
| 109 |
- navigate(ADMIN_BBS_MASTER_ROUTE); |
|
| 107 |
+ handleList(); |
|
| 110 | 108 |
}; |
| 111 | 109 |
|
| 112 | 110 |
const handleUpdate = async () => {
|
... | ... | @@ -123,7 +121,7 @@ |
| 123 | 121 |
} |
| 124 | 122 |
); |
| 125 | 123 |
|
| 126 |
- navigate(ADMIN_BBS_MASTER_ROUTE); |
|
| 124 |
+ handleList(); |
|
| 127 | 125 |
}; |
| 128 | 126 |
|
| 129 | 127 |
const handleDelete = async () => {
|
... | ... | @@ -140,7 +138,7 @@ |
| 140 | 138 |
} |
| 141 | 139 |
); |
| 142 | 140 |
|
| 143 |
- navigate(ADMIN_BBS_MASTER_ROUTE); |
|
| 141 |
+ handleList(); |
|
| 144 | 142 |
}; |
| 145 | 143 |
|
| 146 | 144 |
const handleList = () => {
|
--- src/admin/feature/role/role/api/roleApi.ts
+++ src/admin/feature/role/role/api/roleApi.ts
... | ... | @@ -1,11 +1,33 @@ |
| 1 | 1 |
import {apiClient} from "../../../../../api/apiClient.ts";
|
| 2 | 2 |
import type {PageResponse} from "../../../../../type/pageResponse.ts";
|
| 3 |
-import type {RoleDetailResponse, RoleSearchParams} from "../type/role.types.ts";
|
|
| 3 |
+import type {
|
|
| 4 |
+ DeleteRoleListItem, |
|
| 5 |
+ RoleDetailResponse, |
|
| 6 |
+ RoleFormItem, |
|
| 7 |
+ RoleListItem, |
|
| 8 |
+ RoleSearchParams |
|
| 9 |
+} from "../type/role.types.ts"; |
|
| 4 | 10 |
|
| 5 | 11 |
export async function fetchRoleList(params: RoleSearchParams) {
|
| 6 |
- return apiClient.get<PageResponse<unknown>>(`/sec/rmt/list.do`, params); |
|
| 12 |
+ return apiClient.get<PageResponse<RoleListItem>>(`/sec/rmt/list.do`, params); |
|
| 7 | 13 |
} |
| 8 | 14 |
|
| 9 | 15 |
export async function fetchRoleDetail(roleCode: string) {
|
| 10 | 16 |
return apiClient.get<RoleDetailResponse>(`/sec/rmt/detail.do?roleCode=${roleCode}`);
|
| 11 | 17 |
} |
| 18 |
+ |
|
| 19 |
+export async function fetchRoleDeleteBatch(params: DeleteRoleListItem[]) {
|
|
| 20 |
+ return apiClient.post(`/sec/rmt/EgovRoleDeleteBatch.do`, params); |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+export async function fetchCreateRole(params: RoleFormItem) {
|
|
| 24 |
+ return apiClient.post(`/sec/rmt/EgovRoleInsert.do`, params); |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+export async function fetchUpdateRole(params: RoleFormItem) {
|
|
| 28 |
+ return apiClient.post(`/sec/rmt/EgovRoleUpdate.do`, params) |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+export async function fetchDeleteRole(roleCode: string) {
|
|
| 32 |
+ return apiClient.post(`/sec/rmt/EgovRoleDelete.do?roleCode=${roleCode}`);
|
|
| 33 |
+} |
+++ src/admin/feature/role/role/component/RoleFormTable.tsx
... | ... | @@ -0,0 +1,87 @@ |
| 1 | +import type {RoleFormItem} from "../type/role.types.ts"; | |
| 2 | +import type {ChangeEvent} from "react"; | |
| 3 | +import type {FormMode} from "../../../../../type/viewModel.ts"; | |
| 4 | + | |
| 5 | +type RoleFormTableProps = { | |
| 6 | + form: RoleFormItem; | |
| 7 | + onChange: (event: ChangeEvent<HTMLInputElement>) => void; | |
| 8 | + mode: FormMode; | |
| 9 | +} | |
| 10 | + | |
| 11 | +export const RoleFormTable = ({form, onChange, mode}: RoleFormTableProps) => { | |
| 12 | + return ( | |
| 13 | + <div className="table table_type_rows"> | |
| 14 | + <table> | |
| 15 | + <colgroup> | |
| 16 | + <col style={{width: "200px"}}/> | |
| 17 | + <col style={{width: "auto"}}/> | |
| 18 | + </colgroup> | |
| 19 | + | |
| 20 | + <tbody> | |
| 21 | + {mode === "update" ? | |
| 22 | + <tr> | |
| 23 | + <th> | |
| 24 | + <span className="required">*</span>롤코드 | |
| 25 | + </th> | |
| 26 | + <td> | |
| 27 | + <input | |
| 28 | + name="roleCode" | |
| 29 | + id="roleCode" | |
| 30 | + className="input" | |
| 31 | + type="text" | |
| 32 | + value={form.roleCode} | |
| 33 | + readOnly={true} | |
| 34 | + title="롤 코드" | |
| 35 | + /> | |
| 36 | + </td> | |
| 37 | + </tr> : null | |
| 38 | + } | |
| 39 | + | |
| 40 | + <tr> | |
| 41 | + <th> | |
| 42 | + <span className="required">*</span>롤명 | |
| 43 | + </th> | |
| 44 | + <td> | |
| 45 | + <input name="roleNm" id="roleNm" className="input" type="text" value={form.roleNm} title="롤명" | |
| 46 | + onChange={onChange}/> | |
| 47 | + </td> | |
| 48 | + </tr> | |
| 49 | + <tr> | |
| 50 | + <th><span className="required">*</span>롤패턴</th> | |
| 51 | + <td> | |
| 52 | + <input name="rolePtn" id="rolePtn" className="input" type="text" value={form.rolePtn} | |
| 53 | + title="롤패턴" | |
| 54 | + onChange={onChange}/> | |
| 55 | + </td> | |
| 56 | + </tr> | |
| 57 | + <tr> | |
| 58 | + <th><span className="required">*</span>설명</th> | |
| 59 | + <td> | |
| 60 | + <input name="roleDc" id="roleDc" className="input" type="text" value={form.roleDc} title="설명" | |
| 61 | + onChange={onChange}/> | |
| 62 | + </td> | |
| 63 | + </tr> | |
| 64 | + <tr> | |
| 65 | + <th>롤순서</th> | |
| 66 | + <td> | |
| 67 | + <input name="roleSort" id="roleSort" className="input" type="text" value={form.roleSort} | |
| 68 | + onChange={onChange} | |
| 69 | + title="롤sort"/> | |
| 70 | + </td> | |
| 71 | + </tr> | |
| 72 | + | |
| 73 | + {mode === "update" ? | |
| 74 | + <tr> | |
| 75 | + <th>등록일자</th> | |
| 76 | + <td> | |
| 77 | + <input name="roleCreatDe" id="roleCreatDe" className="input" type="text" | |
| 78 | + value={form.roleCreatDe} title="등록일자" readOnly={true}/> | |
| 79 | + </td> | |
| 80 | + </tr> | |
| 81 | + : null | |
| 82 | + } | |
| 83 | + </tbody> | |
| 84 | + </table> | |
| 85 | + </div> | |
| 86 | + ); | |
| 87 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/component/RoleListTable.tsx
... | ... | @@ -0,0 +1,45 @@ |
| 1 | +import type {SearchParams} from "../../../../../type/searchParams.ts"; | |
| 2 | +import type {RoleListItem} from "../type/role.types.ts"; | |
| 3 | +import type {CheckableTableModel, RowActionsModel} from "../../../../../type/viewModel.ts"; | |
| 4 | +import {EmptyRow} from "../../../../component/EmptyRow.tsx"; | |
| 5 | +import {RoleListTableHeader} from "./RoleListTableHeader.tsx"; | |
| 6 | +import {RoleListTableRow} from "./RoleListTableRow.tsx"; | |
| 7 | + | |
| 8 | +type RoleListTableProps = | |
| 9 | + CheckableTableModel<RoleListItem, SearchParams> & | |
| 10 | + RowActionsModel<{ | |
| 11 | + onDetail: (roleCode: string) => void; | |
| 12 | + }> | |
| 13 | + | |
| 14 | +export const RoleListTable = ({items, params, onChange, pagination, check, rowActions}: RoleListTableProps) => { | |
| 15 | + | |
| 16 | + return ( | |
| 17 | + <div className="table table_type_cols"> | |
| 18 | + <table> | |
| 19 | + <RoleListTableHeader | |
| 20 | + params={params} | |
| 21 | + onChange={onChange} | |
| 22 | + checked={check.isAllChecked} | |
| 23 | + indeterminate={check.isPartiallyChecked} | |
| 24 | + onCheckAll={check.onCheckAll} | |
| 25 | + /> | |
| 26 | + <tbody> | |
| 27 | + {items.length > 0 | |
| 28 | + ? items.map((item, index) => ( | |
| 29 | + <RoleListTableRow | |
| 30 | + key={index} | |
| 31 | + item={item} | |
| 32 | + index={index} | |
| 33 | + searchParams={params} | |
| 34 | + checked={check.isChecked(item.roleCode)} | |
| 35 | + onCheck={check.onCheck} | |
| 36 | + {...pagination} | |
| 37 | + {...rowActions} | |
| 38 | + /> | |
| 39 | + )) | |
| 40 | + : (<EmptyRow colSpan={7}/>)} | |
| 41 | + </tbody> | |
| 42 | + </table> | |
| 43 | + </div> | |
| 44 | + ) | |
| 45 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/component/RoleListTableHeader.tsx
... | ... | @@ -0,0 +1,99 @@ |
| 1 | +import type {SearchParams} from "../../../../../type/searchParams.ts"; | |
| 2 | +import {useTableSort} from "../../../../hook/useTableSort.ts"; | |
| 3 | +import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx"; | |
| 4 | +import {SortableHeaderCell} from "../../../../component/table/SortableHeaderCell.tsx"; | |
| 5 | + | |
| 6 | +interface RoleListTableHeaderProps { | |
| 7 | + params: SearchParams; | |
| 8 | + onChange: (params: SearchParams) => void | |
| 9 | + checked: boolean | |
| 10 | + indeterminate: boolean | |
| 11 | + onCheckAll: (checked: boolean) => void | |
| 12 | +} | |
| 13 | + | |
| 14 | +export const RoleListTableHeader = ({ | |
| 15 | + params, | |
| 16 | + onChange, | |
| 17 | + checked, | |
| 18 | + indeterminate, | |
| 19 | + onCheckAll | |
| 20 | + }: RoleListTableHeaderProps) => { | |
| 21 | + const {handleSort, getSortIcon, isSorted} = useTableSort(params, onChange); | |
| 22 | + | |
| 23 | + return ( | |
| 24 | + <> | |
| 25 | + <colgroup> | |
| 26 | + <col style={{width: "40px"}}/> | |
| 27 | + <col style={{width: "8%"}}/> | |
| 28 | + <col style={{width: "19%"}}/> | |
| 29 | + <col style={{width: "34%"}}/> | |
| 30 | + <col style={{width: "10%"}}/> | |
| 31 | + <col style={{width: "25%"}}/> | |
| 32 | + <col style={{width: "10%"}}/> | |
| 33 | + </colgroup> | |
| 34 | + | |
| 35 | + <thead> | |
| 36 | + <tr> | |
| 37 | + <th> | |
| 38 | + <CheckBox | |
| 39 | + id={"roleCheckAll"} | |
| 40 | + name={"checkAll"} | |
| 41 | + checked={checked} | |
| 42 | + onChange={onCheckAll} | |
| 43 | + indeterminate={indeterminate} | |
| 44 | + /> | |
| 45 | + </th> | |
| 46 | + <SortableHeaderCell | |
| 47 | + field={"SORT_TEMP_NO"} | |
| 48 | + active={isSorted("SORT_TEMP_NO")} | |
| 49 | + icon={getSortIcon("SORT_TEMP_NO")} | |
| 50 | + onSort={handleSort} | |
| 51 | + > | |
| 52 | + 번호 | |
| 53 | + </SortableHeaderCell> | |
| 54 | + <SortableHeaderCell | |
| 55 | + field={"ROLE_DC"} | |
| 56 | + active={isSorted("ROLE_DC")} | |
| 57 | + icon={getSortIcon("ROLE_DC")} | |
| 58 | + onSort={handleSort} | |
| 59 | + > | |
| 60 | + 롤설명 | |
| 61 | + </SortableHeaderCell> | |
| 62 | + <SortableHeaderCell | |
| 63 | + field={"ROLE_PTTRN"} | |
| 64 | + active={isSorted("ROLE_PTTRN")} | |
| 65 | + icon={getSortIcon("ROLE_PTTRN")} | |
| 66 | + onSort={handleSort} | |
| 67 | + > | |
| 68 | + 롤패턴 | |
| 69 | + </SortableHeaderCell> | |
| 70 | + <SortableHeaderCell | |
| 71 | + field={"ROLE_SORT"} | |
| 72 | + active={isSorted("ROLE_SORT")} | |
| 73 | + icon={getSortIcon("ROLE_SORT")} | |
| 74 | + onSort={handleSort} | |
| 75 | + > | |
| 76 | + 롤순서 | |
| 77 | + </SortableHeaderCell> | |
| 78 | + <SortableHeaderCell | |
| 79 | + field={"ROLE_NM"} | |
| 80 | + active={isSorted("ROLE_NM")} | |
| 81 | + icon={getSortIcon("ROLE_NM")} | |
| 82 | + onSort={handleSort} | |
| 83 | + > | |
| 84 | + 롤명 | |
| 85 | + </SortableHeaderCell> | |
| 86 | + <SortableHeaderCell | |
| 87 | + field={"ROLE_CREAT_DE"} | |
| 88 | + active={isSorted("ROLE_CREAT_DE")} | |
| 89 | + icon={getSortIcon("ROLE_CREAT_DE")} | |
| 90 | + onSort={handleSort} | |
| 91 | + > | |
| 92 | + 등록일자 | |
| 93 | + </SortableHeaderCell> | |
| 94 | + </tr> | |
| 95 | + </thead> | |
| 96 | + </> | |
| 97 | + ) | |
| 98 | + | |
| 99 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/component/RoleListTableRow.tsx
... | ... | @@ -0,0 +1,59 @@ |
| 1 | +import {getTableRowNumber} from "../../../../component/table/getTableRowNumber.ts"; | |
| 2 | +import type {SearchParams} from "../../../../../type/searchParams.ts"; | |
| 3 | +import type {RoleListItem} from "../type/role.types.ts"; | |
| 4 | +import {CheckBox} from "../../../../component/checkbox/CheckBox.tsx"; | |
| 5 | + | |
| 6 | +interface RoleListTableRowProps { | |
| 7 | + item: RoleListItem | |
| 8 | + index: number | |
| 9 | + searchParams: SearchParams | |
| 10 | + totalItems: number | |
| 11 | + currentPage: number | |
| 12 | + checked: boolean | |
| 13 | + onCheck: (id: string, checked: boolean) => void | |
| 14 | + onDetail: (roleCode: string) => void | |
| 15 | +} | |
| 16 | + | |
| 17 | +export const RoleListTableRow = ({ | |
| 18 | + item, | |
| 19 | + index, | |
| 20 | + searchParams, | |
| 21 | + totalItems, | |
| 22 | + currentPage, | |
| 23 | + checked, | |
| 24 | + onCheck, | |
| 25 | + onDetail | |
| 26 | + }: RoleListTableRowProps) => { | |
| 27 | + const rowNumber = getTableRowNumber({ | |
| 28 | + searchParams, | |
| 29 | + totalItems, | |
| 30 | + currentPage, | |
| 31 | + index | |
| 32 | + }); | |
| 33 | + const roleCode = item.roleCode; | |
| 34 | + | |
| 35 | + return ( | |
| 36 | + <tr> | |
| 37 | + <td> | |
| 38 | + <CheckBox id={`roleCheckList_${roleCode}`} | |
| 39 | + name={`checkList`} | |
| 40 | + value={roleCode} | |
| 41 | + checked={checked} | |
| 42 | + onChange={(nextChecked) => onCheck(roleCode, nextChecked)} | |
| 43 | + /> | |
| 44 | + </td> | |
| 45 | + <td> | |
| 46 | + {rowNumber} | |
| 47 | + </td> | |
| 48 | + <td> | |
| 49 | + <button onClick={() => onDetail(roleCode)}> | |
| 50 | + {item.roleDc} | |
| 51 | + </button> | |
| 52 | + </td> | |
| 53 | + <td>{item.roleSort}</td> | |
| 54 | + <td>{item.rolePtn}</td> | |
| 55 | + <td>{item.roleNm}</td> | |
| 56 | + <td>{item.roleCreatDe}</td> | |
| 57 | + </tr> | |
| 58 | + ) | |
| 59 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/hook/mutation/useCreateRole.ts
... | ... | @@ -0,0 +1,15 @@ |
| 1 | +import {useMutation, useQueryClient} from "@tanstack/react-query"; | |
| 2 | +import {fetchCreateRole} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export const useCreateRole = () => { | |
| 5 | + const queryClient = useQueryClient(); | |
| 6 | + | |
| 7 | + return useMutation({ | |
| 8 | + mutationFn: fetchCreateRole, | |
| 9 | + onSuccess: () => { | |
| 10 | + queryClient.invalidateQueries({ | |
| 11 | + queryKey: ['roleDetail'] | |
| 12 | + }); | |
| 13 | + } | |
| 14 | + }) | |
| 15 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/hook/mutation/useDeleteRole.ts
... | ... | @@ -0,0 +1,15 @@ |
| 1 | +import {useMutation, useQueryClient} from "@tanstack/react-query"; | |
| 2 | +import {fetchDeleteRole} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export const useDeleteRole = () => { | |
| 5 | + const queryClient = useQueryClient(); | |
| 6 | + | |
| 7 | + return useMutation({ | |
| 8 | + mutationFn: fetchDeleteRole, | |
| 9 | + onSuccess: () => { | |
| 10 | + queryClient.invalidateQueries({ | |
| 11 | + queryKey: ['roleDetail'] | |
| 12 | + }); | |
| 13 | + } | |
| 14 | + }) | |
| 15 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/hook/mutation/useDeleteRoleBatch.ts
... | ... | @@ -0,0 +1,15 @@ |
| 1 | +import {useMutation, useQueryClient} from "@tanstack/react-query"; | |
| 2 | +import {fetchRoleDeleteBatch} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export const useDeleteRoleBatch = () => { | |
| 5 | + const queryClient = useQueryClient(); | |
| 6 | + | |
| 7 | + return useMutation({ | |
| 8 | + mutationFn: fetchRoleDeleteBatch, | |
| 9 | + onSuccess: () => { | |
| 10 | + queryClient.invalidateQueries({ | |
| 11 | + queryKey: ['roleList'] | |
| 12 | + }) | |
| 13 | + } | |
| 14 | + }) | |
| 15 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/hook/mutation/useUpdateRole.ts
... | ... | @@ -0,0 +1,15 @@ |
| 1 | +import {useMutation, useQueryClient} from "@tanstack/react-query"; | |
| 2 | +import {fetchUpdateRole} from "../../api/roleApi.ts"; | |
| 3 | + | |
| 4 | +export const useUpdateRole = () => { | |
| 5 | + const queryClient = useQueryClient(); | |
| 6 | + | |
| 7 | + return useMutation({ | |
| 8 | + mutationFn: fetchUpdateRole, | |
| 9 | + onSuccess: () => { | |
| 10 | + queryClient.invalidateQueries({ | |
| 11 | + queryKey: ['roleDetail'] | |
| 12 | + }); | |
| 13 | + } | |
| 14 | + }) | |
| 15 | +}(No newline at end of file) |
--- src/admin/feature/role/role/hook/page/useRoleFormPage.ts
+++ src/admin/feature/role/role/hook/page/useRoleFormPage.ts
... | ... | @@ -1,8 +1,158 @@ |
| 1 | 1 |
import {useRoleDetail} from "../query/useRoleDetail.ts";
|
| 2 |
+import {ADMIN_ROLE_LIST_ROUTE} from "../../../../../route/adminRouteMap.ts";
|
|
| 3 |
+import {type ChangeEvent, useMemo, useState} from "react";
|
|
| 4 |
+import {toast} from "react-toastify";
|
|
| 5 |
+import type {RoleDetailResponse, RoleFormItem} from "../../type/role.types.ts";
|
|
| 6 |
+import type {FormActionsModel, FormMode, HeaderModel, StatusModel} from "../../../../../../type/viewModel.ts";
|
|
| 7 |
+import {useCreateRole} from "../mutation/useCreateRole.ts";
|
|
| 8 |
+import {useUpdateRole} from "../mutation/useUpdateRole.ts";
|
|
| 9 |
+import {useDeleteRole} from "../mutation/useDeleteRole.ts";
|
|
| 10 |
+import {useNavigate} from "react-router-dom";
|
|
| 2 | 11 |
|
| 3 |
-export const useRoleFormPage = (roleCode: string) => {
|
|
| 12 |
+type RoleFormPageModel = {
|
|
| 13 |
+ header: HeaderModel; |
|
| 14 |
+ status: StatusModel; |
|
| 15 |
+ form: {
|
|
| 16 |
+ form: RoleFormItem; |
|
| 17 |
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void; |
|
| 18 |
+ }; |
|
| 19 |
+ actions: FormActionsModel<FormMode> |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+const initRoleForm = {
|
|
| 23 |
+ roleCode: '', |
|
| 24 |
+ roleNm: '', |
|
| 25 |
+ rolePtn: '', |
|
| 26 |
+ roleDc: '', |
|
| 27 |
+ roleSort: '', |
|
| 28 |
+ roleCreatDe: '' |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+const createInitialForm = (item?: RoleDetailResponse) => ({
|
|
| 32 |
+ ...initRoleForm, |
|
| 33 |
+ ...item, |
|
| 34 |
+}); |
|
| 35 |
+ |
|
| 36 |
+export const useRoleFormPage = (roleCode: string):RoleFormPageModel => {
|
|
| 4 | 37 |
const mode = roleCode ? "update" : "create"; |
| 5 | 38 |
const {data, isLoading, error} = useRoleDetail(roleCode);
|
| 39 |
+ const [formDraft, setFormDraft] = useState<Partial<RoleDetailResponse>>({});
|
|
| 40 |
+ const baseForm = useMemo( |
|
| 41 |
+ () => createInitialForm(data), |
|
| 42 |
+ [data], |
|
| 43 |
+ ); |
|
| 44 |
+ const navigate = useNavigate(); |
|
| 6 | 45 |
|
| 7 |
- return {mode, data, isLoading, error}
|
|
| 8 |
-}(No newline at end of file) |
|
| 46 |
+ const {mutateAsync: createRole, isPending: isCreating} = useCreateRole();
|
|
| 47 |
+ const {mutateAsync: updateRole, isPending: isUpdating} = useUpdateRole();
|
|
| 48 |
+ const {mutateAsync: deleteRole, isPending: isDeleting} = useDeleteRole();
|
|
| 49 |
+ const isPending = isCreating || isUpdating || isDeleting; |
|
| 50 |
+ |
|
| 51 |
+ |
|
| 52 |
+ const form = {
|
|
| 53 |
+ ...baseForm, |
|
| 54 |
+ ...formDraft, |
|
| 55 |
+ }; |
|
| 56 |
+ |
|
| 57 |
+ const title = `롤 ${mode === "create" ? '생성' : '수정'}`;
|
|
| 58 |
+ const breadcrumb = [ |
|
| 59 |
+ {label: '권한 관리'},
|
|
| 60 |
+ {label: '롤 관리', url: ADMIN_ROLE_LIST_ROUTE},
|
|
| 61 |
+ {label: title}
|
|
| 62 |
+ ] |
|
| 63 |
+ |
|
| 64 |
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
| 65 |
+ const {name, value} = event.target;
|
|
| 66 |
+ |
|
| 67 |
+ setFormDraft((prev) => ({...prev, [name]: value}));
|
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ const validateForm = () => {
|
|
| 71 |
+ if(!form.roleNm.trim()) {
|
|
| 72 |
+ toast.warning('롤명을 입력해주세요.');
|
|
| 73 |
+ return false; |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ if(!form.rolePtn.trim()) {
|
|
| 77 |
+ toast.warning('롤 패턴을 입력해주세요.');
|
|
| 78 |
+ return false; |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ if(!form.roleDc.trim()) {
|
|
| 82 |
+ toast.warning('롤 설명을 적어주세요.');
|
|
| 83 |
+ return false; |
|
| 84 |
+ } |
|
| 85 |
+ return true; |
|
| 86 |
+ } |
|
| 87 |
+ const handleCreate = async () => {
|
|
| 88 |
+ if(!validateForm()) {
|
|
| 89 |
+ return; |
|
| 90 |
+ } |
|
| 91 |
+ await toast.promise( |
|
| 92 |
+ createRole(form), |
|
| 93 |
+ {
|
|
| 94 |
+ pending: '등록 중...', |
|
| 95 |
+ success: '등록 완료', |
|
| 96 |
+ error: '등록 실패' |
|
| 97 |
+ } |
|
| 98 |
+ ) |
|
| 99 |
+ handleList(); |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ const handleUpdate = async () => {
|
|
| 103 |
+ if(!validateForm()) {
|
|
| 104 |
+ return; |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ await toast.promise( |
|
| 108 |
+ updateRole(form), |
|
| 109 |
+ {
|
|
| 110 |
+ pending: '변경 중...', |
|
| 111 |
+ success: '변경 완료', |
|
| 112 |
+ error: '변경 실패' |
|
| 113 |
+ } |
|
| 114 |
+ ) |
|
| 115 |
+ handleList(); |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ const handleDelete = async () => {
|
|
| 119 |
+ await toast.promise( |
|
| 120 |
+ deleteRole(form.roleCode), |
|
| 121 |
+ {
|
|
| 122 |
+ pending: '삭제 중...', |
|
| 123 |
+ success: '삭제 완료', |
|
| 124 |
+ error: '삭제 실패' |
|
| 125 |
+ } |
|
| 126 |
+ ) |
|
| 127 |
+ handleList(); |
|
| 128 |
+ } |
|
| 129 |
+ const handleList = () => {
|
|
| 130 |
+ navigate(ADMIN_ROLE_LIST_ROUTE); |
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ |
|
| 134 |
+ return {
|
|
| 135 |
+ header: {
|
|
| 136 |
+ title, |
|
| 137 |
+ breadcrumb, |
|
| 138 |
+ homeUrl: '#', |
|
| 139 |
+ }, |
|
| 140 |
+ status: {
|
|
| 141 |
+ isLoading, |
|
| 142 |
+ error, |
|
| 143 |
+ successMessage: '데이터 조회가 완료되었습니다.' |
|
| 144 |
+ }, |
|
| 145 |
+ form : {
|
|
| 146 |
+ form, |
|
| 147 |
+ onChange: handleChange |
|
| 148 |
+ }, |
|
| 149 |
+ actions: {
|
|
| 150 |
+ mode, |
|
| 151 |
+ disabled: isPending, |
|
| 152 |
+ onCreate: handleCreate, |
|
| 153 |
+ onUpdate: handleUpdate, |
|
| 154 |
+ onDelete: handleDelete, |
|
| 155 |
+ onList: handleList |
|
| 156 |
+ }, |
|
| 157 |
+ } |
|
| 158 |
+} |
+++ src/admin/feature/role/role/hook/page/useRoleListPage.ts
... | ... | @@ -0,0 +1,155 @@ |
| 1 | +import type {DeleteRoleListItem, RoleListItem, RoleSearchParams} from "../../type/role.types.ts"; | |
| 2 | +import type { | |
| 3 | + CheckableTableModel, | |
| 4 | + HeaderModel, ListActionsModel, PaginationModel, | |
| 5 | + RowActionsModel, | |
| 6 | + SearchModel, | |
| 7 | + StatusModel | |
| 8 | +} from "../../../../../../type/viewModel.ts"; | |
| 9 | +import {useMemo, useState} from "react"; | |
| 10 | +import {useRoleList} from "../query/useRoleList.ts"; | |
| 11 | +import {useCheckedList} from "../../../../../hook/useCheckedList.ts"; | |
| 12 | +import {toast} from "react-toastify"; | |
| 13 | +import {useDeleteRoleBatch} from "../mutation/useDeleteRoleBatch.ts"; | |
| 14 | +import {useNavigate} from "react-router-dom"; | |
| 15 | +import {ADMIN_ROLE_FORM_ROUTE} from "../../../../../route/adminRouteMap.ts"; | |
| 16 | + | |
| 17 | +type RoleRowActions = { | |
| 18 | + onDetail: (roleCode: string) => void; | |
| 19 | +} | |
| 20 | + | |
| 21 | +type RoleListPageModel = { | |
| 22 | + header: HeaderModel; | |
| 23 | + status: StatusModel; | |
| 24 | + search: SearchModel<RoleSearchParams> | |
| 25 | + table: CheckableTableModel<RoleListItem, RoleSearchParams> & RowActionsModel<RoleRowActions>; | |
| 26 | + actions: ListActionsModel; | |
| 27 | + pagination: PaginationModel; | |
| 28 | +} | |
| 29 | + | |
| 30 | +const initSearchParam: RoleSearchParams = { | |
| 31 | + pageIndex: 1, | |
| 32 | + pageUnit: 10, | |
| 33 | + searchCnd: "0", | |
| 34 | + searchKeyword: "", | |
| 35 | + searchSortCnd: "", | |
| 36 | + searchSortOrd: "", | |
| 37 | +}; | |
| 38 | + | |
| 39 | +const pageSizeOptions = [ | |
| 40 | + {value: '10', label: '10줄'}, | |
| 41 | + {value: '20', label: '20줄'}, | |
| 42 | + {value: '30', label: '30줄'}, | |
| 43 | +] | |
| 44 | + | |
| 45 | +const searchOptions = [ | |
| 46 | + {value: '', label: '전체'}, | |
| 47 | + {value: '0', label: '롤명'}, | |
| 48 | + {value: '1', label: '롤설명'}, | |
| 49 | + {value: '2', label: '롤패턴'}, | |
| 50 | +] | |
| 51 | + | |
| 52 | +const title = '롤관리'; | |
| 53 | +const breadcrumb = [ | |
| 54 | + {label: '권한관리'}, | |
| 55 | + {label: title} | |
| 56 | +]; | |
| 57 | +const successMessage = '롤관리를 조회하였습니다.' | |
| 58 | + | |
| 59 | +export const useRoleListPage = ():RoleListPageModel => { | |
| 60 | + const [searchParams, setSearchParams] = useState({...initSearchParam}); | |
| 61 | + const {currentPage, totalPages, list, totalItems, isLoading, error, size} = useRoleList(searchParams); | |
| 62 | + const {mutateAsync: deleteRoleBatch} = useDeleteRoleBatch(); | |
| 63 | + const navigate = useNavigate(); | |
| 64 | + | |
| 65 | + const handlePageChange = (pageIndex: number) => { | |
| 66 | + setSearchParams({...initSearchParam, pageIndex}); | |
| 67 | + } | |
| 68 | + const roleCodes = useMemo( | |
| 69 | + () => list.map((item) => item.roleCode), | |
| 70 | + [list] | |
| 71 | + ); | |
| 72 | + const { | |
| 73 | + checkedIds, | |
| 74 | + isAllChecked, | |
| 75 | + isPartiallyChecked, | |
| 76 | + isChecked, | |
| 77 | + handleCheck, | |
| 78 | + handleCheckAll, | |
| 79 | + } = useCheckedList<string>(roleCodes); | |
| 80 | + | |
| 81 | + const handleDetail = (roleCode: string) => { | |
| 82 | + navigate(`${ADMIN_ROLE_FORM_ROUTE}/${roleCode}`); | |
| 83 | + } | |
| 84 | + const handleDeleteBatch = async () => { | |
| 85 | + if(checkedIds.length === 0) { | |
| 86 | + toast.warning('삭제할 롤을 선택해주세요.'); | |
| 87 | + return; | |
| 88 | + } | |
| 89 | + const roleList: DeleteRoleListItem[] = checkedIds.map((roleCode) => ({ | |
| 90 | + roleCode | |
| 91 | + })); | |
| 92 | + | |
| 93 | + await toast.promise( | |
| 94 | + deleteRoleBatch(roleList), | |
| 95 | + { | |
| 96 | + pending: '삭제 처리중...', | |
| 97 | + success: '삭제 완료', | |
| 98 | + error: '삭제 실패' | |
| 99 | + } | |
| 100 | + ) | |
| 101 | + } | |
| 102 | + const handleForm = () => { | |
| 103 | + navigate(ADMIN_ROLE_FORM_ROUTE); | |
| 104 | + } | |
| 105 | + | |
| 106 | + return { | |
| 107 | + header: { | |
| 108 | + title, | |
| 109 | + breadcrumb, | |
| 110 | + }, | |
| 111 | + status: { | |
| 112 | + isLoading, | |
| 113 | + error, | |
| 114 | + successMessage | |
| 115 | + }, | |
| 116 | + search: { | |
| 117 | + totalItems, | |
| 118 | + searchParams, | |
| 119 | + onChange: setSearchParams, | |
| 120 | + pageSizeOptions, | |
| 121 | + searchOptions | |
| 122 | + }, | |
| 123 | + table: { | |
| 124 | + items: list, | |
| 125 | + params: searchParams, | |
| 126 | + onChange: setSearchParams, | |
| 127 | + pagination: { | |
| 128 | + totalItems, | |
| 129 | + currentPage, | |
| 130 | + totalPages | |
| 131 | + }, | |
| 132 | + check: { | |
| 133 | + isAllChecked, | |
| 134 | + isPartiallyChecked, | |
| 135 | + isChecked, | |
| 136 | + onCheck: handleCheck, | |
| 137 | + onCheckAll: handleCheckAll, | |
| 138 | + }, | |
| 139 | + rowActions: { | |
| 140 | + onDetail: handleDetail, | |
| 141 | + } | |
| 142 | + }, | |
| 143 | + actions: { | |
| 144 | + onDelete: handleDeleteBatch, | |
| 145 | + onCreate: handleForm | |
| 146 | + }, | |
| 147 | + pagination: { | |
| 148 | + totalItems, | |
| 149 | + totalPages, | |
| 150 | + currentPage, | |
| 151 | + size, | |
| 152 | + onPageChange: handlePageChange | |
| 153 | + } | |
| 154 | + } | |
| 155 | +}(No newline at end of file) |
--- src/admin/feature/role/role/hook/query/useRoleDetail.ts
+++ src/admin/feature/role/role/hook/query/useRoleDetail.ts
... | ... | @@ -2,10 +2,13 @@ |
| 2 | 2 |
import {fetchRoleDetail} from "../../api/roleApi.ts";
|
| 3 | 3 |
|
| 4 | 4 |
export function useRoleDetail(roleCode: string) {
|
| 5 |
- return useQuery({
|
|
| 5 |
+ const query = useQuery({
|
|
| 6 | 6 |
queryKey: ['roleDetail', roleCode], |
| 7 | 7 |
queryFn: () => fetchRoleDetail(roleCode), |
| 8 | 8 |
placeholderData: keepPreviousData, |
| 9 | 9 |
enabled: !!roleCode, |
| 10 |
- }) |
|
| 10 |
+ }); |
|
| 11 |
+ console.log(query) |
|
| 12 |
+ |
|
| 13 |
+ return query |
|
| 11 | 14 |
}(No newline at end of file) |
--- src/admin/feature/role/role/hook/query/useRoleList.ts
+++ src/admin/feature/role/role/hook/query/useRoleList.ts
... | ... | @@ -1,11 +1,14 @@ |
| 1 | 1 |
import {keepPreviousData, useQuery} from "@tanstack/react-query";
|
| 2 | 2 |
import type {RoleSearchParams} from "../../type/role.types.ts";
|
| 3 | 3 |
import {fetchRoleList} from "../../api/roleApi.ts";
|
| 4 |
+import {createPageQueryResult} from "../../../../../../type/pageResponse.ts";
|
|
| 4 | 5 |
|
| 5 | 6 |
export function useRoleList(searchParams: RoleSearchParams) {
|
| 6 |
- return useQuery({
|
|
| 7 |
+ const query = useQuery({
|
|
| 7 | 8 |
queryKey: ['roleList', searchParams], |
| 8 | 9 |
queryFn: () => fetchRoleList(searchParams), |
| 9 | 10 |
placeholderData: keepPreviousData |
| 10 | 11 |
}); |
| 12 |
+ |
|
| 13 |
+ return createPageQueryResult(query); |
|
| 11 | 14 |
}(No newline at end of file) |
--- src/admin/feature/role/role/page/AuthorRoleFormPage.tsx
... | ... | @@ -1,7 +0,0 @@ |
| 1 | -export const AuthorRoleFormPage = () => { | |
| 2 | - return ( | |
| 3 | - | |
| 4 | - <> | |
| 5 | - </> | |
| 6 | - ); | |
| 7 | -}(No newline at end of file) |
+++ src/admin/feature/role/role/page/RoleFormPage.tsx
... | ... | @@ -0,0 +1,20 @@ |
| 1 | +import {useParams} from "react-router-dom"; | |
| 2 | +import {useRoleFormPage} from "../hook/page/useRoleFormPage.ts"; | |
| 3 | +import {PageHeader} from "../../../../component/PageHeader.tsx"; | |
| 4 | +import {ActionButtonFormGroup} from "../../../../component/button/ActionButtonFormGroup.tsx"; | |
| 5 | +import {RoleFormTable} from "../component/RoleFormTable.tsx"; | |
| 6 | +import {useLoadingToast} from "../../../../hook/useLoadingToast.ts"; | |
| 7 | + | |
| 8 | +export const RoleFormPage = () => { | |
| 9 | + const {roleCode = ''} = useParams(); | |
| 10 | + const {header, form, actions, status} = useRoleFormPage(roleCode); | |
| 11 | + | |
| 12 | + useLoadingToast(status); | |
| 13 | + return ( | |
| 14 | + <> | |
| 15 | + <PageHeader {...header}/> | |
| 16 | + <RoleFormTable {...form} mode={actions.mode}/> | |
| 17 | + <ActionButtonFormGroup {...actions}/> | |
| 18 | + </> | |
| 19 | + ); | |
| 20 | +}(No newline at end of file) |
+++ src/admin/feature/role/role/page/RoleListPage.tsx
... | ... | @@ -0,0 +1,22 @@ |
| 1 | +import {useRoleListPage} from "../hook/page/useRoleListPage.ts"; | |
| 2 | +import {useLoadingToast} from "../../../../hook/useLoadingToast.ts"; | |
| 3 | +import { PageHeader } from "../../../../component/PageHeader.tsx"; | |
| 4 | +import {ActionButtonListGroup} from "../../../../component/button/ActionButtonListGroup.tsx"; | |
| 5 | +import {ListSearchForm} from "../../../../component/ListSearchForm.tsx"; | |
| 6 | +import {Pagination} from "../../../../component/pagination/Pagination.tsx"; | |
| 7 | +import {RoleListTable} from "../component/RoleListTable.tsx"; | |
| 8 | + | |
| 9 | +export const RoleListPage = () => { | |
| 10 | + const {header, status, search, table, pagination, actions} = useRoleListPage(); | |
| 11 | + useLoadingToast(status); | |
| 12 | + | |
| 13 | + return ( | |
| 14 | + <> | |
| 15 | + <PageHeader {...header} /> | |
| 16 | + <ListSearchForm {...search} totalLabel={'총 게시물'}/> | |
| 17 | + <RoleListTable {...table}/> | |
| 18 | + <ActionButtonListGroup {...actions}/> | |
| 19 | + <Pagination {...pagination} /> | |
| 20 | + </> | |
| 21 | + ) | |
| 22 | +}(No newline at end of file) |
--- src/admin/feature/role/role/type/role.types.ts
+++ src/admin/feature/role/role/type/role.types.ts
... | ... | @@ -11,3 +11,25 @@ |
| 11 | 11 |
roleSort: string, |
| 12 | 12 |
roleCreatDe: string, |
| 13 | 13 |
} |
| 14 |
+ |
|
| 15 |
+export interface RoleListItem {
|
|
| 16 |
+ roleCode: string; |
|
| 17 |
+ roleDc: string; |
|
| 18 |
+ rolePtn: string; |
|
| 19 |
+ roleSort: string; |
|
| 20 |
+ roleNm: string; |
|
| 21 |
+ roleCreatDe: string; |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+export interface DeleteRoleListItem {
|
|
| 25 |
+ roleCode: string; |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+export interface RoleFormItem {
|
|
| 29 |
+ roleCode: string; |
|
| 30 |
+ roleDc: string; |
|
| 31 |
+ rolePtn: string; |
|
| 32 |
+ roleSort: string; |
|
| 33 |
+ roleNm: string; |
|
| 34 |
+ roleCreatDe: string; |
|
| 35 |
+} |
--- src/admin/route/AdminRoute.tsx
+++ src/admin/route/AdminRoute.tsx
... | ... | @@ -4,17 +4,19 @@ |
| 4 | 4 |
ADMIN_AUTHOR_DETAIL_ROUTE, ADMIN_AUTHOR_GROUP_LIST_ROUTE, |
| 5 | 5 |
ADMIN_AUTHOR_LIST_ROUTE, |
| 6 | 6 |
ADMIN_AUTHOR_ROLE_LIST_ROUTE, ADMIN_BBS_ARTICLE_FORM_ROUTE, |
| 7 |
- ADMIN_BBS_MASTER_ROUTE, ADMIN_MENU_CREATE_MANAGE_ROUTE, ADMIN_MENU_POPUP_ROUTE, ADMIN_ROLE_FORM_ROUTE |
|
| 7 |
+ ADMIN_BBS_MASTER_ROUTE, ADMIN_MENU_CREATE_MANAGE_ROUTE, ADMIN_MENU_POPUP_ROUTE, ADMIN_ROLE_FORM_ROUTE, |
|
| 8 |
+ ADMIN_ROLE_LIST_ROUTE |
|
| 8 | 9 |
} from "./adminRouteMap.ts"; |
| 9 | 10 |
import {BoardArticleListPage} from "../feature/board/article/page/BoardArticleListPage.tsx";
|
| 10 | 11 |
import {BoardFormPage} from "../feature/board/master/page/BoardFormPage.tsx";
|
| 11 | 12 |
import {AuthorListPage} from "../feature/role/author/page/AuthorListPage.tsx";
|
| 12 | 13 |
import {AuthorRoleListPage} from "../feature/role/authorRole/page/AuthorRoleListPage.tsx";
|
| 13 |
-import {AuthorRoleFormPage} from "../feature/role/role/page/AuthorRoleFormPage.tsx";
|
|
| 14 |
+import {RoleFormPage} from "../feature/role/role/page/RoleFormPage.tsx";
|
|
| 14 | 15 |
import {AuthorRoleMenuListPage} from "../feature/role/authorRoleMenu/page/AuthorRoleMenuListPage.tsx";
|
| 15 | 16 |
import {AuthorRoleMenuPopupPage} from "../feature/role/authorRoleMenu/page/AuthorRoleMenuPopupPage.tsx";
|
| 16 | 17 |
import {AdminLayout} from "../layout/AdminLayout.tsx";
|
| 17 | 18 |
import {AuthorGroupListPage} from "../feature/role/authorGroup/page/AuthorGroupListPage.tsx";
|
| 19 |
+import {RoleListPage} from "../feature/role/role/page/RoleListPage.tsx";
|
|
| 18 | 20 |
|
| 19 | 21 |
const ReadyPage = () => {
|
| 20 | 22 |
return <div>Preparing menu.</div>; |
... | ... | @@ -35,9 +37,11 @@ |
| 35 | 37 |
<Route path={ADMIN_AUTHOR_LIST_ROUTE} element={<AuthorListPage/>}/>
|
| 36 | 38 |
<Route path={ADMIN_AUTHOR_DETAIL_ROUTE} element={<ReadyPage/>}/>
|
| 37 | 39 |
<Route path={ADMIN_AUTHOR_ROLE_LIST_ROUTE} element={<AuthorRoleListPage/>}/>
|
| 38 |
- <Route path={ADMIN_ROLE_FORM_ROUTE} element={<AuthorRoleFormPage/>}/>
|
|
| 39 | 40 |
<Route path={ADMIN_MENU_CREATE_MANAGE_ROUTE} element={<AuthorRoleMenuListPage/>}/>
|
| 40 | 41 |
<Route path={ADMIN_AUTHOR_GROUP_LIST_ROUTE} element={<AuthorGroupListPage/>}/>
|
| 42 |
+ <Route path={ADMIN_ROLE_LIST_ROUTE} element={<RoleListPage/>}/>
|
|
| 43 |
+ <Route path={ADMIN_ROLE_FORM_ROUTE} element={<RoleFormPage/>}/>
|
|
| 44 |
+ <Route path={`${ADMIN_ROLE_FORM_ROUTE}/:roleCode`} element={<RoleFormPage/>}/>
|
|
| 41 | 45 |
<Route path="*" element={<ReadyPage/>}/>
|
| 42 | 46 |
</Route> |
| 43 | 47 |
</Routes> |
--- src/type/viewModel.ts
+++ src/type/viewModel.ts
... | ... | @@ -90,3 +90,5 @@ |
| 90 | 90 |
onDelete?: () => void | Promise<void>; |
| 91 | 91 |
onList?: () => void; |
| 92 | 92 |
}; |
| 93 |
+ |
|
| 94 |
+export type FormMode = 'create' | 'update' |
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?