diff --git a/src/apis/dashboards/types.ts b/src/apis/dashboards/types.ts index b012cc8..f4018e7 100644 --- a/src/apis/dashboards/types.ts +++ b/src/apis/dashboards/types.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { DASHBOARD_FORM_ERROR_MESSAGE, DASHBOARD_FORM_VALID_LENGTH } from '@/constants/dashboard'; import { DEFAULT_COLORS } from '@/constants/colors'; +import { userSchema } from '@/apis/users/types'; // base pagination params 타입 (필요시 공용으로 추출) export type BasePaginationParams = { @@ -45,13 +46,6 @@ export const dashboardFormSchema = z.object({ }); export type DashboardFormType = z.infer; -// TODO : 임시 유저 스키마(추후 /apis/auth 쪽에서 작성된 schema 임포트 필요) -export const userSchema = z.object({ - id: z.number(), - email: z.string().email(), - nickname: z.string(), -}); - export const invitationUserSchema = userSchema.pick({ id: true, email: true, diff --git a/src/apis/members/index.ts b/src/apis/members/index.ts new file mode 100644 index 0000000..c0404d7 --- /dev/null +++ b/src/apis/members/index.ts @@ -0,0 +1,23 @@ +import axiosClientHelper from '@/utils/network/axiosClientHelper'; +import { MembersParams, MembersRespons, membersResponseSchema } from './types'; + +export const getMembers = async ({ page, size, dashboardId }: MembersParams) => { + const response = await axiosClientHelper.get('/members', { + params: { + size: size || 20, + page: page || 1, + dashboardId, + }, + }); + + const result = membersResponseSchema.safeParse(response.data); + if (!result.success) { + throw new Error('서버에서 받은 데이터가 예상과 다릅니다.'); + } + return result.data; +}; + +export const deleteMember = async (memberId: number) => { + const response = await axiosClientHelper.delete(`/members/${memberId}`); + return response.data; +}; diff --git a/src/apis/members/quries.ts b/src/apis/members/quries.ts new file mode 100644 index 0000000..2a6ae49 --- /dev/null +++ b/src/apis/members/quries.ts @@ -0,0 +1,31 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { deleteMember, getMembers } from '.'; + +export const useMembersQuery = (page: number, size: number, dashboardId: number) => { + return useQuery({ + queryKey: ['members', dashboardId, page, size], + queryFn: () => + getMembers({ + page, + size, + dashboardId, + }), + }); +}; + +export const useMembersMutation = () => { + const queryClient = useQueryClient(); + + const remove = useMutation({ + mutationFn: ({ memberId }: { memberId: number; dashboardId: number }) => { + return deleteMember(memberId); + }, + onSuccess: (_, { dashboardId }) => { + queryClient.invalidateQueries({ queryKey: ['members', dashboardId] }); + }, + }); + + return { + remove: remove.mutateAsync, + }; +}; diff --git a/src/apis/members/types.ts b/src/apis/members/types.ts new file mode 100644 index 0000000..63b0525 --- /dev/null +++ b/src/apis/members/types.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { userSchema } from '../users/types'; +import { BasePaginationParams } from '../dashboards/types'; + +export type MembersParams = BasePaginationParams & { + dashboardId: number; +}; + +export const memberSchema = userSchema.extend({ + userId: z.number(), + isOwner: z.boolean(), +}); +export type Member = z.infer; + +export const membersResponseSchema = z.object({ + totalCount: z.number(), + members: z.array(memberSchema), +}); + +export type MembersRespons = z.infer; diff --git a/src/app/(after-login)/dashboard/[id]/edit/page.tsx b/src/app/(after-login)/dashboard/[id]/edit/page.tsx index 73cba66..b2d7195 100644 --- a/src/app/(after-login)/dashboard/[id]/edit/page.tsx +++ b/src/app/(after-login)/dashboard/[id]/edit/page.tsx @@ -1,52 +1,43 @@ -'use client'; - -import { useParams, useRouter } from 'next/navigation'; -import { useDashboardMutation } from '@/apis/dashboards/queries'; -import Button from '@/components/ui/Button/Button'; -import useAlert from '@/hooks/useAlert'; -import { getErrorMessage } from '@/utils/errorMessage'; import DetailModify from '@/components/dashboard/DetailModify'; import DetailMembers from '@/components/dashboard/DetailMembers'; import DetailInvited from '@/components/dashboard/DetailInvited'; import GoBackLink from '@/components/ui/Link/GoBackLink'; -export default function DashboardEditPage() { - const router = useRouter(); - const alert = useAlert(); - const { id } = useParams<{ id: string }>(); - const { remove } = useDashboardMutation(); +import DetailDelete from '@/components/dashboard/DetailDelete'; +import { Suspense } from 'react'; +import axiosServerHelper from '@/utils/network/axiosServerHelper'; +import { redirect } from 'next/navigation'; +import { Dashboard } from '@/apis/dashboards/types'; + +export default async function DashboardEditPage({ params }: { params: Promise<{ id: string }> }) { + const id = (await params).id; + const response = await axiosServerHelper(`/dashboards/${id}`); + const { createdByMe } = response.data; - const handleDelete = async () => { - try { - await remove(Number(id)); - alert('삭제했습니다.'); - router.push(`/mydashboard`); - } catch (error) { - const message = getErrorMessage(error); - alert(message); - } - }; + if (!createdByMe) { + redirect('/mydashboard'); + } return (
-
- {/* 대시보드 정보 */} - + loading...
}> +
+ {/* 대시보드 정보 */} + - {/* 구성원 리스트 */} - + {/* 구성원 리스트 */} + - {/* 초대내역 */} - + {/* 초대내역 */} + - {/* 대시보드 삭제 */} - -
+ {/* 대시보드 삭제 */} + +
+ ); } diff --git a/src/components/dashboard/DetailDelete.tsx b/src/components/dashboard/DetailDelete.tsx new file mode 100644 index 0000000..11b17e2 --- /dev/null +++ b/src/components/dashboard/DetailDelete.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { useDashboardMutation } from '@/apis/dashboards/queries'; +import Button from '@/components/ui/Button/Button'; +import useAlert from '@/hooks/useAlert'; +import { getErrorMessage } from '@/utils/errorMessage'; + +export default function DetailDelete() { + const { id } = useParams<{ id: string }>(); + const router = useRouter(); + const alert = useAlert(); + const { remove } = useDashboardMutation(); + + const handleDelete = async () => { + try { + await remove(Number(id)); + alert('삭제했습니다.'); + router.push(`/mydashboard`); + } catch (error) { + const message = getErrorMessage(error); + alert(message); + } + }; + + return ( + + ); +} diff --git a/src/components/dashboard/DetailInvited.tsx b/src/components/dashboard/DetailInvited.tsx index 6bf5d04..7f8dc96 100644 --- a/src/components/dashboard/DetailInvited.tsx +++ b/src/components/dashboard/DetailInvited.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useRef, useState } from 'react'; import Button from '@/components/ui/Button/Button'; import { Card, CardTitle } from '@/components/ui/Card/Card'; diff --git a/src/components/dashboard/DetailMembers.tsx b/src/components/dashboard/DetailMembers.tsx index 22bc52f..fa421ff 100644 --- a/src/components/dashboard/DetailMembers.tsx +++ b/src/components/dashboard/DetailMembers.tsx @@ -1,10 +1,100 @@ +'use client'; + import { Card, CardTitle } from '@/components/ui/Card/Card'; +import { useState } from 'react'; +import { useParams } from 'next/navigation'; +import useAlert from '@/hooks/useAlert'; +import PaginationWithCounter from '@/components/pagination/PaginationWithCounter'; +import { useMembersMutation, useMembersQuery } from '@/apis/members/quries'; +import { Table, TableBody, TableCell, TableCol, TableColGroup, TableHead, TableHeadCell, TableRow } from '@/components/ui/Table/Table'; +import { isAxiosError } from 'axios'; +import Button from '../ui/Button/Button'; +import { getErrorMessage } from '@/utils/errorMessage'; +import Avatar from '../ui/Avatar/Avatar'; + +const PAGE_SIZE = 5; export default function DetailMembers() { + const { id } = useParams<{ id: string }>(); + const [page, setPage] = useState(1); + const { data, error, isLoading } = useMembersQuery(page, PAGE_SIZE, Number(id)); + const { remove } = useMembersMutation(); + const alert = useAlert(); + + const removeMember = async (memberId: number) => { + try { + await remove({ memberId, dashboardId: Number(id) }); + alert('맴버를 삭제했습니다.'); + } catch (error) { + const message = getErrorMessage(error); + alert(message); + } + }; + const notAllowed = isAxiosError(error) && error.status === 403; + return ( - 구성원 -
구성원 리스트(작업필요)
+ + 구성원 +
+ +
+
+ + + + + + + + 이름 + + + + + {isLoading && ( + + +
구성원을 가져오는 중입니다.
+
+
+ )} + {notAllowed && ( + + +
권한이 없습니다.
+
+
+ )} + {data?.members.length === 0 && ( + + +
구성원이 없습니다.
+
+
+ )} + {data?.members.map((item) => ( + + +
+ + {item.nickname} +
+
+ + + +
+ ))} +
+
); } diff --git a/src/components/dashboard/DetailModify.tsx b/src/components/dashboard/DetailModify.tsx index 292547a..70ea64e 100644 --- a/src/components/dashboard/DetailModify.tsx +++ b/src/components/dashboard/DetailModify.tsx @@ -1,22 +1,17 @@ -import { useParams } from 'next/navigation'; +'use client'; + import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useDashboardMutation, useDashboardQuery } from '@/apis/dashboards/queries'; +import { useDashboardMutation } from '@/apis/dashboards/queries'; import { Card, CardTitle } from '@/components/ui/Card/Card'; import { Input } from '@/components/ui/Field'; import ColorPicker from '@/components/ui/Chip/ColorPicker'; -import { dashboardFormSchema, DashboardFormType } from '@/apis/dashboards/types'; +import { Dashboard, dashboardFormSchema, DashboardFormType } from '@/apis/dashboards/types'; import { getErrorMessage } from '@/utils/errorMessage'; import Button from '@/components/ui/Button/Button'; -import { useEffect } from 'react'; -import { DEFAULT_COLORS } from '@/constants/colors'; import useAlert from '@/hooks/useAlert'; -export default function DetailModify() { - const { id } = useParams<{ id: string }>(); - const { data, isLoading } = useDashboardQuery(Number(id)); - const alert = useAlert(); - +export default function DetailModify({ data }: { data: Dashboard }) { const { handleSubmit, register, @@ -27,26 +22,20 @@ export default function DetailModify() { resolver: zodResolver(dashboardFormSchema), mode: 'onBlur', defaultValues: { - title: '', - color: DEFAULT_COLORS[0], + title: data.title, + color: data.color, }, }); const { update } = useDashboardMutation(); - - useEffect(() => { - //react query의 데이터가 오기전에 rhf의 기본값이 셋팅되어서 effect를 통해 재설정 - if (data) { - reset({ - title: data.title, - color: data.color, - }); - } - }, [data, reset]); + const alert = useAlert(); const onSubmit = async (formData: DashboardFormType) => { try { - await update({ id: Number(id), ...formData }); - reset(); + await update({ id: Number(data.id), ...formData }); + reset({ + title: formData.title, + color: formData.color, + }); alert('수정했습니다.'); } catch (error) { const message = getErrorMessage(error); @@ -58,33 +47,27 @@ export default function DetailModify() { return ( - {isLoading ? ( -
대시보드 정보를 가져오는 중입니다.
- ) : ( - <> - {data?.title} -
-
- - { - return field.onChange(value)} />; - }} - /> - -
-
- - )} + {data.title} +
+
+ + { + return field.onChange(value)} />; + }} + /> + +
+
); }