Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade attendace stats to database rpc #95

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions src/api/attendance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isBefore } from 'date-fns'
import { getToday, toDate, toYMD } from '../calendar/utilities'
import { Attendance, AttendanceTypesType, EventAttendance, MemberAttendance } from '../types/Api'
import Config from '../config'
import { Attendance, AttendanceStats, AttendanceTypesType, EventAttendance, MemberAttendance } from '../types/Api'
import { getStartEndOfSeason } from '../utilities/converters'
import { supabase } from './SupabaseClient'

Expand Down Expand Up @@ -125,23 +126,21 @@ const getAttendanceForMember = async ({ season, memberId }) => {
* Fetch Attendance for all members within the season (so far)
* Seaason is the year the game comes out (in January)
*/
const getAttendanceForAllMembers = async (season: string) => {
const getAttendanceStatsForAllMembers = async (season: string) => {
const { startDate, endDate } = getStartEndOfSeason(season)
const theEnd = isBefore(getToday(), toDate(endDate)) ? toYMD(getToday()) : endDate
veggie2u marked this conversation as resolved.
Show resolved Hide resolved
const { data, error } = await supabase
.from('attendance')
.select('attendance_id, member_id, meeting_date, attendance, event_id, events(*)')
.gte('meeting_date', startDate)
.lte('meeting_date', theEnd)
.eq('events.deleted', false)
// doesn't work how we want it to. just returns null for an event, doesn't remove attendance record
.eq('events.take_attendance', true)
const { data, error } = await supabase.rpc('aggregate_attendance_stats', {
Lyanthropos marked this conversation as resolved.
Show resolved Hide resolved
start_date: startDate,
end_date: theEnd,
//number of attended events by a member of the last few regular events (Config.lastNumPracticesAttended).
last_num_practices: Config.lastNumPracticesAttended,
})
if (error) throw error

if (data.length === 0) {
return null
}
return data as Attendance[]
return data as AttendanceStats[]
}

export {
Expand All @@ -152,5 +151,5 @@ export {
insertAttendance,
getAttendanceForMember,
getAttendanceByEvent,
getAttendanceForAllMembers,
getAttendanceStatsForAllMembers as getAttendanceForAllMembers,
}
83 changes: 9 additions & 74 deletions src/pages/admin/AttendanceForSeasonByMember.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { sortByFirstName } from '../../utilities/sorts'
import Config from '../../config'

const AttendanceForSeasonByMember: Component<{ season: Accessor<string> }> = (props) => {
const [allAttendance] = createResource(props.season, getAttendanceForAllMembers)
const [allAttendanceStats] = createResource(props.season, getAttendanceForAllMembers)
const [members] = createResource(props.season, getMembers)
const [events] = createResource(props.season, getSeasonEvents)
const [filteredMembers, setfilteredMembers] = createSignal<Member[]>([])
Expand Down Expand Up @@ -48,73 +48,6 @@ const AttendanceForSeasonByMember: Component<{ season: Accessor<string> }> = (pr
return records.length
})

// function to get the number of attended events by a member of the last few regular events (Config.lastNumPracticesAttended).
// events should be sorted on backend
const lastFewRegularCount = (memberId: number) => {
if (isEmpty(events())) {
return 0
}
// get only regular events
const filtered = events().filter((event: RobotEvent) => {
return event.event_type === EventTypes.REGULAR_PRACTICE
})
// need the date of the practice we are going by
let lastDate: string
// if we have less events than the number we are looking for, take the last one
if (filtered.length < Config.lastNumPracticesAttended) {
lastDate = filtered[filtered.length - 1].event_date
} else {
lastDate = filtered[Config.lastNumPracticesAttended - 1].event_date
}
// now find the attendance records for the events limited by the date we figured out
const records = allAttendance().filter((record) => {
const event = eventMap().get(record.event_id)
// undefined means the event shouldn't have had attendance records on it
return (
event !== undefined &&
record.member_id === memberId &&
event.event_type === EventTypes.REGULAR_PRACTICE &&
record.attendance !== AttendanceTypes.ABSENT &&
toDate(record.meeting_date) >= toDate(lastDate)
)
})
if (isEmpty(records)) {
return 0
}
return records.length
}

// function to get practices attended by a member
const getFullCount = (memberId: number) => {
if (isEmpty(allAttendance())) {
return 0
}
const data = allAttendance().filter((record) => {
const event = eventMap().get(record.event_id)
// undefined means the event shouldn't have had attendance records on it
return event !== undefined && record.member_id === memberId && record.attendance !== AttendanceTypes.ABSENT
})
return isEmpty(data) ? 0 : data.length
}

// function to get regular practices attended by a member
const getRegularCount = (memberId: number) => {
if (isEmpty(allAttendance())) {
return 0
}
const data = allAttendance().filter((record) => {
const event = eventMap().get(record.event_id)
// undefined means the event shouldn't have had attendance records on it
return (
event !== undefined &&
record.member_id === memberId &&
event.event_type === EventTypes.REGULAR_PRACTICE &&
record.attendance !== AttendanceTypes.ABSENT
)
})
return isEmpty(data) ? 0 : data.length
}

type memberIdType = { memberId: number }
const handleNavToMember = (data: memberIdType, event) => {
event.preventDefault()
Expand All @@ -131,7 +64,7 @@ const AttendanceForSeasonByMember: Component<{ season: Accessor<string> }> = (pr

// the show prevents race condition when solid can't tell eventMap updated on it's own
return (
<Show when={eventMap()?.size > 0 && allAttendance()?.length > 0}>
<Show when={eventMap()?.size > 0 && allAttendanceStats()?.length > 0}>
<div>
<div class="text-l lg:text-xl font-semibold p-4">
{allRegularCount()} regular meetings of {allEventCount()} total events in season {props.season()}
Expand Down Expand Up @@ -160,9 +93,11 @@ const AttendanceForSeasonByMember: Component<{ season: Accessor<string> }> = (pr
<tbody>
<For each={filteredMembers()}>
{(record) => {
const fullCount = getFullCount(record.member_id)
const regularCount = getRegularCount(record.member_id)
const lastFew = lastFewRegularCount(record.member_id)
const {
all_count: allCount,
regular_count: regularCount,
last_few_count: lastFewCount,
} = allAttendanceStats().find((candidate) => candidate.member_id === record.member_id)
return (
<tr>
<td
Expand Down Expand Up @@ -193,12 +128,12 @@ const AttendanceForSeasonByMember: Component<{ season: Accessor<string> }> = (pr
</div>
</td>
<td>
{fullCount} ({calculatePercent(fullCount, allEventCount())}%)
{allCount} ({calculatePercent(allCount, allEventCount())}%)
</td>
<td>
{regularCount} ({calculatePercent(regularCount, allRegularCount())}%)
</td>
<td>{lastFew}</td>
<td>{lastFewCount}</td>
</tr>
)
}}
Expand Down
8 changes: 8 additions & 0 deletions src/types/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ type Attendance = {
event_id: number
}

type AttendanceStats = {
member_id: number
all_count: number
regular_count: number
last_few_count: number
}

type MemberAttendance = {
member_id: number
first_name: string
Expand Down Expand Up @@ -203,6 +210,7 @@ export type {
TeamRoleType,
AttendanceTypesType,
Attendance,
AttendanceStats,
SchoolType,
MeetingCount,
Parent,
Expand Down