diff --git a/docs/attendance.md b/docs/attendance.md index 54598cb..a25ad4b 100644 --- a/docs/attendance.md +++ b/docs/attendance.md @@ -30,6 +30,35 @@ struct AttendanceSummary { } ``` +### Daily Count +Total Lab count for each date +```rust +pub struct DailyCount { + pub date: String, + pub count: i64, +} +``` + +### Member Summary +Total lab members attended in the past 6 months +```rust +pub struct MemberAttendanceSummary { + pub id: i32, + pub name: String, + pub present_days: i64, +} +``` + +### Attendance Report +Attendance report of the club members +```rust +pub struct AttendanceReport { + pub daily_count: Vec, + pub member_attendance: Vec, + pub max_days: i64, +} +``` + ## Queries ### Get Attendance @@ -64,6 +93,25 @@ query { } ``` +### Get Attendance Report +Get Attendance report containing lab count and members attendance report of the past 6 months. +`maxDays returns the count of days when lab was open in the past 6 months` +```graphql +query{ + getAttendanceSummary(startDate:"2024-12-20", endDate: "2024-12-27"){ + memberAttendance{ + name, + presentDays + } + dailyCount{ + date, + count + } + maxDays + } +} +``` + ### Mark Attendance Record a member's attendance for the day. diff --git a/src/graphql/queries/attendance_queries.rs b/src/graphql/queries/attendance_queries.rs index 200d4f3..7da6cb2 100644 --- a/src/graphql/queries/attendance_queries.rs +++ b/src/graphql/queries/attendance_queries.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use crate::models::attendance::{Attendance, AttendanceWithMember}; +use crate::models::attendance::{ + Attendance, AttendanceReport, AttendanceWithMember, DailyCount, MemberAttendanceSummary, +}; use async_graphql::{Context, Object, Result}; use chrono::NaiveDate; use sqlx::PgPool; @@ -21,6 +23,88 @@ impl AttendanceQueries { ) } + async fn get_attendance_summary( + &self, + ctx: &Context<'_>, + start_date: String, + end_date: String, + ) -> Result { + let pool = ctx.data::>().expect("Pool must be in context."); + + let start = NaiveDate::parse_from_str(&start_date, "%Y-%m-%d") + .map_err(|_| async_graphql::Error::new("Invalid start_date format. Use YYYY-MM-DD"))?; + let end = NaiveDate::parse_from_str(&end_date, "%Y-%m-%d") + .map_err(|_| async_graphql::Error::new("Invalid end_date format. Use YYYY-MM-DD"))?; + if start > end { + return Err(async_graphql::Error::new( + "startDate cannot be greater than endDate.", + )); + } + + let daily_count_result = sqlx::query!( + r#" + SELECT + attendace.date, + COUNT(CASE WHEN attendace.is_present = true THEN attendace.member_id END) as total_present + FROM Attendance attendace + WHERE attendace.date BETWEEN $1 AND $2 + GROUP BY attendace.date + ORDER BY attendace.date + "#, + start, + end + ) + .fetch_all(pool.as_ref()) + .await; + + let daily_count_rows = daily_count_result?; + + let daily_count = daily_count_rows + .into_iter() + .map(|row| DailyCount { + date: row.date.to_string(), + count: row.total_present.unwrap_or(0), + }) + .collect(); + + let member_attendance_query = sqlx::query!( + r#" + SELECT member.member_id as "id!", member.name as "name!", + COUNT(attendance.is_present)::int as "present_days!" + FROM Member member + LEFT JOIN Attendance attendance + ON member.member_id = attendance.member_id + AND attendance.is_present AND attendance.date >= CURRENT_DATE - INTERVAL '6 months' + GROUP BY member.member_id, member.name + ORDER BY member.member_id + "# + ) + .fetch_all(pool.as_ref()) + .await; + + let member_attendance = member_attendance_query? + .into_iter() + .map(|row| MemberAttendanceSummary { + id: row.id, + name: row.name, + present_days: row.present_days as i64, + }) + .collect(); + + let max_days = sqlx::query_scalar::<_, i64>( + "SELECT COUNT(DISTINCT date) FROM Attendance + WHERE date >= CURRENT_DATE - INTERVAL '6 months' AND is_present", + ) + .fetch_one(pool.as_ref()) + .await?; + + Ok(AttendanceReport { + daily_count, + member_attendance, + max_days, + }) + } + async fn attendance_by_date( &self, ctx: &Context<'_>, diff --git a/src/models/attendance.rs b/src/models/attendance.rs index 96daba1..311755e 100644 --- a/src/models/attendance.rs +++ b/src/models/attendance.rs @@ -57,3 +57,26 @@ pub struct AttendanceWithMember { pub name: String, pub year: i32, } + +// This struct is used to get the Lab count of a date +#[derive(SimpleObject)] +pub struct DailyCount { + pub date: String, + pub count: i64, +} + +// This struct is used to fetch the attended lab of each member +#[derive(SimpleObject)] +pub struct MemberAttendanceSummary { + pub id: i32, + pub name: String, + pub present_days: i64, +} + +// This struct is used for getting the combined Attendance report +#[derive(SimpleObject)] +pub struct AttendanceReport { + pub daily_count: Vec, + pub member_attendance: Vec, + pub max_days: i64, +}