small tweaks

This commit is contained in:
Mitchell Marino 2023-04-16 16:08:47 -05:00
parent 9e6168b5e1
commit ad4377b52c
4 changed files with 68 additions and 19 deletions

View File

@ -130,7 +130,7 @@ pub struct Signin {
/// The model for the query parameters of the report request. /// The model for the query parameters of the report request.
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ReportQuery { pub struct ReportQuery {
pub grade: Option<i32>, pub detailed_event_view: bool,
} }
/// The model for the confirm attending request. /// The model for the confirm attending request.
@ -164,6 +164,20 @@ pub struct LeaderBoardEntry {
pub username: String, pub username: String,
pub points: Option<i64>, pub points: Option<i64>,
} }
#[derive(
sqlx::Type, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
)]
pub struct ReportEntry {
pub username: String,
pub grade: i32,
pub points: Option<i64>,
pub event_titles: Vec<String>,
pub event_points: Vec<i32>,
pub event_discriptions: Vec<String>,
pub event_types: Vec<String>,
}
/// Module for (de)serializing [OffsetDateTime] to conform with the JWT spec (RFC 7519 section 2, "Numeric Date") /// Module for (de)serializing [OffsetDateTime] to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
mod serde_numeric_date { mod serde_numeric_date {
use serde::{self, Deserialize, Deserializer, Serializer}; use serde::{self, Deserialize, Deserializer, Serializer};

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
jwt::handle_token, jwt::handle_token,
models::{LeaderBoardEntry, ReportQuery, Role}, models::{ReportEntry, ReportQuery, Role},
AppState, AppState,
}; };
use axum::{ use axum::{
@ -40,24 +40,24 @@ pub async fn get_report(
}; };
let result = query_as!( let result = query_as!(
LeaderBoardEntry, ReportEntry,
r#" r#"
SELECT SELECT
u.username, u.username,
COALESCE(SUM(e.points), 0) AS points u.grade,
FROM COALESCE(SUM(e.points), 0) AS points,
users u array_agg(e.title) AS "event_titles!",
array_agg(e.description) AS "event_discriptions!",
array_agg(e.points) AS "event_points!",
array_agg(e.event_type) AS "event_types!: Vec<String>"
FROM users u
LEFT JOIN event_attendees ea LEFT JOIN event_attendees ea
ON u.id = ea.user_id AND ea.confirmed = true ON u.id = ea.user_id AND ea.confirmed = true
LEFT JOIN events e LEFT JOIN events e
ON ea.event_id = e.id ON ea.event_id = e.id
WHERE
u.grade = $1 OR $2
GROUP BY u.id GROUP BY u.id
ORDER BY points ORDER BY u.grade, u.username, points
"#, "#,
report_query.grade.unwrap_or(0),
report_query.grade.is_none(),
) )
.fetch_all(&app_state.db_pool) .fetch_all(&app_state.db_pool)
.await; .await;
@ -76,9 +76,10 @@ pub async fn get_report(
}; };
let mut context = Context::new(); let mut context = Context::new();
context.insert("grade", &report_query.grade);
context.insert("date", &Local::now().format("%Y-%m-%d").to_string()); context.insert("date", &Local::now().format("%Y-%m-%d").to_string());
context.insert("users", &records); context.insert("users", &records);
context.insert("detailed_event_view", &report_query.detailed_event_view);
let report = match TEMPLATES.render("report.html", &context) { let report = match TEMPLATES.render("report.html", &context) {
Ok(report) => report, Ok(report) => report,
Err(err) => { Err(err) => {

View File

@ -199,7 +199,7 @@ pub async fn mark_claimed(
match result { match result {
Ok(record) => { Ok(record) => {
if (record.rows_affected() == 0) { if record.rows_affected() == 0 {
( (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
Json(json!({ "error": format!("winner_id not found") })), Json(json!({ "error": format!("winner_id not found") })),

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Student Point Report Grade {{ grade }}</title> <title>Student Point Report</title>
<style> <style>
/* color theme */ /* color theme */
:root { :root {
@ -15,12 +15,19 @@
} }
/* table styling */ /* table styling */
table { table {
font-family: Arial, sans-serif;
color: var(--text-color);
width: 100%;
}
.styled-table{
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
margin: 20px 0; margin: 20px 0;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif; }
color: var(--text-color); .inner-table{
margin: 0;
padding: 0;
} }
th, td { th, td {
text-align: left; text-align: left;
@ -30,7 +37,7 @@
th { th {
background-color: var(--primary-color); background-color: var(--primary-color);
color: white; color: white;
font-weight: normal; font-weight: bold;
} }
/* header styling */ /* header styling */
header { header {
@ -51,21 +58,48 @@
</head> </head>
<body> <body>
<header> <header>
<h1>Student Point Report Grade {{ grade }}</h1> <h1>Student Point Report</h1>
<p style="margin: 0;">{{ date }}</p> <p style="margin: 0;">{{ date }}</p>
</header> </header>
<table> <table class="styled-table">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Grade</th>
<th>Points</th> <th>Points</th>
{% if detailed_event_view %}
<th>Events</th>
{% endif %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in users %} {% for user in users %}
<tr> <tr>
<td>{{ user.username }}</td> <td>{{ user.username }}</td>
<td>{{ user.grade }}</td>
<td>{{ user.points }}</td> <td>{{ user.points }}</td>
{% if detailed_event_view %}
<td>
<table class="inner-table">
<thead>
<tr>
<th>Points</td>
<th>Type</td>
<th>Title</td>
</tr>
</thead>
<tbody>
{% for i in range(end=user.event_titles|length) %}
<tr>
<td>{{user.event_points[i]}}</td>
<td>{{user.event_types[i]}}</td>
<td>{{user.event_titles[i]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>