diff --git a/src/db.rs b/src/db.rs index 088f833..12bfaec 100644 --- a/src/db.rs +++ b/src/db.rs @@ -45,6 +45,8 @@ pub struct Announcement { pub end_date: String, pub created_at: String, pub flags: Vec, + #[serde(default)] + pub classes: Vec, } pub fn open(db_path: &str) -> Result { @@ -64,12 +66,16 @@ pub fn open(db_path: &str) -> Result { "ALTER TABLE announcements ADD COLUMN flags TEXT NOT NULL DEFAULT '[]'", [], ); + let _ = conn.execute( + "ALTER TABLE announcements ADD COLUMN classes TEXT NOT NULL DEFAULT '[]'", + [], + ); Ok(conn) } pub fn get_for_date(conn: &Connection, date: &str) -> Result> { let mut stmt = conn.prepare( - "SELECT id, author, text_content, start_date, end_date, flags, created_at + "SELECT id, author, text_content, start_date, end_date, flags, classes, created_at FROM announcements WHERE date(?) BETWEEN date(start_date) AND date(end_date) ORDER BY created_at DESC", @@ -90,7 +96,7 @@ pub fn list_all( let (sql, param_values): (&str, Vec>) = if let Some(date) = filter_date { ( - "SELECT id, author, text_content, start_date, end_date, flags, created_at + "SELECT id, author, text_content, start_date, end_date, flags, classes, created_at FROM announcements WHERE date(?1) BETWEEN date(start_date) AND date(end_date) ORDER BY start_date DESC, created_at DESC @@ -103,7 +109,7 @@ pub fn list_all( ) } else { ( - "SELECT id, author, text_content, start_date, end_date, flags, created_at + "SELECT id, author, text_content, start_date, end_date, flags, classes, created_at FROM announcements ORDER BY start_date DESC, created_at DESC LIMIT ?1 OFFSET ?2", @@ -141,13 +147,22 @@ pub fn create( text_content: Option<&str>, start_date: &str, end_date: &str, + classes: &[String], flags: &[AnnouncementFlag], ) -> Result { + let classes_json = serde_json::to_string(classes).unwrap_or_else(|_| "[]".to_string()); let flags_json = serde_json::to_string(flags).unwrap_or_else(|_| "[]".to_string()); conn.execute( - "INSERT INTO announcements (author, text_content, start_date, end_date, flags) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![author, text_content, start_date, end_date, flags_json], + "INSERT INTO announcements (author, text_content, start_date, end_date, classes, flags) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![ + author, + text_content, + start_date, + end_date, + classes_json, + flags_json + ], )?; let id = conn.last_insert_rowid(); get_by_id(conn, id)?.ok_or_else(|| { @@ -162,7 +177,9 @@ pub fn delete(conn: &Connection, id: i64) -> Result { fn map_row(row: &rusqlite::Row) -> rusqlite::Result { let flags_json: String = row.get(5)?; + let classes_json: String = row.get(6)?; let flags: Vec = serde_json::from_str(&flags_json).unwrap_or_default(); + let classes: Vec = serde_json::from_str(&classes_json).unwrap_or_default(); Ok(Announcement { id: row.get(0)?, author: row.get(1)?, @@ -170,13 +187,14 @@ fn map_row(row: &rusqlite::Row) -> rusqlite::Result { start_date: row.get(3)?, end_date: row.get(4)?, flags, - created_at: row.get(6)?, + classes, + created_at: row.get(7)?, }) } fn get_by_id(conn: &Connection, id: i64) -> Result> { let mut stmt = conn.prepare( - "SELECT id, author, text_content, start_date, end_date, flags, created_at + "SELECT id, author, text_content, start_date, end_date, flags, classes, created_at FROM announcements WHERE id = ?1", )?; let mut rows = stmt.query_map(params![id], map_row)?; diff --git a/src/management/mod.rs b/src/management/mod.rs index 07e7f4c..fc6d752 100644 --- a/src/management/mod.rs +++ b/src/management/mod.rs @@ -92,7 +92,7 @@ async fn management_page( let total_pages = (total as u32).div_ceil(page_size).max(1); let rows: String = if announcements.is_empty() { - let colspan = if filter_date.is_some() { "8" } else { "7" }; + let colspan = if filter_date.is_some() { "9" } else { "8" }; format!( r#"No announcements yet."#, colspan @@ -109,6 +109,15 @@ async fn management_page( }) .collect::>() .join(" "); + let classes_display: String = if a.classes.is_empty() { + r#"All"#.to_string() + } else { + a.classes + .iter() + .map(|c| format!(r#"{}"#, escape_html(c))) + .collect::>() + .join(" ") + }; let text_display = a .text_content .as_ref() @@ -121,6 +130,7 @@ async fn management_page( text = text_display, start = a.start_date, end = a.end_date, + classes_display = classes_display, flags_display = flags_display, ) }) @@ -244,6 +254,8 @@ struct CreateInput { text_content: Option, start_date: String, end_date: String, + #[serde(default)] + classes: String, #[serde(default, deserialize_with = "one_or_many")] flags: Vec, } @@ -263,10 +275,17 @@ async fn create_announcement( .filter_map(|s| s.parse::().ok()) .collect(); + let classes: Vec = input + .classes + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + let text = input.text_content.filter(|s| !s.is_empty()); let conn = db.lock().await; - if let Err(e) = db::create(&conn, &input.author, text.as_deref(), &input.start_date, &input.end_date, &flags) { + if let Err(e) = db::create(&conn, &input.author, text.as_deref(), &input.start_date, &input.end_date, &classes, &flags) { return ( StatusCode::INTERNAL_SERVER_ERROR, [("location", "/")], diff --git a/templates/announcement_row.html b/templates/announcement_row.html index 92529c1..1acf6cc 100644 --- a/templates/announcement_row.html +++ b/templates/announcement_row.html @@ -4,6 +4,7 @@ {text} {start} {end} + {classes_display} {flags_display}
diff --git a/templates/management_page.html b/templates/management_page.html index 8f50c00..9f9645a 100644 --- a/templates/management_page.html +++ b/templates/management_page.html @@ -42,6 +42,9 @@ .cell-actions {{ white-space: nowrap; }} .flag {{ display: inline-block; padding: 0.2rem 0.55rem; border-radius: 999px; background: #dbeafe; color: #1e40af; font-size: 0.75rem; font-weight: 500; white-space: nowrap; }} + .class-pill {{ display: inline-block; padding: 0.2rem 0.55rem; border-radius: 999px; background: #f0fdf4; color: #166534; font-size: 0.75rem; font-weight: 500; white-space: nowrap; }} + .class-pill.all {{ background: #f1f5f9; color: #64748b; font-style: italic; }} + .field-hint {{ font-size: 0.8rem; color: #94a3b8; font-weight: 400; }} .empty {{ padding: 3rem 1rem; text-align: center; color: #94a3b8; font-size: 0.95rem; }} .pagination {{ display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; padding: 1rem 1.25rem; background: #fff; border-top: 1px solid #e2e8f0; }} @@ -93,6 +96,7 @@ Text Start End + For Flags Actions @@ -125,7 +129,12 @@ End date -