From 96892005683e8870d2f7e1fd6e74b955fc932c90 Mon Sep 17 00:00:00 2001 From: jspark2000 Date: Fri, 22 Mar 2024 05:45:03 +0000 Subject: [PATCH] feat: implement update roster page --- .../update/_components/UpdateRosterForm.tsx | 280 ++++++++++++++++++ .../app/console/roster/[id]/update/page.tsx | 23 ++ .../roster/_components/RosterListTable.tsx | 8 +- .../new/_components/CreateRosterForm.tsx | 17 +- frontend/src/app/console/roster/page.tsx | 6 - frontend/src/lib/actions.ts | 6 +- frontend/src/lib/forms.ts | 14 +- 7 files changed, 338 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/console/roster/[id]/update/_components/UpdateRosterForm.tsx create mode 100644 frontend/src/app/console/roster/[id]/update/page.tsx diff --git a/frontend/src/app/console/roster/[id]/update/_components/UpdateRosterForm.tsx b/frontend/src/app/console/roster/[id]/update/_components/UpdateRosterForm.tsx new file mode 100644 index 0000000..819242b --- /dev/null +++ b/frontend/src/app/console/roster/[id]/update/_components/UpdateRosterForm.tsx @@ -0,0 +1,280 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { RosterStatus, RosterType } from '@/lib/enums' +import fetcher from '@/lib/fetcher' +import { RosterFormSchema } from '@/lib/forms' +import type { Roster } from '@/lib/types/roster' +import { zodResolver } from '@hookform/resolvers/zod' +import { useRouter } from 'next/navigation' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { toast } from 'sonner' +import type { z } from 'zod' + +export default function UpdateRosterForm({ roster }: { roster: Roster }) { + const [isFetching, setIsFetching] = useState(false) + + const router = useRouter() + + const UpdateRosterFormSchema = RosterFormSchema.omit({ id: true }) + + const form = useForm>({ + resolver: zodResolver(UpdateRosterFormSchema), + defaultValues: { + ...roster, + offPosition: roster.offPosition ?? undefined, + defPosition: roster.defPosition ?? undefined, + splPosition: roster.splPosition ?? undefined + } + }) + + const onSubmit = async (data: z.infer) => { + try { + setIsFetching(true) + await fetcher.put( + `/rosters/${roster.id}`, + { + ...data, + class: data.class === '' ? '없음' : data.class, + offPosition: + data.type !== RosterType.Athlete ? data.type : data.offPosition + }, + false + ) + router.push('/console/roster?revalidate=true') + router.refresh() + toast.success('부원정보가 업데이트 되었습니다') + } catch (error) { + toast.error('부원을 업데이트하지 못했습니다') + } finally { + setIsFetching(false) + } + } + + return ( +
+ +
+ ( + + 이름 + + + + + + )} + /> +
+
+ ( + + 학번 (코치진의 경우 전화번호 뒤 4자리) + + + + + + )} + /> +
+
+ ( + + 구분 + + + + )} + /> +
+
+ ( + + 상태 + + + + )} + /> +
+
+ ( + + 입부년도 (미식축구부에 입부한 년도) + + + + + + )} + /> +
+
+ ( + + 입학년도 (학교에 입학한 년도) + + + + + + )} + /> +
+
+ ( + + 직책 (선택) + + + + + + )} + /> +
+ {form.getValues('type') === RosterType.Athlete && ( + <> +
+ ( + + 오펜스포지션 + + + + + + )} + /> +
+
+ ( + + 디펜스포지션 + + + + + + )} + /> +
+
+ ( + + 스페셜포지션 + + + + + + )} + /> +
+ + )} +
+ + +
+
+ + ) +} diff --git a/frontend/src/app/console/roster/[id]/update/page.tsx b/frontend/src/app/console/roster/[id]/update/page.tsx new file mode 100644 index 0000000..2463869 --- /dev/null +++ b/frontend/src/app/console/roster/[id]/update/page.tsx @@ -0,0 +1,23 @@ +import { getRoster } from '@/lib/actions' +import UpdateRosterForm from './_components/UpdateRosterForm' + +export default async function RosterPage({ + params +}: { + params: { + id: number + } +}) { + const roster = await getRoster(params.id) + + return ( +
+
+
+

부원정보수정

+ +
+
+
+ ) +} diff --git a/frontend/src/app/console/roster/_components/RosterListTable.tsx b/frontend/src/app/console/roster/_components/RosterListTable.tsx index d0f50be..8dfb8f5 100644 --- a/frontend/src/app/console/roster/_components/RosterListTable.tsx +++ b/frontend/src/app/console/roster/_components/RosterListTable.tsx @@ -17,8 +17,8 @@ import { UserIcon } from '@heroicons/react/24/outline' import type { ColumnDef } from '@tanstack/react-table' import { MoreHorizontal } from 'lucide-react' import Image from 'next/image' +import { useRouter } from 'next/navigation' import { useState } from 'react' -import { toast } from 'sonner' import DeleteRosterForm from './DeleteRosterForm' export default function RosterListTable({ @@ -29,6 +29,8 @@ export default function RosterListTable({ const [open, setOpen] = useState(false) const [targetRoster, setTargetRoster] = useState() + const router = useRouter() + const handleClick = (roster: RosterListItem) => { setTargetRoster(roster) setOpen(true) @@ -132,7 +134,9 @@ export default function RosterListTable({ 메뉴 toast.warning('준비중인 기능입니다')} + onClick={() => + router.push(`/console/roster/${roster.id}/update`) + } > 수정 diff --git a/frontend/src/app/console/roster/new/_components/CreateRosterForm.tsx b/frontend/src/app/console/roster/new/_components/CreateRosterForm.tsx index ce92272..c7874b5 100644 --- a/frontend/src/app/console/roster/new/_components/CreateRosterForm.tsx +++ b/frontend/src/app/console/roster/new/_components/CreateRosterForm.tsx @@ -50,13 +50,18 @@ export default function CreateRosterForm() { setIsFetching(true) try { - await fetcher.post('/rosters', { - ...data, - class: data.class === '' ? '없음' : data.class, - offPosition: - data.type !== RosterType.Athlete ? data.type : data.offPosition - }) + await fetcher.post( + '/rosters', + { + ...data, + class: data.class === '' ? '없음' : data.class, + offPosition: + data.type !== RosterType.Athlete ? data.type : data.offPosition + }, + false + ) router.push('/console/roster?revalidate=true') + router.refresh() toast.success('부원을 등록했습니다') } catch (error) { toast.error('부원을 등록하지 못했습니다') diff --git a/frontend/src/app/console/roster/page.tsx b/frontend/src/app/console/roster/page.tsx index 88b7b99..106e414 100644 --- a/frontend/src/app/console/roster/page.tsx +++ b/frontend/src/app/console/roster/page.tsx @@ -3,7 +3,6 @@ import { Button } from '@/components/ui/button' import { getRosters } from '@/lib/actions' import { calculateTotalPages } from '@/lib/utils' import { PAGINATION_LIMIT_DEFAULT } from '@/lib/vars' -import { revalidatePath } from 'next/cache' import Link from 'next/link' import RosterListTable from './_components/RosterListTable' @@ -12,16 +11,11 @@ export default async function RosterPage({ }: { searchParams?: { page?: string - revalidate?: string } }) { const currentPage = Number(searchParams?.page) || 1 const rosterList = await getRosters(currentPage) - if (searchParams?.revalidate) { - revalidatePath('/console/roster') - } - return (
diff --git a/frontend/src/lib/actions.ts b/frontend/src/lib/actions.ts index 932734b..c5513f7 100644 --- a/frontend/src/lib/actions.ts +++ b/frontend/src/lib/actions.ts @@ -3,7 +3,7 @@ import type { RosterType } from './enums' import fetcher from './fetcher' import type { AttendanceList } from './types/attendance' -import type { RosterList } from './types/roster' +import type { Roster, RosterList } from './types/roster' import type { ScheduleList, ScheduleListItem } from './types/schedule' import type { SurveyGroupList, @@ -24,6 +24,10 @@ export const getUsers = async (page: number): Promise => { ) } +export const getRoster = async (rosterId: number): Promise => { + return await fetcher.get(`/rosters/${rosterId}`) +} + export const getRosters = async (page: number): Promise => { return await fetcher.get( `/rosters?page=${page}&limit=${PAGINATION_LIMIT_DEFAULT}&filter=Enable` diff --git a/frontend/src/lib/forms.ts b/frontend/src/lib/forms.ts index 1b2b221..45c2cf7 100644 --- a/frontend/src/lib/forms.ts +++ b/frontend/src/lib/forms.ts @@ -72,7 +72,19 @@ export const RosterFormSchema = z.object({ .optional() .refine( (val) => { - return val === undefined || ['QB', 'OL', 'RB', 'WR', 'TE'].includes(val) + return ( + val === undefined || + [ + 'QB', + 'OL', + 'RB', + 'WR', + 'TE', + 'HeadCoach', + 'Coach', + 'Staff' + ].includes(val) + ) }, { message: