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

Commit

Permalink
feat: implement attendance statistic service
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 committed Mar 24, 2024
1 parent 6b181b4 commit a99ab92
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 42 deletions.
14 changes: 14 additions & 0 deletions backend/app/src/attendance/attendance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ export class AttendanceController {
}
}

@Get('statistic')
@Roles(Role.Manager)
async getAttendanceGroupedByRosterType(
@Query('scheduleId', ParseIntPipe) scheduleId: number
) {
try {
return await this.attendanceService.getAttendanceGroupedByRosterType(
scheduleId
)
} catch (error) {
BusinessExceptionHandler(error)
}
}

@Put(':attendanceId')
@Roles(Role.Manager)
async updateAttendance(
Expand Down
135 changes: 99 additions & 36 deletions backend/app/src/attendance/attendance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
Prisma,
RosterType,
type Attendance,
type Roster
type Roster,
AttendanceResponse,
AttendanceLocation
} from '@prisma/client'
import type {
AttendanceWithRoster,
Expand Down Expand Up @@ -150,54 +152,115 @@ export class AttendanceService {
}
}

async getAttendancesGroupedByPosition(scheduleId: number) {
async getAttendanceGroupedByRosterType(scheduleId: number) {
try {
const athleteAttendances = await this.prisma.attendance.findMany({
const attendances = await this.prisma.attendance.findMany({
where: {
scheduleId,
Roster: {
registerYear: {
not: new Date().getFullYear()
},
type: RosterType.Athlete
}
scheduleId
},
include: {
Roster: true
}
})

const athleteNewbieAttendances = await this.prisma.attendance.findMany({
where: {
scheduleId,
Roster: {
registerYear: new Date().getFullYear(),
type: RosterType.Athlete
}
},
include: {
Roster: true
const statistics = attendances.map((attendance) => {
return {
type: attendance.Roster.type,
isNewbie: attendance.Roster.registerYear === new Date().getFullYear(),
response: attendance.response,
location: attendance.location
}
})

const staffAttendances = await this.prisma.attendance.findMany({
where: {
scheduleId,
Roster: {
type: RosterType.Staff
}
},
include: {
Roster: true
}
})
const athleteStatistics = statistics.filter(
(item) => item.type === RosterType.Athlete && !item.isNewbie
)

const athleteNewbieStatistics = statistics.filter(
(item) => item.type === RosterType.Athlete && item.isNewbie
)

const staffStatistics = statistics.filter(
(item) => item.type === RosterType.Staff && !item.isNewbie
)

const staffNewbieStatistics = statistics.filter(
(item) => item.type === RosterType.Staff && item.isNewbie
)

return {
athlete: this.calculateAthleteAttendances(athleteAttendances),
athleteNewbie: this.calculateAthleteAttendances(
athleteNewbieAttendances
),
staff: this.calculateStaffAttendances(staffAttendances)
athlete: {
total: athleteStatistics.filter(
(item) => item.response !== AttendanceResponse.Absence
).length,
seoul: athleteStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Seoul
).length,
suwon: athleteStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Suwon
).length,
absence: athleteStatistics.filter(
(item) => item.response === AttendanceResponse.Absence
).length
},
athleteNewbie: {
total: athleteNewbieStatistics.filter(
(item) => item.response !== AttendanceResponse.Absence
).length,
seoul: athleteNewbieStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Seoul
).length,
suwon: athleteNewbieStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Suwon
).length,
absence: athleteNewbieStatistics.filter(
(item) => item.response === AttendanceResponse.Absence
).length
},
staff: {
total: staffStatistics.filter(
(item) => item.response !== AttendanceResponse.Absence
).length,
seoul: staffStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Seoul
).length,
suwon: staffStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Suwon
).length,
absence: staffStatistics.filter(
(item) => item.response === AttendanceResponse.Absence
).length
},
staffNewbie: {
total: staffNewbieStatistics.filter(
(item) => item.response !== AttendanceResponse.Absence
).length,
seoul: staffNewbieStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Seoul
).length,
suwon: staffNewbieStatistics.filter(
(item) =>
item.response !== AttendanceResponse.Absence &&
item.location === AttendanceLocation.Suwon
).length,
absence: staffNewbieStatistics.filter(
(item) => item.response === AttendanceResponse.Absence
).length
}
}
} catch (error) {
throw new UnexpectedException(error)
Expand Down
12 changes: 8 additions & 4 deletions backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ const seedingDatabase = async () => {
registerYear: 2019,
studentId: '2019310003',
status: RosterStatus.Enable,
type: RosterType.Staff
type: RosterType.Staff,
offPosition: ''
},
{
name: 'staff02',
Expand All @@ -157,7 +158,8 @@ const seedingDatabase = async () => {
registerYear: 2023,
studentId: '2023310005',
status: RosterStatus.Enable,
type: RosterType.Staff
type: RosterType.Staff,
offPosition: ''
},
{
name: 'coach01',
Expand All @@ -166,7 +168,8 @@ const seedingDatabase = async () => {
registerYear: 2014,
studentId: '201401',
status: RosterStatus.Enable,
type: RosterType.Coach
type: RosterType.Coach,
offPosition: ''
},
{
name: 'headCoach01',
Expand All @@ -175,7 +178,8 @@ const seedingDatabase = async () => {
registerYear: 2010,
studentId: '201001',
status: RosterStatus.Enable,
type: RosterType.HeadCoach
type: RosterType.HeadCoach,
offPosition: ''
}
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getAttendanceStatistic } from '@/lib/actions'
import AttendanceStatisticTable from './AttendanceStatisticTable'

export default async function AttendanceStatisticSection({
params
}: {
params: {
id: number
}
}) {
const statistic = await getAttendanceStatistic(params.id)

return <AttendanceStatisticTable {...statistic} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client'

import { DataTable } from '@/components/DataTable'
import type {
AttendanceStatistic,
AttendanceStatisticItem
} from '@/lib/types/attendance'
import type { ColumnDef } from '@tanstack/react-table'

interface Row extends AttendanceStatisticItem {
title: string
}

export default function AttendanceStatisticTable(
statistic: AttendanceStatistic
) {
const statisticList: Row[] = [
{ ...statistic.athlete, title: '선수 (재학생)' },
{ ...statistic.athleteNewbie, title: '선수 (신입생)' },
{ ...statistic.staff, title: '스태프 (재학생)' },
{ ...statistic.staffNewbie, title: '스태프 (신입생)' }
]

const columns: ColumnDef<Row>[] = [
{
accessorKey: 'title',
header: '구분'
},
{
accessorKey: 'total',
header: '합계'
},
{
accessorKey: 'seoul',
header: '명륜'
},
{
accessorKey: 'suwon',
header: '율전'
},
{
accessorKey: 'absence',
header: '불참'
}
]

return <DataTable columns={columns} data={statisticList} />
}
3 changes: 2 additions & 1 deletion frontend/src/app/console/schedule/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Search from '@/components/Search'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import AthleteAttendanceListTable from './_components/AthleteAttendanceListTable'
import AttendanceStatisticSection from './_components/AttendanceStatisticSection'
import ScheduleCard from './_components/ScheduleCard'
import StaffAttendanceListTable from './_components/StaffAttendanceListTable'

Expand Down Expand Up @@ -34,7 +35,7 @@ export default async function AttendanceStatisticPage({
</div>
<div className="col-span-12 flex flex-col space-y-3 sm:col-span-9">
<h2 className="text-base font-bold">포지션별 출석 통계</h2>
<p className="text-amber-400">준비중입니다</p>
<AttendanceStatisticSection params={params} />
</div>
</div>
<div className="grid grid-cols-12 items-center gap-3">
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import type { RosterType } from './enums'
import fetcher from './fetcher'
import type { AttendanceList } from './types/attendance'
import type { AttendanceList, AttendanceStatistic } from './types/attendance'
import type { Roster, RosterList } from './types/roster'
import type { ScheduleList, ScheduleListItem } from './types/schedule'
import type {
Expand Down Expand Up @@ -89,6 +89,14 @@ export const getAttendances = async (
)
}

export const getAttendanceStatistic = async (
scheduleId: number
): Promise<AttendanceStatistic> => {
return await fetcher.get<AttendanceStatistic>(
`/attendances/statistic?scheduleId=${scheduleId}`
)
}

export const getSurveyUnsubmits = async (surveyGroupId: number) => {
return await fetcher.get<SurveyUnsubmitList>(
`/surveys/groups/${surveyGroupId}/unsubmits`
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/lib/types/attendance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ export interface AttendanceList {
attendances: AttendanceListItem[]
total: number
}

export interface AttendanceStatistic {
athlete: AttendanceStatisticItem
athleteNewbie: AttendanceStatisticItem
staff: AttendanceStatisticItem
staffNewbie: AttendanceStatisticItem
}

export interface AttendanceStatisticItem {
total: number
seoul: number
suwon: number
absence: number
}

0 comments on commit a99ab92

Please sign in to comment.