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

Commit

Permalink
fix: modify attendance check page ui
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 committed Apr 8, 2024
1 parent 26c1aaf commit 7ef2651
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 9 deletions.
2 changes: 2 additions & 0 deletions backend/app/src/attendance/attendance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ export class AttendanceController {
async getUncheckedAttendances(
@Query('scheduleId', ParseIntPipe) scheduleId: number,
@Query('page', ParseIntPipe) page: number,
@Query('response') response: string,
@Query('limit', new ParseIntPipe({ optional: true })) limit?: number
): Promise<{ attendances: AttendanceWithRoster[]; total: number }> {
try {
return await this.attendanceService.getUncheckedAttendances(
scheduleId,
response,
page,
limit
)
Expand Down
17 changes: 14 additions & 3 deletions backend/app/src/attendance/attendance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ export class AttendanceService {

async getUncheckedAttendances(
scheduleId: number,
response: string,
page: number,
limit = 1
): Promise<{ attendances: AttendanceWithRoster[]; total: number }> {
try {
const attendances = await this.prisma.attendance.findMany({
where: {
scheduleId,
result: null
result: null,
response: this.transformAttendanceResponse(response)
},
skip: calculatePaginationOffset(page, limit),
take: limit,
Expand Down Expand Up @@ -98,7 +100,8 @@ export class AttendanceService {
const total = await this.prisma.attendance.count({
where: {
scheduleId,
result: null
result: null,
response: this.transformAttendanceResponse(response)
}
})

Expand Down Expand Up @@ -476,12 +479,20 @@ export class AttendanceService {
case AttendanceLocation.Seoul:
return '명륜'
case AttendanceLocation.Suwon:
return '수원'
return '율전'
default:
return ''
}
}

private transformAttendanceResponse(response: string): AttendanceResponse {
try {
return AttendanceResponse[response]
} catch (error) {
return AttendanceResponse.Present
}
}

private transformRosterType(rosterType: string): RosterType {
try {
return RosterType[rosterType]
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-table": "^8.15.0",
"class-variance-authority": "^0.7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default function UpdateUserForm({
</FormControl>
<SelectContent>
<SelectItem value={Role.User}>일반</SelectItem>
<SelectItem value={Role.Manager}>매니자</SelectItem>
<SelectItem value={Role.Manager}>매니저</SelectItem>
<SelectItem value={Role.Admin}>관리자</SelectItem>
</SelectContent>
</Select>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
'use client'

import Badge, { BadgeColor } from '@/components/Badge'
import { Button } from '@/components/ui/button'
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle
} from '@/components/ui/drawer'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel
} from '@/components/ui/form'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { AttendanceLocation, AttendanceStatus } from '@/lib/enums'
import fetcher from '@/lib/fetcher'
import { AttendanceFormSchema } from '@/lib/forms'
import type { AttendanceListItem } from '@/lib/types/attendance'
import { zodResolver } from '@hookform/resolvers/zod'
import { useRouter } from 'next/navigation'
import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import type { z } from 'zod'

export default function AttendanceCheckForm({
attendance,
open,
setOpen
}: {
attendance: AttendanceListItem
open: boolean
setOpen: Dispatch<SetStateAction<boolean>>
}) {
const [isFetching, setIsFetching] = useState(false)
const router = useRouter()

const CheckAttendanceFormSchema = AttendanceFormSchema.pick({
result: true
})

const form = useForm<z.infer<typeof CheckAttendanceFormSchema>>({
resolver: zodResolver(CheckAttendanceFormSchema),
defaultValues: {
result: attendance.response
}
})

useEffect(() => {
form.reset({
result: attendance.response
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open])

const onSubmit = async (data: z.infer<typeof CheckAttendanceFormSchema>) => {
try {
setIsFetching(true)
await fetcher.put(`/attendances/${attendance.id}`, data, false)
setOpen(false)
toast.success('출석체크 성공')
router.refresh()
} catch (error) {
toast.error('출결 정보를 업데이트하지 못했습니다')
} finally {
setIsFetching(false)
}
}

const renderAttendanceStatus = (attendance: AttendanceListItem) => {
switch (attendance.response) {
case AttendanceStatus.Absence:
return <Badge color={BadgeColor.red} content="불참" />
case AttendanceStatus.Tardy:
return <Badge color={BadgeColor.yellow} content="부분참석" />
case AttendanceStatus.Present:
default:
return <Badge color={BadgeColor.green} content="참석" />
}
}

const renderAttendanceLocation = (attendance: AttendanceListItem) => {
switch (attendance.location) {
case AttendanceLocation.Seoul:
return <Badge color={BadgeColor.pink} content="명륜" />
case AttendanceLocation.Suwon:
return <Badge color={BadgeColor.indigo} content="율전" />
default:
return <Badge color={BadgeColor.gray} content="통합" />
}
}

const renderType = (attendance: AttendanceListItem) => {
switch (attendance.Roster.registerYear) {
case new Date().getFullYear():
return <Badge color={BadgeColor.yellow} content="신입생" />
default:
return <Badge color={BadgeColor.blue} content="재학생" />
}
}

return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerContent>
<div className="mx-auto w-full max-w-sm">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<DrawerHeader>
<DrawerTitle>출석체크</DrawerTitle>
<DrawerDescription>
<div className="flex flex-col space-y-3">
<div>
{attendance.Roster.name}/{attendance.Roster.admissionYear}
</div>
<div>구분 {renderType(attendance)}</div>
<div>
출석조사 응답 {renderAttendanceStatus(attendance)}
</div>
<div>출석위치 {renderAttendanceLocation(attendance)}</div>
</div>
</DrawerDescription>
</DrawerHeader>
<div className="space-y-3 p-4">
<FormField
control={form.control}
name="result"
render={({ field }) => (
<FormItem>
<FormLabel>실제출석</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value={AttendanceStatus.Present}>
참석
</SelectItem>
<SelectItem value={AttendanceStatus.Tardy}>
부분참석
</SelectItem>
<SelectItem value={AttendanceStatus.Absence}>
불참
</SelectItem>
</SelectContent>
</Select>
</FormItem>
)}
/>
</div>
<DrawerFooter>
<Button type="submit" className="w-full" disabled={isFetching}>
체크하기
</Button>
<DrawerClose asChild>
<Button variant="outline">취소</Button>
</DrawerClose>
</DrawerFooter>
</form>
</Form>
</div>
</DrawerContent>
</Drawer>
)
}
Loading

0 comments on commit 7ef2651

Please sign in to comment.