feat: 게시판 관리 페이지 rewrites 로직 적용
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
import { ApimanPage } from "@/pages/apiman";
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return <ApimanPage />;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { ModifyPage as VModifyPage } from "@/pages/apiman/ui/modify-page";
|
|
||||||
|
|
||||||
|
|
||||||
export default async function ModifyPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: number}>;
|
|
||||||
}) {
|
|
||||||
const { id } = await params;
|
|
||||||
return <VModifyPage authorizationNo={id} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { CreatePage } from "@/pages/apiman/ui/create-page";
|
|
||||||
export default CreatePage;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { GroupPage } from "@/pages/group";
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return <GroupPage />;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { ModifyPage as VModifyPage } from "@/pages/group/ui/modify-page";
|
|
||||||
|
|
||||||
export default async function ModifyPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: string }>;
|
|
||||||
}) {
|
|
||||||
const { id } = await params;
|
|
||||||
return <VModifyPage groupId={id} />;
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { CreatePage } from "@/pages/group/ui/create-page";
|
|
||||||
export default CreatePage;
|
|
||||||
+12
-1
@@ -1,5 +1,16 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {};
|
const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN;
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/board/:path*",
|
||||||
|
destination: `${subDomain}/board/:path*`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import type { Page } from "@/shared/types";
|
|
||||||
import { client } from "@/shared/api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
|
|
||||||
export interface Api {
|
|
||||||
authorizationNo: number; // API 일련번호
|
|
||||||
authorizationName: string; // API 명
|
|
||||||
urlPatternValue: string; // URL 패턴
|
|
||||||
httpMethodCode: string; // 하이퍼텍스트 전송 프로토콜 메쏘드 값 (예: GET, POST)
|
|
||||||
sortSeq: number; // 정렬 순서
|
|
||||||
useYn: string; // 사용 여부 (Y/N)
|
|
||||||
regDt: string; // 등록 일시
|
|
||||||
rgtr: string; // 등록자 UUID
|
|
||||||
mdfcnDt: string | null; // 갱신 일시
|
|
||||||
mdfr: string | null; // 갱신자 UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiParams {
|
|
||||||
authorizationName?: string; // API 명 검색
|
|
||||||
httpMethodCode?: string; // HTTP 메소드 검색
|
|
||||||
useYn?: string; // 사용 여부 검색
|
|
||||||
page?: number; // 페이지 번호
|
|
||||||
size?: number; // 페이지 크기
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiFormData {
|
|
||||||
authorizationNo: number | null; // API 일련번호 (신규 등록 시 null)
|
|
||||||
authorizationName: string; // API 명
|
|
||||||
urlPatternValue: string; // URL 패턴
|
|
||||||
httpMethodCode: string; // HTTP 메소드
|
|
||||||
sortSeq: number; // 정렬 순서
|
|
||||||
useYn: string; // 사용 여부
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ApimanApi = {
|
|
||||||
getApiList: async (params: ApiParams) => {
|
|
||||||
const { data } = await client.get<Page<Api>>(
|
|
||||||
`${ENDPOINT.USER_SERVICE.AUTHRTS}`,
|
|
||||||
{ params }
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getApi: async (authorizationNo: number) => {
|
|
||||||
const { data } = await client.get<Api>(`${ENDPOINT.USER_SERVICE.AUTHRTS}/${authorizationNo}`);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
createApi: async (apiData: ApiFormData) => {
|
|
||||||
const { data } = await client.post<Api>(ENDPOINT.USER_SERVICE.AUTHRTS, apiData);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateApi: async (authorizationNo: number, apiData: ApiFormData) => {
|
|
||||||
const { data } = await client.put<Api>(`${ENDPOINT.USER_SERVICE.AUTHRTS}/${authorizationNo}`, apiData);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteApi: async (authorizationNo: number) => {
|
|
||||||
const { data } = await client.delete<void>(`${ENDPOINT.USER_SERVICE.AUTHRTS}/${authorizationNo}`);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export { ApimanPage } from "./ui/apiman-page";
|
|
||||||
export { ApimanForm } from "./ui/apiman-form";
|
|
||||||
export { CreatePage } from "./ui/create-page";
|
|
||||||
export { ModifyPage } from "./ui/modify-page";
|
|
||||||
export { ApimanApi } from "./api/apiman-api";
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
import { Badge, Button } from "@common/components";
|
|
||||||
import type { Api } from "../api/apiman-api";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
// API 권한 관리를 위한 타입 정의
|
|
||||||
type ApiTableMeta = {
|
|
||||||
openRoleDialog: (api: Api) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const columns: ColumnDef<Api>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "authorizationNo",
|
|
||||||
header: "번호",
|
|
||||||
cell: ({ row }) => <div>{row.original.authorizationNo}</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "authorizationName",
|
|
||||||
header: "API 명",
|
|
||||||
cell: ({ row }) => <div>{row.original.authorizationName}</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "urlPatternValue",
|
|
||||||
header: "URL 패턴",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="max-w-md truncate text-left" title={row.original.urlPatternValue}>
|
|
||||||
{row.original.urlPatternValue}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "httpMethodCode",
|
|
||||||
header: "HTTP 메소드",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const method = row.original.httpMethodCode;
|
|
||||||
let color = "bg-gray-500";
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case "GET":
|
|
||||||
color = "bg-blue-500";
|
|
||||||
break;
|
|
||||||
case "POST":
|
|
||||||
color = "bg-green-500";
|
|
||||||
break;
|
|
||||||
case "PUT":
|
|
||||||
color = "bg-orange-500";
|
|
||||||
break;
|
|
||||||
case "DELETE":
|
|
||||||
color = "bg-red-500";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge className={`${color} text-white`}>
|
|
||||||
{method}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "sortSeq",
|
|
||||||
header: "정렬 순서",
|
|
||||||
cell: ({ row }) => <div className="text-center">{row.original.sortSeq}</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "useYn",
|
|
||||||
header: "사용 여부",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const useYn = row.original.useYn;
|
|
||||||
return (
|
|
||||||
<Badge className={useYn === "Y" ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}>
|
|
||||||
{useYn === "Y" ? "사용" : "미사용"}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "mdfrNm",
|
|
||||||
header: "수정자",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "mdfcnDt",
|
|
||||||
header: "수정일시",
|
|
||||||
cell: ({ row }) => dayjs(row.original.mdfcnDt).format("YYYY-MM-DD"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
header: "권한",
|
|
||||||
cell: ({ row, table }) => {
|
|
||||||
// 메타 데이터에서 권한 다이얼로그 열기 함수 가져오기
|
|
||||||
const meta = table.options.meta as ApiTableMeta | undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-16 bg-blue-500 text-white hover:bg-blue-600"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation(); // 행 클릭 이벤트 전파 방지
|
|
||||||
if (meta?.openRoleDialog) {
|
|
||||||
// console.log(row.original);
|
|
||||||
meta.openRoleDialog(row.original);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
권한
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogTrigger,
|
|
||||||
Button,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
Input,
|
|
||||||
RadioGroup,
|
|
||||||
RadioGroupItem,
|
|
||||||
DialogFooter,
|
|
||||||
DialogClose,
|
|
||||||
Checkbox,
|
|
||||||
Label,
|
|
||||||
} from "@common/components";
|
|
||||||
import { ApiRoleApi, type ApiRoleByApi } from "../../apirole/api/apirole-api";
|
|
||||||
|
|
||||||
// 페이지네이션 응답을 위한 인터페이스
|
|
||||||
interface Page<T> {
|
|
||||||
content: T[];
|
|
||||||
pageable: {
|
|
||||||
pageNumber: number;
|
|
||||||
pageSize: number;
|
|
||||||
};
|
|
||||||
totalElements: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API 역할 인터페이스 정의
|
|
||||||
interface Role extends ApiRoleByApi {
|
|
||||||
selected?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ApiRoleDialogProps {
|
|
||||||
api: ApiRoleByApi | null;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
onSave: (roleIds: string[]) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ApiRoleDialog({ api, isOpen, onClose, onSave }: ApiRoleDialogProps): JSX.Element {
|
|
||||||
const [roles, setRoles] = useState<Role[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
// API에서 역할 데이터 가져오기
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen && api && api.authorizationNo) {
|
|
||||||
// 실제 API 호출로 데이터 가져오기
|
|
||||||
ApiRoleApi.getApiRoleByApiList({ authorizationNo: api.authorizationNo })
|
|
||||||
.then((data: Page<ApiRoleByApi>) => {
|
|
||||||
// 데이터를 Role 인터페이스에 맞게 변환
|
|
||||||
const rolesWithSelected = data.content.map(role => ({
|
|
||||||
...role,
|
|
||||||
mode: role.useYn ==null?"C":"R",
|
|
||||||
selected: role.useYn === "Y"
|
|
||||||
}));
|
|
||||||
setRoles(rolesWithSelected);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("역할 데이터 가져오기 오류:", error);
|
|
||||||
// 오류 발생 시 빈 배열 설정
|
|
||||||
setRoles([]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isOpen, api]);
|
|
||||||
|
|
||||||
const handleToggleRole = (roleId: string) => {
|
|
||||||
setRoles(
|
|
||||||
roles.map((role) =>
|
|
||||||
role.roleId === roleId ? { ...role, useYn: role.useYn === "Y" ? "N" : "Y", mode: role.mode === "R" ? "U" : "C" } : role
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
//const selectedRoleIds = roles.filter((role) => role.useYn === "Y").map((role) => role.roleId ?? "");
|
|
||||||
await onSave(roles);
|
|
||||||
onClose();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("권한 저장 중 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Dialog가 닫힐 때 호출되는 함수
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
|
||||||
if (!open) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
|
||||||
<DialogContent className="sm:max-w-[600px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="text-xl font-bold">
|
|
||||||
API 권한 설정 - {api?.authorizationName}
|
|
||||||
</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="card mb-4">
|
|
||||||
<Table>
|
|
||||||
<colgroup>
|
|
||||||
<col style={{ width: "20%" }} />
|
|
||||||
<col style={{ width: "30%" }} />
|
|
||||||
<col style={{ width: "20%" }} />
|
|
||||||
<col style={{ width: "30%" }} />
|
|
||||||
</colgroup>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell as="th">API 명</TableCell>
|
|
||||||
<TableCell className="border">
|
|
||||||
{api?.authorizationName || ""}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell as="th">HTTP 메소드</TableCell>
|
|
||||||
<TableCell className="border">
|
|
||||||
{api?.httpMethodCode || ""}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell as="th">URL 패턴</TableCell>
|
|
||||||
<TableCell colSpan={3} className="border">
|
|
||||||
{api?.urlPatternValue || ""}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-h-[400px] overflow-auto">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="w-[50px]">허용</TableHead>
|
|
||||||
<TableHead className="w-[150px]">권한 ID</TableHead>
|
|
||||||
<TableHead>권한명</TableHead>
|
|
||||||
<TableHead>권한내용</TableHead>
|
|
||||||
<TableHead className="w-[80px]">사용여부</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{roles.map((role) => (
|
|
||||||
<TableRow key={role.roleId}>
|
|
||||||
<TableCell className="text-center">
|
|
||||||
<Checkbox
|
|
||||||
checked={role.useYn === "Y"}
|
|
||||||
onCheckedChange={() => handleToggleRole(role.roleId)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{role.roleId}</TableCell>
|
|
||||||
<TableCell>{role.roleName}</TableCell>
|
|
||||||
<TableCell>{role.roleContent}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<span className={role.useYn === "Y" ? "text-green-600" : "text-red-600"}>
|
|
||||||
{role.useYn === "Y" ? "사용" : "미사용"}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
{roles.length === 0 && (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={5} className="text-center py-4">
|
|
||||||
권한 정보가 없습니다.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-2 mt-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={onClose}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
취소
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? "저장 중..." : "저장"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
// apps/cs/src/pages/apiman/ui/apiman-form.tsx
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
||||||
import { type UseFormReturn } from "react-hook-form";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
RadioGroup,
|
|
||||||
RadioGroupItem,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@common/components";
|
|
||||||
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
|
|
||||||
import type { ApiFormData } from "../api/apiman-api";
|
|
||||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
|
||||||
|
|
||||||
interface ApimanFormProps {
|
|
||||||
form: UseFormReturn<ApiFormData>;
|
|
||||||
authorizationNo?: number;
|
|
||||||
handleSave: (data: ApiFormData) => Promise<void>;
|
|
||||||
handleDelete?: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ApimanForm({ form, authorizationNo, handleSave, handleDelete }: ApimanFormProps) {
|
|
||||||
const isModifyMode = !!authorizationNo;
|
|
||||||
const router = useRouter();
|
|
||||||
const { confirm } = useConfirm();
|
|
||||||
const [onConfirm, setOnConfirm] = useState(false);
|
|
||||||
|
|
||||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
|
||||||
setOnConfirm(true);
|
|
||||||
await callback();
|
|
||||||
setOnConfirm(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (data: ApiFormData) => {
|
|
||||||
const confirmMessage = isModifyMode
|
|
||||||
? "수정하시겠습니까?"
|
|
||||||
: "저장하시겠습니까?";
|
|
||||||
|
|
||||||
await handleConfirm(async () => {
|
|
||||||
if (await confirm(confirmMessage)) {
|
|
||||||
await handleSave(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDelete = async () => {
|
|
||||||
await handleConfirm(async () => {
|
|
||||||
if (await confirm("삭제하시겠습니까?")) {
|
|
||||||
handleDelete?.();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
||||||
<div className="card">
|
|
||||||
<Table variant="secondary">
|
|
||||||
<colgroup>
|
|
||||||
<col width="20%" />
|
|
||||||
<col width="80%" />
|
|
||||||
</colgroup>
|
|
||||||
<TableBody>
|
|
||||||
{isModifyMode && (
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>API 일련번호</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="authorizationNo"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value?.toString() ?? ""}
|
|
||||||
className="w-[12rem] !text-[1.3rem]"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>API 명</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="authorizationName"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="!text-[1.3rem]"
|
|
||||||
placeholder="API 명을 입력하세요"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>URL 패턴</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="urlPatternValue"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="!text-[1.3rem]"
|
|
||||||
placeholder="URL 패턴을 입력하세요 (예: /api/users/{id})"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>요청 메소드</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="httpMethodCode"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={field.value || undefined}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="!text-[1.3rem]">
|
|
||||||
<SelectValue placeholder="요청 메소드를 선택하세요" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="GET">GET</SelectItem>
|
|
||||||
<SelectItem value="POST">POST</SelectItem>
|
|
||||||
<SelectItem value="PUT">PUT</SelectItem>
|
|
||||||
<SelectItem value="DELETE">DELETE</SelectItem>
|
|
||||||
<SelectItem value="PATCH">PATCH</SelectItem>
|
|
||||||
<SelectItem value="OPTIONS">OPTIONS</SelectItem>
|
|
||||||
<SelectItem value="HEAD">TRACE</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>정렬 순서</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="sortSeq"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
{...field}
|
|
||||||
value={field.value?.toString() ?? ""}
|
|
||||||
className="w-[12rem] !text-[1.3rem]"
|
|
||||||
onChange={(e) => field.onChange(parseInt(e.target.value) || 0)}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>사용 여부</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="useYn"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroup
|
|
||||||
{...field}
|
|
||||||
name="useYn"
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
value={field.value}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="Y" id="useY" />
|
|
||||||
<Label htmlFor="useY">예</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="N" id="useN" />
|
|
||||||
<Label htmlFor="useN">아니오</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
variant="outline"
|
|
||||||
type="button"
|
|
||||||
onClick={() => router.back()}
|
|
||||||
>
|
|
||||||
목록
|
|
||||||
</Button>
|
|
||||||
<Button size="lg" type="submit">
|
|
||||||
저장
|
|
||||||
</Button>
|
|
||||||
{isModifyMode && (
|
|
||||||
<Button size="lg" variant="destructive" type="button" onClick={onDelete}>
|
|
||||||
삭제
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
<ConfirmDialog open={onConfirm} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ApimanForm };
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import "@common/assets/styles/grid.css";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import {
|
|
||||||
getCoreRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
useReactTable
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
DataTable,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
Pagination
|
|
||||||
} from "@common/components";
|
|
||||||
|
|
||||||
import { ApimanApi, type Api, type ApiParams } from "../api/apiman-api";
|
|
||||||
import { type ApiRoleByApi } from "../../apirole/api/apirole-api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
import { columns } from "../model/apiman-table-columns";
|
|
||||||
import { ApiRoleDialog } from "./api-role-dialog";
|
|
||||||
|
|
||||||
function ApimanPage() {
|
|
||||||
const router = useRouter();
|
|
||||||
const [apiQuery, setApiQuery] = useState<ApiParams>({
|
|
||||||
authorizationName: "",
|
|
||||||
httpMethodCode: "",
|
|
||||||
useYn: "",
|
|
||||||
page: 0,
|
|
||||||
size: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// API 역할 다이얼로그 상태 관리
|
|
||||||
const [apiRoleDialog, setApiRoleDialog] = useState<{
|
|
||||||
isOpen: boolean;
|
|
||||||
api: ApiRoleByApi | null;
|
|
||||||
}>({
|
|
||||||
isOpen: false,
|
|
||||||
api: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: apiList, refetch, isLoading } = useQuery({
|
|
||||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHRTS, apiQuery],
|
|
||||||
queryFn: () => ApimanApi.getApiList(apiQuery),
|
|
||||||
enabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: apiList?.content ?? [],
|
|
||||||
columns,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
pageCount: apiList?.totalPages ?? -1,
|
|
||||||
state: {
|
|
||||||
pagination: {
|
|
||||||
pageIndex: apiQuery.page ?? 0,
|
|
||||||
pageSize: apiQuery.size ?? 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
openRoleDialog: (api: ApiRoleByApi) => {
|
|
||||||
setApiRoleDialog({
|
|
||||||
isOpen: true,
|
|
||||||
api,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
onPaginationChange: (updater) => {
|
|
||||||
const newPagination =
|
|
||||||
typeof updater === "function"
|
|
||||||
? updater({ pageIndex: apiQuery.page ?? 0, pageSize: apiQuery.size ?? 10 })
|
|
||||||
: updater;
|
|
||||||
|
|
||||||
setApiQuery((prev) => ({
|
|
||||||
...prev,
|
|
||||||
page: newPagination.pageIndex,
|
|
||||||
size: newPagination.pageSize,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
manualPagination: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refetch();
|
|
||||||
}, [apiQuery, refetch]);
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setApiQuery(prev => ({ ...prev, page: 0 }));
|
|
||||||
refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleRowClick = (api: Api) => {
|
|
||||||
router.push(`/apiman/register/${api.authorizationNo}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// API 역할 저장 핸들러
|
|
||||||
const handleSaveRoles = async (roles: ApiRoleByApi[]) => {
|
|
||||||
try {
|
|
||||||
// 실제 구현에서는 API 호출로 대체
|
|
||||||
console.log(`API ${apiRoleDialog.api?.authorizationNo}에 권한 저장:`, roles);
|
|
||||||
// 성공 시 다이얼로그 닫기
|
|
||||||
setApiRoleDialog({ isOpen: false, api: null });
|
|
||||||
// 목록 새로고침
|
|
||||||
refetch();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("권한 저장 중 오류 발생:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card card-border !mt-6">
|
|
||||||
{/* API 역할 다이얼로그 */}
|
|
||||||
<ApiRoleDialog
|
|
||||||
api={apiRoleDialog.api}
|
|
||||||
isOpen={apiRoleDialog.isOpen}
|
|
||||||
onClose={() => setApiRoleDialog({ isOpen: false, api: null })}
|
|
||||||
onSave={handleSaveRoles}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="card card-border">
|
|
||||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
|
||||||
|
|
||||||
<Label htmlFor="httpMethodCode" className="mr-2 text-label">요청 메소드</Label>
|
|
||||||
<Select
|
|
||||||
value={apiQuery.httpMethodCode || undefined}
|
|
||||||
onValueChange={(value) => setApiQuery( prev => ({ ...prev, httpMethodCode: value === "ALL" ? "" : value }))}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="httpMethodCode" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]">
|
|
||||||
<SelectValue placeholder="요청 메소드 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="ALL">전체</SelectItem>
|
|
||||||
<SelectItem value="GET">GET</SelectItem>
|
|
||||||
<SelectItem value="POST">POST</SelectItem>
|
|
||||||
<SelectItem value="PUT">PUT</SelectItem>
|
|
||||||
<SelectItem value="DELETE">DELETE</SelectItem>
|
|
||||||
<SelectItem value="OPTIONS">OPTIONS</SelectItem>
|
|
||||||
<SelectItem value="PATCH">PATCH</SelectItem>
|
|
||||||
<SelectItem value="HEAD">HEAD</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Label htmlFor="authorizationName" className="mr-2 text-label">API 명</Label>
|
|
||||||
<Input
|
|
||||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
|
||||||
id="authorizationName"
|
|
||||||
value={apiQuery.authorizationName || ""}
|
|
||||||
onChange={(e) => setApiQuery( prev => ({ ...prev, authorizationName: e.target.value }))}
|
|
||||||
placeholder="API 명 입력"
|
|
||||||
/>
|
|
||||||
<Button className="h-[3.2rem]" type="submit">
|
|
||||||
조회
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow">
|
|
||||||
<div className="card-header">
|
|
||||||
<h3 className="text-2xl font-medium text-label">모듈API 목록</h3>
|
|
||||||
<Button onClick={() => router.push("/apiman/register")}>+등록</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataTable
|
|
||||||
table={table}
|
|
||||||
isLoading={isLoading}
|
|
||||||
onRowClick={handleRowClick}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-end space-x-2 py-4">
|
|
||||||
<div className="flex-1 text-lm text-muted-foreground">
|
|
||||||
총 {apiList?.totalElements ?? 0} 건
|
|
||||||
</div>
|
|
||||||
<Pagination
|
|
||||||
currentPage={apiQuery.page + 1}
|
|
||||||
totalPages={apiList?.totalPages ?? 0}
|
|
||||||
onPageChange={(page) =>
|
|
||||||
setApiQuery(prev => ({ ...prev, page: page - 1 }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
value={String(apiQuery.size)}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
setApiQuery(prev => ({ ...prev, page: 0, size: Number(value) }))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 w-[70px]">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{[2,10, 20, 30, 40, 50].map((pageSize) => (
|
|
||||||
<SelectItem key={pageSize} value={String(pageSize)}>
|
|
||||||
{pageSize}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ApimanPage };
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { ApimanApi, type ApiFormData } from "../api/apiman-api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
|
||||||
import { ApimanForm } from "./apiman-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
|
|
||||||
// API 스키마 정의
|
|
||||||
const apiFormSchema = z.object({
|
|
||||||
authorizationNo: z.number().nullable(),
|
|
||||||
authorizationName: z.string().min(1, "API 명을 입력해주세요"),
|
|
||||||
urlPatternValue: z.string().min(1, "URL 패턴을 입력해주세요"),
|
|
||||||
httpMethodCode: z.string().min(1, "HTTP 메소드를 선택해주세요"),
|
|
||||||
sortSeq: z.number().min(0, "정렬 순서는 0 이상이어야 합니다"),
|
|
||||||
useYn: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
function CreatePage() {
|
|
||||||
const router = useRouter();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { setMessage: alert } = useAlertStore((state) => state);
|
|
||||||
|
|
||||||
const { mutateAsync: createApi } = useMutation({
|
|
||||||
mutationFn: (data: ApiFormData) => ApimanApi.createApi(data),
|
|
||||||
onSuccess: () => {
|
|
||||||
alert("등록되었습니다.", () => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHRTS],
|
|
||||||
});
|
|
||||||
router.push("/apiman");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (error: unknown) => {
|
|
||||||
const axiosError = error as AxiosError<{ message: string }>;
|
|
||||||
if (axiosError.response?.data) {
|
|
||||||
alert(axiosError.response.data.message);
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
alert(error.message);
|
|
||||||
} else {
|
|
||||||
alert("알 수 없는 오류가 발생했습니다.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<ApiFormData>({
|
|
||||||
resolver: zodResolver(apiFormSchema),
|
|
||||||
defaultValues: {
|
|
||||||
authorizationNo: null,
|
|
||||||
authorizationName: "",
|
|
||||||
urlPatternValue: "",
|
|
||||||
httpMethodCode: "",
|
|
||||||
sortSeq: 0,
|
|
||||||
useYn: "Y"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card card-border !mb-0 !mt-6">
|
|
||||||
<div className="card-header">
|
|
||||||
<h3 className="text-2xl font-medium text-label">API 등록</h3>
|
|
||||||
</div>
|
|
||||||
<ApimanForm
|
|
||||||
form={form}
|
|
||||||
handleSave={createApi}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { CreatePage };
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { ApimanApi, type ApiFormData } from "../api/apiman-api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
|
||||||
import { ApimanForm } from "./apiman-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
|
|
||||||
// API 스키마 정의
|
|
||||||
const apiFormSchema = z.object({
|
|
||||||
authorizationNo: z.number().nullable(),
|
|
||||||
authorizationName: z.string().min(1, "API 명을 입력해주세요"),
|
|
||||||
urlPatternValue: z.string().min(1, "URL 패턴을 입력해주세요"),
|
|
||||||
httpMethodCode: z.string().min(1, "HTTP 메소드를 선택해주세요"),
|
|
||||||
sortSeq: z.number(),
|
|
||||||
useYn: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
function ModifyPage({ authorizationNo }: { authorizationNo: number }) {
|
|
||||||
const router = useRouter();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { setMessage: alert } = useAlertStore((state) => state);
|
|
||||||
|
|
||||||
const { data: apiDetail } = useQuery({
|
|
||||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHRTS, authorizationNo],
|
|
||||||
queryFn: () => ApimanApi.getApi(authorizationNo),
|
|
||||||
select: (data) => {
|
|
||||||
const keys = Object.keys(apiFormSchema.shape);
|
|
||||||
const filteredData = Object.fromEntries(
|
|
||||||
Object.entries(data).filter(([key]) => keys.includes(key)),
|
|
||||||
);
|
|
||||||
return filteredData;
|
|
||||||
},
|
|
||||||
enabled: !!authorizationNo,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutateAsync: updateApi } = useMutation({
|
|
||||||
mutationFn: (data: ApiFormData) => ApimanApi.updateApi(authorizationNo, data),
|
|
||||||
onSuccess: () => {
|
|
||||||
alert("수정되었습니다.", () => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHRTS],
|
|
||||||
});
|
|
||||||
router.back();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (error: unknown) => {
|
|
||||||
const axiosError = error as AxiosError<{ message: string }>;
|
|
||||||
if (axiosError.response?.data) {
|
|
||||||
alert(axiosError.response.data.message);
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
alert(error.message);
|
|
||||||
} else {
|
|
||||||
alert("알 수 없는 오류가 발생했습니다.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutateAsync: deleteApi } = useMutation({
|
|
||||||
mutationFn: () => ApimanApi.deleteApi(authorizationNo),
|
|
||||||
onSuccess: () => {
|
|
||||||
alert("삭제되었습니다.", router.back);
|
|
||||||
},
|
|
||||||
onError: (error: unknown) => {
|
|
||||||
const axiosError = error as AxiosError<{ message: string }>;
|
|
||||||
if (axiosError.response?.data) {
|
|
||||||
alert(axiosError.response.data.message);
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
alert(error.message);
|
|
||||||
} else {
|
|
||||||
alert("알 수 없는 오류가 발생했습니다.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<ApiFormData>({
|
|
||||||
resolver: zodResolver(apiFormSchema),
|
|
||||||
values: apiDetail as ApiFormData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card card-border !mb-0 !mt-6">
|
|
||||||
<div className="card-header">
|
|
||||||
<h3 className="text-2xl font-medium text-label">API 수정</h3>
|
|
||||||
</div>
|
|
||||||
<ApimanForm
|
|
||||||
form={form}
|
|
||||||
authorizationNo={authorizationNo}
|
|
||||||
handleSave={async (data) => {
|
|
||||||
await updateApi(data);
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
handleDelete={deleteApi}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ModifyPage };
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import type { Page } from "@/shared/types";
|
|
||||||
import { client } from "@/shared/api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
|
|
||||||
export interface ApiRole {
|
|
||||||
roleId?: string;
|
|
||||||
/** @format int64 */
|
|
||||||
authorizationNo?: number;
|
|
||||||
authorizationName?: string;
|
|
||||||
urlPatternValue?: string;
|
|
||||||
httpMethodCode?: string;
|
|
||||||
/** @format int32 */
|
|
||||||
sortSeq?: number;
|
|
||||||
createdAt?: boolean;
|
|
||||||
}
|
|
||||||
/** API 에서 역할 추가 (API에 대한 역할) */
|
|
||||||
export interface ApiRoleByApi {
|
|
||||||
roleId?: string;
|
|
||||||
roleName?: string;
|
|
||||||
roleContent?: string;
|
|
||||||
useYn?: string;
|
|
||||||
/** @format int64 */
|
|
||||||
authorizationNo?: number;
|
|
||||||
mode?:string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiRoleParams {
|
|
||||||
authorizationNo?: number; // API 일련번호 검색
|
|
||||||
roleId?: string; // API 역할 검색
|
|
||||||
page?: number; // 페이지 번호
|
|
||||||
size?: number; // 페이지 크기
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiRoleFormData {
|
|
||||||
authorizationNo: number | null; // API 일련번호 (신규 등록 시 null)
|
|
||||||
authorizationName: string; // API 명
|
|
||||||
urlPatternValue: string; // URL 패턴
|
|
||||||
httpMethodCode: string; // HTTP 메소드
|
|
||||||
sortSeq: number; // 정렬 순서
|
|
||||||
useYn: string; // 사용 여부
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ApiRoleApi = {
|
|
||||||
getApiRoleList: async (params: ApiRoleParams) => {
|
|
||||||
const { data } = await client.get<Page<ApiRole>>(
|
|
||||||
`${ENDPOINT.USER_SERVICE.ROLES_AUTHRTS}`,
|
|
||||||
{ params }
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
getApiRoleByApiList: async (params: ApiRoleParams) => {
|
|
||||||
const { data } = await client.get<Page<ApiRoleByApi>>(
|
|
||||||
`${ENDPOINT.USER_SERVICE.ROLES_AUTHRTS}/api`,
|
|
||||||
{ params }
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
getApiRole: async (authorizationNo: number) => {
|
|
||||||
const { data } = await client.get<ApiRole>(`${ENDPOINT.USER_SERVICE.ROLES_AUTHRTS}/${authorizationNo}`);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
createApiRole: async (apiData: ApiRoleFormData) => {
|
|
||||||
const { data } = await client.post<ApiRole>(ENDPOINT.USER_SERVICE.ROLES_AUTHRTS, apiData);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateApiRole: async (authorizationNo: number, apiData: ApiRoleFormData) => {
|
|
||||||
const { data } = await client.put<ApiRole>(`${ENDPOINT.USER_SERVICE.ROLES_AUTHRTS}/${authorizationNo}`, apiData);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteApiRole: async (authorizationNo: number) => {
|
|
||||||
const { data } = await client.delete<void>(`${ENDPOINT.USER_SERVICE.ROLES_AUTHRTS}/${authorizationNo}`);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
//import { http } from "@/libs/axios";
|
|
||||||
import type { Page } from "@/shared/types";
|
|
||||||
import { client } from "@/shared/api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
|
|
||||||
export interface Group {
|
|
||||||
groupId: string;
|
|
||||||
groupNm: string;
|
|
||||||
groupSeCd: string;
|
|
||||||
groupExpln: string;
|
|
||||||
delYn: string;
|
|
||||||
regDt: string;
|
|
||||||
rgtr: string;
|
|
||||||
mdfcnDt: string | null;
|
|
||||||
mdfr: string | null;
|
|
||||||
authrtId: string;
|
|
||||||
siteId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupParams {
|
|
||||||
groupNm?: string;
|
|
||||||
groupSeCd?: string;
|
|
||||||
siteId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupFormData {
|
|
||||||
groupId: string;
|
|
||||||
groupNm: string;
|
|
||||||
groupSeCd: string;
|
|
||||||
groupExpln: string;
|
|
||||||
delYn: string;
|
|
||||||
authrtId: string;
|
|
||||||
siteId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const GroupApi = {
|
|
||||||
getGroupList: async (params: GroupParams) => {
|
|
||||||
const { data } = await client.get<Page<Group>>(
|
|
||||||
`${ENDPOINT.CMS_SERVICE.GROUP}`
|
|
||||||
, { params });
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getGroup: async (groupId: string) => {
|
|
||||||
const { data } = await client.get<Group>(`${ENDPOINT.CMS_SERVICE.GROUP}/${groupId}`);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
createGroup: async (groupData: GroupFormData) => {
|
|
||||||
const { data } = await client.post<Group>(ENDPOINT.CMS_SERVICE.GROUP, groupData);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateGroup: async (groupId: string, groupData: GroupFormData) => {
|
|
||||||
const { data } = await client.put<Group>(`${ENDPOINT.CMS_SERVICE.GROUP}/${groupId}`, groupData);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteGroup: async (groupId: string) => {
|
|
||||||
const { data } = await client.delete<void>(`${ENDPOINT.CMS_SERVICE.GROUP}/${groupId}`);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { GroupPage } from "./ui/group-page";
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { type ColumnDef } from "@tanstack/react-table";
|
|
||||||
import { Checkbox } from "@common/components/ui";
|
|
||||||
import { type Group } from "../api/group-api";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
export const columns: ColumnDef<Group>[] = [
|
|
||||||
/* {
|
|
||||||
id: "select",
|
|
||||||
header: ({ table }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={table.getIsAllPageRowsSelected()}
|
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
||||||
aria-label="전체 선택"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={row.getIsSelected()}
|
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
||||||
aria-label="행 선택"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
|
||||||
}, */
|
|
||||||
{
|
|
||||||
id: "no",
|
|
||||||
header: "NO",
|
|
||||||
accessorFn: (_, index) => index + 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "groupId",
|
|
||||||
header: "그룹 ID",
|
|
||||||
enableSorting: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "groupNm",
|
|
||||||
header: "그룹명",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "groupSeNm",
|
|
||||||
header: "그룹구분",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "delYn",
|
|
||||||
header: "삭제여부",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={row.original.delYn === "Y"}
|
|
||||||
disabled
|
|
||||||
aria-label="삭제여부"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "siteId",
|
|
||||||
header: "사이트 ID",
|
|
||||||
enableSorting: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "roleNm",
|
|
||||||
header: "권한",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "mdfrNm",
|
|
||||||
header: "수정자",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "mdfcnDt",
|
|
||||||
header: "수정일시",
|
|
||||||
cell: ({ row }) => dayjs(row.original.mdfcnDt).format("YYYY-MM-DD"),
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
// apps/cs/src/pages/group/ui/create-page.tsx
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useMutation } from "@tanstack/react-query";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { GroupApi, type GroupFormData } from "../api/group-api";
|
|
||||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
|
||||||
import { GroupForm } from "./group-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const groupFormSchema = z.object({
|
|
||||||
groupId: z.string(),
|
|
||||||
groupNm: z.string().min(1, "그룹명을 입력해주세요"),
|
|
||||||
groupSeCd: z.string().min(1, "그룹구분을 선택해주세요"),
|
|
||||||
groupExpln: z.string(),
|
|
||||||
delYn: z.string(),
|
|
||||||
authrtId: z.string(),
|
|
||||||
siteId: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
function CreatePage() {
|
|
||||||
const router = useRouter();
|
|
||||||
const { setMessage: alert } = useAlertStore((state) => state);
|
|
||||||
|
|
||||||
const { mutateAsync: createGroup } = useMutation({
|
|
||||||
mutationFn: (data: GroupFormData) => GroupApi.createGroup(data),
|
|
||||||
onSuccess: () => {
|
|
||||||
alert("등록되었습니다.", router.back);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
/*{
|
|
||||||
"timestamp": "2025-04-07T14:30:15.471581",
|
|
||||||
"message": "해당 권한이 존재하지 않습니다. ID = sd",
|
|
||||||
"status": 400,
|
|
||||||
"code": "B001",
|
|
||||||
"errors": []
|
|
||||||
}*/
|
|
||||||
if(error.response.data){
|
|
||||||
alert(error.response.data.message);
|
|
||||||
}else{
|
|
||||||
alert(error.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<GroupFormData>({
|
|
||||||
resolver: zodResolver(groupFormSchema),
|
|
||||||
defaultValues: {
|
|
||||||
groupId: "",
|
|
||||||
groupNm: "",
|
|
||||||
groupSeCd: "",
|
|
||||||
groupExpln: "",
|
|
||||||
delYn: "N",
|
|
||||||
authrtId: "",
|
|
||||||
siteId: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card card-border !mb-0 !mt-6">
|
|
||||||
<div className="card-header">
|
|
||||||
<h3 className="text-2xl font-medium text-label">그룹 등록</h3>
|
|
||||||
</div>
|
|
||||||
<GroupForm form={form} handleSave={createGroup} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { CreatePage };
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
// apps/cs/src/pages/group/ui/group-form.tsx
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
||||||
import { type UseFormReturn } from "react-hook-form";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
RadioGroup,
|
|
||||||
RadioGroupItem,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
} from "@common/components";
|
|
||||||
import { CodeSelect } from "@/entities/code";
|
|
||||||
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
|
|
||||||
import type { GroupFormData } from "../api/group-api";
|
|
||||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
|
||||||
|
|
||||||
interface GroupFormProps {
|
|
||||||
form: UseFormReturn<GroupFormData>;
|
|
||||||
groupId?: string;
|
|
||||||
handleSave: (data: GroupFormData) => Promise<void>;
|
|
||||||
handleDelete?: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function GroupForm({ form, groupId, handleSave, handleDelete }: GroupFormProps) {
|
|
||||||
const isModifyMode = !!groupId;
|
|
||||||
const router = useRouter();
|
|
||||||
const { confirm } = useConfirm();
|
|
||||||
const [onConfirm, setOnConfirm] = useState(false);
|
|
||||||
|
|
||||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
|
||||||
setOnConfirm(true);
|
|
||||||
await callback();
|
|
||||||
setOnConfirm(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (data: GroupFormData) => {
|
|
||||||
const confirmMessage = isModifyMode
|
|
||||||
? "수정하시겠습니까?"
|
|
||||||
: "저장하시겠습니까?";
|
|
||||||
|
|
||||||
await handleConfirm(async () => {
|
|
||||||
if (await confirm(confirmMessage)) {
|
|
||||||
handleSave(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDelete = async () => {
|
|
||||||
await handleConfirm(async () => {
|
|
||||||
if (await confirm("삭제하시겠습니까?")) {
|
|
||||||
handleDelete?.();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
||||||
<div className="card">
|
|
||||||
<Table variant="secondary">
|
|
||||||
<colgroup>
|
|
||||||
<col width="20%" />
|
|
||||||
<col width="80%" />
|
|
||||||
</colgroup>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>그룹 ID</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="groupId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="w-[12rem] !text-[1.3rem]"
|
|
||||||
disabled={isModifyMode}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>그룹명</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="groupNm"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="!text-[1.3rem]"
|
|
||||||
placeholder="그룹명을 입력하세요"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>그룹구분</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="groupSeCd"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<CodeSelect
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
upCd="GROUPSECD"
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>그룹설명</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="groupExpln"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="!text-[1.3rem]"
|
|
||||||
placeholder="그룹설명을 입력하세요"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>사이트 ID</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="siteId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="!text-[1.3rem]"
|
|
||||||
placeholder="사이트 ID를 입력하세요"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>권한 ID</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="authrtId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
className="!text-[1.3rem]"
|
|
||||||
placeholder="권한 ID를 입력하세요"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>삭제여부</TableHead>
|
|
||||||
<TableCell className="border">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="delYn"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroup
|
|
||||||
{...field}
|
|
||||||
name="delYn"
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="Y" id="delY" />
|
|
||||||
<Label htmlFor="delY">예</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="N" id="delN" />
|
|
||||||
<Label htmlFor="delN">아니오</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
color="white"
|
|
||||||
type="button"
|
|
||||||
onClick={() => router.back()}
|
|
||||||
>
|
|
||||||
목록
|
|
||||||
</Button>
|
|
||||||
<Button size="lg" type="submit">
|
|
||||||
저장
|
|
||||||
</Button>
|
|
||||||
{isModifyMode && (
|
|
||||||
<Button size="lg" color="red" type="button" onClick={onDelete}>
|
|
||||||
삭제
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
<ConfirmDialog open={onConfirm} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { GroupForm };
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import "@common/assets/styles/grid.css";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import {
|
|
||||||
getCoreRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
useReactTable
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
DataTable,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
Pagination
|
|
||||||
} from "@common/components";
|
|
||||||
|
|
||||||
import { GroupApi, type Group, type GroupParams } from "../api/group-api";
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
|
||||||
import { columns } from "../model/group-table-columns";
|
|
||||||
|
|
||||||
function GroupPage() {
|
|
||||||
const router = useRouter();
|
|
||||||
const [groupQuery, setGroupQuery] = useState<GroupParams>({
|
|
||||||
groupNm: "",
|
|
||||||
groupSeCd: "",
|
|
||||||
page: 0,
|
|
||||||
size: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: groupList, refetch } = useQuery({
|
|
||||||
queryKey: [ENDPOINT.CMS_SERVICE.GROUP, groupQuery],
|
|
||||||
queryFn: () => GroupApi.getGroupList(groupQuery),
|
|
||||||
enabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: groupList?.content ?? [],
|
|
||||||
columns,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
pageCount: groupList?.totalPages ?? -1,
|
|
||||||
state: {
|
|
||||||
pagination: {
|
|
||||||
pageIndex: groupQuery.page,
|
|
||||||
pageSize: groupQuery.size,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
onPaginationChange: (updater) => {
|
|
||||||
if (typeof updater === 'function') {
|
|
||||||
const newPagination = updater({
|
|
||||||
pageIndex: groupQuery.page,
|
|
||||||
pageSize: groupQuery.size,
|
|
||||||
});
|
|
||||||
setGroupQuery(prev => ({
|
|
||||||
...prev,
|
|
||||||
page: newPagination.pageIndex,
|
|
||||||
size: newPagination.pageSize,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
manualPagination: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setGroupQuery(prev => ({ ...prev, page: 0 })); // 검색 시 첫 페이지로 이동
|
|
||||||
refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowClick = (row: Group) => {
|
|
||||||
router.push(`/group/register/${row.groupId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refetch();
|
|
||||||
}, [groupQuery, refetch]); //[groupQuery.page, groupQuery.size]); // 페이지나 사이즈 변경 시 자동 조회
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card card-border !mt-6">
|
|
||||||
<div className="card card-border">
|
|
||||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
|
||||||
<Label htmlFor="" className="mr-2 text-label">
|
|
||||||
그룹명
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
|
||||||
value={groupQuery.groupNm}
|
|
||||||
onChange={(e) => setGroupQuery(prev => ({ ...prev, groupNm: e.target.value }))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button className="h-[3.2rem]" type="submit">
|
|
||||||
조회
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div className="card card-border">
|
|
||||||
<div className="card-header">
|
|
||||||
<h3 className="text-2xl font-medium text-label">그룹 목록</h3>
|
|
||||||
<Button onClick={() => router.push("/group/register")}>+등록</Button>
|
|
||||||
</div>
|
|
||||||
<div className="card !m-[1.2rem] h-[49rem] overflow-auto !p-0">
|
|
||||||
<DataTable table={table} onRowClick={handleRowClick} />
|
|
||||||
<div className="flex items-center justify-end space-x-2 py-4">
|
|
||||||
<div className="flex-1 text-lm text-muted-foreground">
|
|
||||||
총 {groupList?.totalElements ?? 0} 건
|
|
||||||
</div>
|
|
||||||
<Pagination
|
|
||||||
currentPage={groupQuery.page + 1}
|
|
||||||
totalPages={groupList?.totalPages ?? 0}
|
|
||||||
onPageChange={(page) =>
|
|
||||||
setGroupQuery(prev => ({ ...prev, page: page - 1 }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
value={String(groupQuery.size)}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
setGroupQuery(prev => ({ ...prev, page: 0, size: Number(value) }))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 w-[70px]">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{[2,10, 20, 30, 40, 50].map((pageSize) => (
|
|
||||||
<SelectItem key={pageSize} value={String(pageSize)}>
|
|
||||||
{pageSize}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { GroupPage };
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
export const PORT = process.env.NEXT_PUBLIC_PORT || "3000";
|
export const PORT = process.env.NEXT_PUBLIC_PORT || "3000";
|
||||||
export const PROXY_HOST =
|
export const PROXY_HOST =
|
||||||
process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`;
|
process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`;
|
||||||
|
|
||||||
export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL;
|
|
||||||
export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "CS";
|
export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "CS";
|
||||||
|
|
||||||
export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||||
export const ACCESS_TOKEN =
|
export const ACCESS_TOKEN =
|
||||||
process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
|
process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
|
||||||
|
|||||||
Generated
+137
-4
@@ -37,6 +37,115 @@ importers:
|
|||||||
specifier: 2.4.2
|
specifier: 2.4.2
|
||||||
version: 2.4.2
|
version: 2.4.2
|
||||||
|
|
||||||
|
apps/common/board:
|
||||||
|
dependencies:
|
||||||
|
'@common/assets':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../packages/common/assets
|
||||||
|
'@common/components':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../packages/common/components
|
||||||
|
'@frontend-opensource/use-react-hooks':
|
||||||
|
specifier: ^1.1.4
|
||||||
|
version: 1.1.4
|
||||||
|
'@hookform/resolvers':
|
||||||
|
specifier: ^4.1.3
|
||||||
|
version: 4.1.3(react-hook-form@7.54.2)
|
||||||
|
'@minoru/react-dnd-treeview':
|
||||||
|
specifier: ^3.5.0
|
||||||
|
version: 3.5.0(dnd-core@16.0.1)(react-dnd@16.0.1)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-label':
|
||||||
|
specifier: ^2.1.2
|
||||||
|
version: 2.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot':
|
||||||
|
specifier: ^1.1.2
|
||||||
|
version: 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||||
|
'@tanstack/react-query':
|
||||||
|
specifier: ^5.67.1
|
||||||
|
version: 5.67.1(react@18.3.1)
|
||||||
|
'@tanstack/react-table':
|
||||||
|
specifier: ^8.21.2
|
||||||
|
version: 8.21.2(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@tinymce/tinymce-react':
|
||||||
|
specifier: ^6.1.0
|
||||||
|
version: 6.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
axios:
|
||||||
|
specifier: ^1.8.1
|
||||||
|
version: 1.8.1
|
||||||
|
chart.js:
|
||||||
|
specifier: ^4.4.8
|
||||||
|
version: 4.4.8
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.13
|
||||||
|
version: 1.11.13
|
||||||
|
js-cookie:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
|
next:
|
||||||
|
specifier: 15.1.7
|
||||||
|
version: 15.1.7(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
react-chartjs-2:
|
||||||
|
specifier: ^5.3.0
|
||||||
|
version: 5.3.0(chart.js@4.4.8)(react@18.3.1)
|
||||||
|
react-dnd:
|
||||||
|
specifier: ^16.0.1
|
||||||
|
version: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1)
|
||||||
|
react-hook-form:
|
||||||
|
specifier: ^7.54.2
|
||||||
|
version: 7.54.2(react@18.3.1)
|
||||||
|
tailwindcss-animate:
|
||||||
|
specifier: ^1.0.7
|
||||||
|
version: 1.0.7(tailwindcss@3.4.17)
|
||||||
|
tus-js-client:
|
||||||
|
specifier: ^4.3.1
|
||||||
|
version: 4.3.1
|
||||||
|
zod:
|
||||||
|
specifier: ^3.24.2
|
||||||
|
version: 3.24.2
|
||||||
|
zustand:
|
||||||
|
specifier: ^5.0.3
|
||||||
|
version: 5.0.3(@types/react@18.3.18)(react@18.3.1)
|
||||||
|
devDependencies:
|
||||||
|
'@eslint/eslintrc':
|
||||||
|
specifier: ^3
|
||||||
|
version: 3.3.0
|
||||||
|
'@faker-js/faker':
|
||||||
|
specifier: ^9.5.0
|
||||||
|
version: 9.5.0
|
||||||
|
'@types/js-cookie':
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.13.1
|
||||||
|
version: 22.13.9
|
||||||
|
'@types/webpack':
|
||||||
|
specifier: ^5.28.5
|
||||||
|
version: 5.28.5
|
||||||
|
eslint:
|
||||||
|
specifier: ^9
|
||||||
|
version: 9.21.0
|
||||||
|
eslint-config-next:
|
||||||
|
specifier: 15.1.7
|
||||||
|
version: 15.1.7(eslint@9.21.0)(typescript@5.6.3)
|
||||||
|
postcss:
|
||||||
|
specifier: ^8
|
||||||
|
version: 8.5.3
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.4.2
|
||||||
|
version: 3.5.2
|
||||||
|
prettier-plugin-tailwindcss:
|
||||||
|
specifier: ^0.6.11
|
||||||
|
version: 0.6.11(prettier@3.5.2)
|
||||||
|
steiger:
|
||||||
|
specifier: ^0.5.5
|
||||||
|
version: 0.5.5(typescript@5.6.3)
|
||||||
|
tailwindcss:
|
||||||
|
specifier: ^3.4.1
|
||||||
|
version: 3.4.17
|
||||||
|
typescript:
|
||||||
|
specifier: ^5
|
||||||
|
version: 5.6.3
|
||||||
|
|
||||||
apps/cs:
|
apps/cs:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@common/assets':
|
'@common/assets':
|
||||||
@@ -1700,7 +1809,7 @@ packages:
|
|||||||
dnd-multi-backend: 8.1.2(dnd-core@16.0.1)
|
dnd-multi-backend: 8.1.2(dnd-core@16.0.1)
|
||||||
framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dnd: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1)
|
react-dnd: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1)
|
||||||
react-dnd-html5-backend: 16.0.1
|
react-dnd-html5-backend: 16.0.1
|
||||||
react-dnd-touch-backend: 16.0.1
|
react-dnd-touch-backend: 16.0.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
@@ -3508,7 +3617,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==}
|
resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/prop-types@15.7.14:
|
/@types/prop-types@15.7.14:
|
||||||
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||||
@@ -3537,7 +3645,7 @@ packages:
|
|||||||
/@types/webpack@5.28.5:
|
/@types/webpack@5.28.5:
|
||||||
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
|
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.5
|
'@types/node': 22.13.9
|
||||||
tapable: 2.2.1
|
tapable: 2.2.1
|
||||||
webpack: 5.98.0
|
webpack: 5.98.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -6178,7 +6286,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
|
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
|
||||||
engines: {node: '>= 10.13.0'}
|
engines: {node: '>= 10.13.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.5
|
'@types/node': 22.13.9
|
||||||
merge-stream: 2.0.0
|
merge-stream: 2.0.0
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
dev: true
|
dev: true
|
||||||
@@ -7138,6 +7246,31 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-dnd@16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/hoist-non-react-statics': '>= 3.3.1'
|
||||||
|
'@types/node': '>= 12'
|
||||||
|
'@types/react': ^18.3.18
|
||||||
|
react: ^18.3.1
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/hoist-non-react-statics':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@react-dnd/invariant': 4.0.2
|
||||||
|
'@react-dnd/shallowequal': 4.0.2
|
||||||
|
'@types/node': 22.13.9
|
||||||
|
'@types/react': 18.3.18
|
||||||
|
dnd-core: 16.0.1
|
||||||
|
fast-deep-equal: 3.1.3
|
||||||
|
hoist-non-react-statics: 3.3.2
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-docgen-typescript@2.2.2(typescript@5.6.3):
|
/react-docgen-typescript@2.2.2(typescript@5.6.3):
|
||||||
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
packages:
|
packages:
|
||||||
- "apps/*"
|
- "apps/*"
|
||||||
|
- "apps/common/*"
|
||||||
- "packages/*"
|
- "packages/*"
|
||||||
- "packages/common/*"
|
- "packages/common/*"
|
||||||
|
|||||||
Reference in New Issue
Block a user