group 관리추가
This commit is contained in:
@@ -17,3 +17,4 @@ dist
|
|||||||
|
|
||||||
# env
|
# env
|
||||||
.env*
|
.env*
|
||||||
|
.qodo
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { GroupPage } from "@/pages/group";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <GroupPage />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { CreatePage } from "@/pages/group/ui/create-page";
|
||||||
|
export default CreatePage;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export { GroupApi } from "./api/group-api";
|
||||||
|
export {
|
||||||
|
type GroupParams,
|
||||||
|
type Group,
|
||||||
|
type GroupFormData,
|
||||||
|
} from "./model/types";
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
//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;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { GroupPage } from "./ui/group-page";
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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,
|
||||||
|
}, */
|
||||||
|
{
|
||||||
|
accessorKey: "groupId",
|
||||||
|
header: "그룹 ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "groupNm",
|
||||||
|
header: "그룹명",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "groupSeNm",
|
||||||
|
header: "그룹구분",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "delYn",
|
||||||
|
header: "삭제여부",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={row.original.delYn === "Y"}
|
||||||
|
disabled
|
||||||
|
aria-label="삭제여부"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "roleNm",
|
||||||
|
header: "권한",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "mdfrNm",
|
||||||
|
header: "수정자",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "mdfcnDt",
|
||||||
|
header: "수정일시",
|
||||||
|
cell: ({ row }) => dayjs(row.original.mdfcnDt).format("YYYY-MM-DD"),
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// 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 };
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
// 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 };
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
"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.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 };
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// apps/cs/src/pages/group/ui/modify-page.tsx
|
||||||
|
"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 { GroupApi, type GroupFormData } from "../api/group-api";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||||
|
import { GroupForm } from "./group-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// Group 스키마 정의
|
||||||
|
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 ModifyPage({ groupId }: { groupId: string }) {
|
||||||
|
const router = useRouter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { setMessage: alert } = useAlertStore((state) => state);
|
||||||
|
|
||||||
|
const { data: groupDetail } = useQuery({
|
||||||
|
queryKey: [ENDPOINT.CMS_SERVICE.GROUP, groupId],
|
||||||
|
queryFn: () => GroupApi.getGroup(groupId),
|
||||||
|
select: (data) => {
|
||||||
|
const keys = Object.keys(groupFormSchema.shape);
|
||||||
|
const filteredData = Object.fromEntries(
|
||||||
|
Object.entries(data).filter(([key]) => keys.includes(key)),
|
||||||
|
);
|
||||||
|
return filteredData;
|
||||||
|
},
|
||||||
|
enabled: !!groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutateAsync: updateGroup } = useMutation({
|
||||||
|
mutationFn: (data: GroupFormData) => GroupApi.updateGroup(groupId, data),
|
||||||
|
onSuccess: () => {
|
||||||
|
alert("수정되었습니다.", () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [ENDPOINT.CMS_SERVICE.GROUP],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if(error.response.data){
|
||||||
|
alert(error.response.data.message);
|
||||||
|
}else{
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutateAsync: deleteGroup } = useMutation({
|
||||||
|
mutationFn: () => GroupApi.deleteGroup(groupId),
|
||||||
|
onSuccess: () => {
|
||||||
|
alert("삭제되었습니다.", router.back);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if(error.response.data){
|
||||||
|
alert(error.response.data.message);
|
||||||
|
}else{
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm<GroupFormData>({
|
||||||
|
resolver: zodResolver(groupFormSchema),
|
||||||
|
values: groupDetail as GroupFormData,
|
||||||
|
});
|
||||||
|
|
||||||
|
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}
|
||||||
|
groupId={groupId}
|
||||||
|
handleSave={updateGroup}
|
||||||
|
handleDelete={deleteGroup}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ModifyPage };
|
||||||
@@ -21,6 +21,7 @@ export const ENDPOINT = {
|
|||||||
CONTENTS: "/cms-service/api/v1/contents",
|
CONTENTS: "/cms-service/api/v1/contents",
|
||||||
USER_OGNZ: "/cms-service/api/v1/user-ognz",
|
USER_OGNZ: "/cms-service/api/v1/user-ognz",
|
||||||
IMAGES: "/cms-service/api/v1/images",
|
IMAGES: "/cms-service/api/v1/images",
|
||||||
|
GROUP: "/cms-service/api/v1/groups",
|
||||||
},
|
},
|
||||||
BOARD_SERVICE: {
|
BOARD_SERVICE: {
|
||||||
BBS: "/board-service/api/v1/bbs",
|
BBS: "/board-service/api/v1/bbs",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
.qodo
|
||||||
@@ -22,3 +22,4 @@ export * from "./slider";
|
|||||||
export * from "./switch";
|
export * from "./switch";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
export * from "./tabs";
|
export * from "./tabs";
|
||||||
|
export * from "./pagination";
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// apps/cs/src/common/components/ui/pagination.tsx
|
||||||
|
import { Button } from "../button";
|
||||||
|
|
||||||
|
interface PaginationProps {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) {
|
||||||
|
// 표시할 페이지 번호 범위 계산
|
||||||
|
const getPageRange = () => {
|
||||||
|
const range: number[] = [];
|
||||||
|
const maxButtons = 5; // 한 번에 보여줄 최대 페이지 버튼 수
|
||||||
|
const halfMaxButtons = Math.floor(maxButtons / 2);
|
||||||
|
|
||||||
|
let startPage = Math.max(currentPage - halfMaxButtons, 1);
|
||||||
|
let endPage = Math.min(startPage + maxButtons - 1, totalPages);
|
||||||
|
|
||||||
|
if (endPage - startPage + 1 < maxButtons) {
|
||||||
|
startPage = Math.max(endPage - maxButtons + 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
range.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{/* 첫 페이지 버튼 */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange(1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
{"<<"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 이전 페이지 버튼 */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
{"<"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 페이지 번호 버튼들 */}
|
||||||
|
{getPageRange().map((pageNum) => (
|
||||||
|
<Button
|
||||||
|
key={pageNum}
|
||||||
|
variant={currentPage === pageNum ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange(pageNum)}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 다음 페이지 버튼 */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
{">"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 마지막 페이지 버튼 */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onPageChange(totalPages)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
{">>"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Pagination };
|
||||||
Reference in New Issue
Block a user