Skip to content
This repository has been archived by the owner on Dec 16, 2024. It is now read-only.

Commit

Permalink
chore: save commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 committed Mar 8, 2024
1 parent 85573b1 commit ccd0fee
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 6 deletions.
2 changes: 1 addition & 1 deletion backend/app/src/roster/roster.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Roster[]> {
): Promise<{ total: number; rosters: Roster[] }> {
try {
return await this.rosterService.getRosters(page, limit, filter)
} catch (error) {
Expand Down
12 changes: 10 additions & 2 deletions backend/app/src/roster/roster.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ export class RosterService {
page: number,
limit = 10,
filter?: string
): Promise<Roster[]> {
): 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
},
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserListItem>[] = [
const columns: ColumnDef<UserListItem>[] = [
{
accessorKey: 'id',
header: 'ID'
Expand Down
89 changes: 89 additions & 0 deletions frontend/src/app/console/roster/_components/DeleteRosterForm.tsx
Original file line number Diff line number Diff line change
@@ -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<z.infer<typeof DeleteRosterFormSchema>>({
resolver: zodResolver(DeleteRosterFormSchema),
defaultValues: {
id: roster.id
}
})

return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>
<button>삭제</button>
</DrawerTrigger>
<DrawerContent>
<div className="mx-auto w-full max-w-sm">
<Form {...form}>
<form>
<DrawerHeader>
<DrawerTitle>로스터 삭제</DrawerTitle>
<DrawerDescription>
삭제할 로스터의 이름과 학번을 정확하게 입력해주세요
</DrawerDescription>
</DrawerHeader>
<div className="space-y-3 p-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>이름</FormLabel>
<FormControl>
<Input {...field} placeholder={roster.backNumber} />
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="studentId"
render={({ field }) => (
<FormItem>
<FormLabel>학번</FormLabel>
<FormControl>
<Input {...field} placeholder={roster.studentId} />
</FormControl>
</FormItem>
)}
/>
</div>
</form>
</Form>
</div>
</DrawerContent>
</Drawer>
)
}
117 changes: 117 additions & 0 deletions frontend/src/app/console/roster/_components/RosterListTable.tsx
Original file line number Diff line number Diff line change
@@ -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 <Badge color={BadgeColor.indigo} content="선수" />
case RosterType.Staff:
return <Badge color={BadgeColor.yellow} content="스태프" />
case RosterType.Coach:
return <Badge color={BadgeColor.green} content="코치" />
case RosterType.HeadCoach:
default:
return <Badge color={BadgeColor.red} content="감독" />
}
}

const columns: ColumnDef<RosterListItem>[] = [
{
id: 'profile',
header: '이름',
cell: ({ row }) => {
const roster = row.original

return (
<div className="flex flex-nowrap items-center gap-x-1.5">
<div className="h-6 w-6 rounded-full">
{roster.profileImageUrl ? (
<Image
src={roster.profileImageUrl}
width={32}
height={32}
alt=""
className="object-cover"
/>
) : (
<UserIcon />
)}
</div>
<p>{roster.name}</p>
</div>
)
}
},
{
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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>메뉴</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => alert(roster.id)}>
수정
</DropdownMenuItem>
<DropdownMenuItem>
<DeleteRosterForm roster={roster} />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
}
]

return <DataTable columns={columns} data={rosters} />
}
27 changes: 25 additions & 2 deletions frontend/src/app/console/roster/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<main className="mx-auto flex w-full flex-grow flex-col items-center justify-center">
<div className="mt-4 w-full px-4 text-left sm:px-6">
<h1 className="text-base font-bold sm:text-xl">부원명단</h1>
</div>
<div className="flex w-full flex-grow flex-col gap-5 py-4 sm:px-6"></div>
<div className="flex w-full flex-grow flex-col gap-5 py-4 sm:px-6">
<RosterListTable rosters={rosterList.rosters} />
<Pagination
totalPages={calculateTotalPages(
rosterList.total,
PAGINATION_LIMIT_DEFAULT
)}
/>
</div>
</main>
)
}
7 changes: 7 additions & 0 deletions frontend/src/lib/actions.ts
Original file line number Diff line number Diff line change
@@ -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<UserProfile> => {
Expand All @@ -10,3 +11,9 @@ export const getCurrentUserProfile = async (): Promise<UserProfile> => {
export const getUsers = async (page: number): Promise<UserList> => {
return await fetcher.get<UserList>(`/user/list?page=${page}&limit=10`)
}

export const getRosters = async (page: number): Promise<RosterList> => {
return await fetcher.get<RosterList>(
`/rosters?page=${page}&limit=10&filter=Enable`
)
}
14 changes: 14 additions & 0 deletions frontend/src/lib/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
10 changes: 10 additions & 0 deletions frontend/src/lib/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '필수 입력 사항입니다'
})
})
39 changes: 39 additions & 0 deletions frontend/src/lib/types/roster.ts
Original file line number Diff line number Diff line change
@@ -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[]
}
Loading

0 comments on commit ccd0fee

Please sign in to comment.