diff --git a/apps/cs/src/pages/admin/api/manager-ip-service.ts b/apps/cs/src/pages/admin/api/manager-ip-service.ts new file mode 100644 index 000000000..6275fb714 --- /dev/null +++ b/apps/cs/src/pages/admin/api/manager-ip-service.ts @@ -0,0 +1,50 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + ManagerIp, + ManagerIpInfo, + ManagerIpList, + ManagerIpParams, + ManagerIpService, +} from "../model/manager-ip-interface"; + +export class ManagerIpApiService implements ManagerIpService { + public async getManagerIpList( + params: ManagerIpParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MANAGER_IP}`, + { params }, + ); + return response.data; + } + + public async getManagerIpInfo(mngNo: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MANAGER_IP}/${mngNo}`, + ); + return response.data; + } + + public async saveManagerIp(data: ManagerIp[]): Promise { + const response = await client.post( + ENDPOINT.CMS_SERVICE.MANAGER_IP, + data, + ); + return response.data; + } + + public async updateManagerIp(mngNo: string, data: ManagerIp): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MANAGER_IP}/${mngNo}`, + data, + ); + return response.data; + } + + public async deleteManagerIp(mngNo: string): Promise { + await client.delete(`${ENDPOINT.CMS_SERVICE.MANAGER_IP}/${mngNo}`); + } +} + +export const ManagerIpApi = new ManagerIpApiService(); diff --git a/apps/cs/src/pages/admin/model/manager-ip-interface.ts b/apps/cs/src/pages/admin/model/manager-ip-interface.ts new file mode 100644 index 000000000..f698db533 --- /dev/null +++ b/apps/cs/src/pages/admin/model/manager-ip-interface.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface ManagerIpParams extends PageableParams { + ipAddr?: string; + userUnqId?: string; +} + +export const managerIpSchema = z.object({ + mngNo: z.number().optional(), + mode: z.string(), + ipAddr: z.string().max(15), + userUnqId: z.string(), + regDt: z.string().optional(), + rgtr: z.string().optional(), + mdfcnDt: z.string().optional(), + mdfr: z.string().optional(), +}); + +export const managerIpFormSchema = z.object({ + ips: z.array( + managerIpSchema.omit({ + regDt: true, + rgtr: true, + mdfcnDt: true, + mdfr: true, + }), + ), +}); + +export type ManagerIp = z.infer; +export type ManagerIpFormData = z.infer; +export type ManagerIpList = PageableList; +export type ManagerIpInfo = ManagerIp; + +export interface ManagerIpService { + getManagerIpList: (params: ManagerIpParams) => Promise; + getManagerIpInfo: (mngNo: string) => Promise; + saveManagerIp: (data: ManagerIp[]) => Promise; + updateManagerIp: (mngNo: string, data: ManagerIp) => Promise; + deleteManagerIp: (mngNo: string) => Promise; +} diff --git a/apps/cs/src/pages/admin/model/table-columns.tsx b/apps/cs/src/pages/admin/model/table-columns.tsx index 6b6574749..83780c981 100644 --- a/apps/cs/src/pages/admin/model/table-columns.tsx +++ b/apps/cs/src/pages/admin/model/table-columns.tsx @@ -1,6 +1,7 @@ import dayjs from "dayjs"; import type { ColumnDef } from "@tanstack/react-table"; import type { AdminUserList } from "@/entities/user"; +import { ManagerIpDialog } from "../ui/manager-ip-dialog"; export const columns: ColumnDef[] = [ { @@ -38,4 +39,11 @@ export const columns: ColumnDef[] = [ header: "최종수정일", cell: ({ row }) => dayjs(row.original.mdfcnDt).format("YYYY-MM-DD"), }, + { + id: "action", + header: "관리", + cell: ({ row }) => { + return ; + }, + }, ]; diff --git a/apps/cs/src/pages/admin/ui/admin-page.tsx b/apps/cs/src/pages/admin/ui/admin-page.tsx index f5b971834..a4cd5265d 100644 --- a/apps/cs/src/pages/admin/ui/admin-page.tsx +++ b/apps/cs/src/pages/admin/ui/admin-page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { getCoreRowModel, @@ -26,12 +27,11 @@ import { type UserSearchParams, type AdminUserListResponse, } from "@/entities/user"; +import { GroupUsersApi } from "@/entities/group-user"; import { ENDPOINT } from "@/shared/config"; import { useDataTable, useAlertStore } from "@/shared/lib"; import { columns } from "../model/table-columns"; import { AddAdminDialog } from "./add-admin-dialog"; -import { GroupUsersApi } from "@/entities/group-user"; -import { useConfirm } from "@frontend-opensource/use-react-hooks"; function AdminPage() { const queryClient = useQueryClient(); diff --git a/apps/cs/src/pages/admin/ui/manager-ip-dialog.tsx b/apps/cs/src/pages/admin/ui/manager-ip-dialog.tsx new file mode 100644 index 000000000..a97947dc5 --- /dev/null +++ b/apps/cs/src/pages/admin/ui/manager-ip-dialog.tsx @@ -0,0 +1,241 @@ +"use client"; + +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useForm, useFieldArray } from "react-hook-form"; +import { + Dialog, + DialogTrigger, + Button, + DialogContent, + DialogHeader, + DialogTitle, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Input, + DialogFooter, + DialogClose, + Checkbox, +} from "@common/components"; +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useMutationWrapper } from "@/shared/lib"; +import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; +import { ManagerIpApi } from "../api/manager-ip-service"; +import { + managerIpFormSchema, + type ManagerIp, + type ManagerIpFormData, +} from "../model/manager-ip-interface"; + +type ManagerIpDialogProps = { + userUnqId: string; +}; + +function ManagerIpDialog({ userUnqId }: ManagerIpDialogProps) { + const { confirm } = useConfirm(); + const alert = useAlertStore((state) => state.setMessage); + + const [open, setOpen] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + + const { data: managerIpList } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MANAGER_IP, open], + queryFn: () => ManagerIpApi.getManagerIpList({ userUnqId }), + select: (data) => { + const ips = data.content.map((item) => ({ + ...item, + mode: "U", + })); + initialForm(); + return ips; + }, + enabled: open, + }); + + const { mutateAsync } = useMutationWrapper({ + mutationFn: (data: ManagerIp[]) => ManagerIpApi.saveManagerIp(data), + }); + + const form = useForm({ + resolver: zodResolver(managerIpFormSchema), + values: { + ips: managerIpList ?? [], + }, + }); + + const { fields, append, remove, update } = useFieldArray({ + control: form.control, + name: "ips", + }); + + const initialForm = () => { + form.reset(); + setSelectedRows([]); + }; + + const onSubmit = async (data: ManagerIpFormData) => { + if (await confirm("저장하시겠습니까?")) { + await mutateAsync(data.ips); + alert("저장되었습니다.", () => { + setOpen(false); + }); + } + }; + + const handleAddIP = () => { + append({ + mngNo: 0, + mode: "C", + ipAddr: "", + userUnqId, + }); + }; + + const handleSelectAll = () => { + if (selectedRows.length === fields.length) { + setSelectedRows([]); + } else { + setSelectedRows(fields.map((_, index) => index)); + } + }; + + const handleSelectRow = (i: number) => { + setSelectedRows((prev) => + prev.includes(i) ? prev.filter((j) => j !== i) : [...prev, i], + ); + }; + + const handleDeleteIP = async () => { + if (selectedRows.length === 0) return; + + if (await confirm("선택한 IP를 삭제하시겠습니까?")) { + const sortedIndices = [...selectedRows].sort((a, b) => b - a); + + for (const i of sortedIndices) { + const field = fields[i]; + if (field?.mngNo) { + if (field.mode === "C") { + remove(i); + } else { + update(i, { + ...field, + mode: "D", + }); + } + } else { + remove(i); + } + } + + setSelectedRows([]); + } + }; + + const isAllSelected = + fields.length > 0 && selectedRows.length === fields.length; + + return ( + + + + + + + 접속 IP 관리 + +
+ { + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + > +
+ + +
+
+ + + + + + + + + + + IP 주소 + + + + {fields.map((field, i) => { + if (field.mode === "D") return null; + return ( + + + handleSelectRow(i)} + /> + + + ( + + + + + + )} + /> + + + ); + })} + +
+
+ + + + + + +
+ +
+
+ ); +} + +export { ManagerIpDialog }; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index 54d038dbf..35b3b6707 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -47,6 +47,7 @@ export const ENDPOINT = { ZIPS: "/cms-service/api/v1/zips", GROUPS: "/cms-service/api/v1/groups", GROUP_USERS: "/cms-service/api/v1/group-users", + MANAGER_IP: "/cms-service/api/v1/mgrip", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs",