From ccd0fee23b4e441f87efb700525ad5ed7faf2d6c Mon Sep 17 00:00:00 2001 From: jspark2000 Date: Fri, 8 Mar 2024 16:31:37 +0000 Subject: [PATCH] chore: save commit --- backend/app/src/roster/roster.controller.ts | 2 +- backend/app/src/roster/roster.service.ts | 12 +- frontend/package.json | 1 + .../account/_component/UserListTable.tsx | 2 +- .../roster/_components/DeleteRosterForm.tsx | 89 +++++++++++++ .../roster/_components/RosterListTable.tsx | 117 ++++++++++++++++++ frontend/src/app/console/roster/page.tsx | 27 +++- frontend/src/lib/actions.ts | 7 ++ frontend/src/lib/enums.ts | 14 +++ frontend/src/lib/forms.ts | 10 ++ frontend/src/lib/types/roster.ts | 39 ++++++ pnpm-lock.yaml | 11 ++ 12 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 frontend/src/app/console/roster/_components/DeleteRosterForm.tsx create mode 100644 frontend/src/app/console/roster/_components/RosterListTable.tsx create mode 100644 frontend/src/lib/types/roster.ts diff --git a/backend/app/src/roster/roster.controller.ts b/backend/app/src/roster/roster.controller.ts index 5e9758e..79bb387 100644 --- a/backend/app/src/roster/roster.controller.ts +++ b/backend/app/src/roster/roster.controller.ts @@ -30,7 +30,7 @@ export class RosterController { @Query('page', ParseIntPipe) page: number, @Query('limit', new ParseIntPipe({ optional: true })) limit?: number, @Query('filter') filter?: string - ): Promise { + ): Promise<{ total: number; rosters: Roster[] }> { try { return await this.rosterService.getRosters(page, limit, filter) } catch (error) { diff --git a/backend/app/src/roster/roster.service.ts b/backend/app/src/roster/roster.service.ts index 07b3ecf..1a9a3ac 100644 --- a/backend/app/src/roster/roster.service.ts +++ b/backend/app/src/roster/roster.service.ts @@ -42,11 +42,11 @@ export class RosterService { page: number, limit = 10, filter?: string - ): Promise { + ): Promise<{ total: number; rosters: Roster[] }> { try { const status = this.checkRosterStatus(filter) - return await this.prisma.roster.findMany({ + const rosters = await this.prisma.roster.findMany({ where: { status }, @@ -56,6 +56,14 @@ export class RosterService { name: 'asc' } }) + + const total = await this.prisma.roster.count({ + where: { + status + } + }) + + return { total, rosters } } catch (error) { if (error instanceof BusinessException) throw error throw new UnexpectedException(error) diff --git a/frontend/package.json b/frontend/package.json index a4db676..a78a98e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "@tanstack/react-table": "^8.13.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "lucide-react": "^0.350.0", "moment": "^2.30.1", "next": "14.1.1", "next-auth": "^4.24.6", diff --git a/frontend/src/app/console/account/_component/UserListTable.tsx b/frontend/src/app/console/account/_component/UserListTable.tsx index c0c55c5..fb80fd9 100644 --- a/frontend/src/app/console/account/_component/UserListTable.tsx +++ b/frontend/src/app/console/account/_component/UserListTable.tsx @@ -5,7 +5,7 @@ import LocalTime from '@/components/Localtime' import type { UserListItem } from '@/lib/types/user' import type { ColumnDef } from '@tanstack/react-table' -export const columns: ColumnDef[] = [ +const columns: ColumnDef[] = [ { accessorKey: 'id', header: 'ID' diff --git a/frontend/src/app/console/roster/_components/DeleteRosterForm.tsx b/frontend/src/app/console/roster/_components/DeleteRosterForm.tsx new file mode 100644 index 0000000..8e468c7 --- /dev/null +++ b/frontend/src/app/console/roster/_components/DeleteRosterForm.tsx @@ -0,0 +1,89 @@ +'use client' + +import { + Drawer, + DrawerContent, + DrawerDescription, + DrawerHeader, + DrawerTitle, + DrawerTrigger +} from '@/components/ui/drawer' +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel +} from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { RosterFormSchema } from '@/lib/forms' +import type { RosterListItem } from '@/lib/types/roster' +import { zodResolver } from '@hookform/resolvers/zod' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import type { z } from 'zod' + +export function DeleteRosterForm({ roster }: { roster: RosterListItem }) { + const DeleteRosterFormSchema = RosterFormSchema.pick({ + id: true, + name: true, + studentId: true + }) + + const [open, setOpen] = useState(false) + + const form = useForm>({ + resolver: zodResolver(DeleteRosterFormSchema), + defaultValues: { + id: roster.id + } + }) + + return ( + + + + + +
+
+ + + 로스터 삭제 + + 삭제할 로스터의 이름과 학번을 정확하게 입력해주세요 + + +
+ ( + + 이름 + + + + + )} + /> + ( + + 학번 + + + + + )} + /> +
+
+ +
+
+
+ ) +} diff --git a/frontend/src/app/console/roster/_components/RosterListTable.tsx b/frontend/src/app/console/roster/_components/RosterListTable.tsx new file mode 100644 index 0000000..e504743 --- /dev/null +++ b/frontend/src/app/console/roster/_components/RosterListTable.tsx @@ -0,0 +1,117 @@ +'use client' + +import Badge, { BadgeColor } from '@/components/Badge' +import { DataTable } from '@/components/DataTable' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu' +import { RosterType } from '@/lib/enums' +import type { RosterListItem } from '@/lib/types/roster' +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 { DeleteRosterForm } from './DeleteRosterForm' + +export default function RosterListTable({ + rosters +}: { + rosters: RosterListItem[] +}) { + const createTypeBadge = (type: RosterType) => { + switch (type) { + case RosterType.Athlete: + return + case RosterType.Staff: + return + case RosterType.Coach: + return + case RosterType.HeadCoach: + default: + return + } + } + + const columns: ColumnDef[] = [ + { + id: 'profile', + header: '이름', + cell: ({ row }) => { + const roster = row.original + + return ( +
+
+ {roster.profileImageUrl ? ( + + ) : ( + + )} +
+

{roster.name}

+
+ ) + } + }, + { + accessorKey: 'type', + header: '구분', + cell: ({ row }) => { + return createTypeBadge(row.getValue('type')) + } + }, + { + accessorKey: 'studentId', + header: '학번' + }, + { + accessorKey: 'admissionYear', + header: '입학년도' + }, + { + accessorKey: 'registerYear', + header: '입부년도' + }, + { + id: 'actions', + cell: ({ row }) => { + const roster = row.original + + return ( + + + + + + 메뉴 + + alert(roster.id)}> + 수정 + + + + + + + ) + } + } + ] + + return +} diff --git a/frontend/src/app/console/roster/page.tsx b/frontend/src/app/console/roster/page.tsx index f10a4eb..d920da7 100644 --- a/frontend/src/app/console/roster/page.tsx +++ b/frontend/src/app/console/roster/page.tsx @@ -1,10 +1,33 @@ -export default async function RosterPage() { +import Pagination from '@/components/Pagination' +import { getRosters } from '@/lib/actions' +import { calculateTotalPages } from '@/lib/utils' +import { PAGINATION_LIMIT_DEFAULT } from '@/lib/vars' +import RosterListTable from './_components/RosterListTable' + +export default async function RosterPage({ + searchParams +}: { + searchParams?: { + page?: string + } +}) { + const currentPage = Number(searchParams?.page) || 1 + const rosterList = await getRosters(currentPage) + return (

부원명단

-
+
+ + +
) } diff --git a/frontend/src/lib/actions.ts b/frontend/src/lib/actions.ts index 4f1714b..e44cdd5 100644 --- a/frontend/src/lib/actions.ts +++ b/frontend/src/lib/actions.ts @@ -1,6 +1,7 @@ 'use server' import fetcher from './fetcher' +import type { RosterList } from './types/roster' import type { UserList, UserProfile } from './types/user' export const getCurrentUserProfile = async (): Promise => { @@ -10,3 +11,9 @@ export const getCurrentUserProfile = async (): Promise => { export const getUsers = async (page: number): Promise => { return await fetcher.get(`/user/list?page=${page}&limit=10`) } + +export const getRosters = async (page: number): Promise => { + return await fetcher.get( + `/rosters?page=${page}&limit=10&filter=Enable` + ) +} diff --git a/frontend/src/lib/enums.ts b/frontend/src/lib/enums.ts index df71890..a5dcd27 100644 --- a/frontend/src/lib/enums.ts +++ b/frontend/src/lib/enums.ts @@ -9,3 +9,17 @@ export enum AccountStatus { Enable = 'Enable', Disable = 'Disable' } + +export enum RosterStatus { + Enable = 'Enable', + Military = 'Military', + Absence = 'Absence', + Alumni = 'Alumni' +} + +export enum RosterType { + Athlete = 'Athlete', + Staff = 'Staff', + Coach = 'Coach', + HeadCoach = 'HeadCoach' +} diff --git a/frontend/src/lib/forms.ts b/frontend/src/lib/forms.ts index 5c87b26..c67da12 100644 --- a/frontend/src/lib/forms.ts +++ b/frontend/src/lib/forms.ts @@ -25,3 +25,13 @@ export const AccountFormSchema = z.object({ message: '비밀번호는 최소 6글자 이상이어야 합니다' }) }) + +export const RosterFormSchema = z.object({ + id: z.number(), + name: z.string().min(1, { + message: '필수 입력 사항압니다' + }), + studentId: z.string().min(1, { + message: '필수 입력 사항입니다' + }) +}) diff --git a/frontend/src/lib/types/roster.ts b/frontend/src/lib/types/roster.ts new file mode 100644 index 0000000..1bfb2e6 --- /dev/null +++ b/frontend/src/lib/types/roster.ts @@ -0,0 +1,39 @@ +import type { RosterStatus, RosterType } from '../enums' + +type RosterBasic = { + id: number + name: string + type: RosterType + status: RosterStatus + target: boolean +} + +export interface Roster extends RosterBasic { + studentId: string + admissionYear: number + profileImageUrl?: string + registerYear: number + offPosition?: string + defPosition?: string + splPosition?: string + backNumber?: string +} + +export interface RosterProfile extends RosterBasic { + profileImageUrl?: string + registerYear: number + offPosition?: string + defPosition?: string + splPosition?: string + backNumber?: string +} + +export interface RosterListItem extends RosterProfile { + studentId: string + admissionYear: number +} + +export interface RosterList { + total: number + rosters: RosterListItem[] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1b558c..5384c52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,9 @@ importers: clsx: specifier: ^2.1.0 version: 2.1.0 + lucide-react: + specifier: ^0.350.0 + version: 0.350.0(react@18.2.0) moment: specifier: ^2.30.1 version: 2.30.1 @@ -11741,6 +11744,14 @@ packages: dependencies: yallist: 4.0.0 + /lucide-react@0.350.0(react@18.2.0): + resolution: {integrity: sha512-5IZVKsxxG8Nn81gpsz4XLNgCAXkppCh0Y0P0GLO39h5iVD2WEaB9of6cPkLtzys1GuSfxJxmwsDh487y7LAf/g==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true