feat: add course search UI with ts-rs type bindings

Integrate ts-rs for Rust-to-TypeScript type generation, build course
search page with filters, pagination, and expandable detail rows,
and refactor theme toggle into a reactive store with view transition
animation.
This commit is contained in:
2026-01-28 22:11:17 -06:00
parent 15256ff91c
commit 5fab8c216a
26 changed files with 1360 additions and 401 deletions
+3 -1
View File
@@ -3,9 +3,11 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use ts_rs::TS;
/// Represents a meeting time stored as JSONB in the courses table.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct DbMeetingTime {
pub begin_time: Option<String>,
pub end_time: Option<String>,
+5 -1
View File
@@ -2,6 +2,7 @@
use crate::data::models::ReferenceData;
use crate::error::Result;
use html_escape::decode_html_entities;
use sqlx::PgPool;
/// Batch upsert reference data entries.
@@ -12,7 +13,10 @@ pub async fn batch_upsert(entries: &[ReferenceData], db_pool: &PgPool) -> Result
let categories: Vec<&str> = entries.iter().map(|e| e.category.as_str()).collect();
let codes: Vec<&str> = entries.iter().map(|e| e.code.as_str()).collect();
let descriptions: Vec<&str> = entries.iter().map(|e| e.description.as_str()).collect();
let descriptions: Vec<String> = entries
.iter()
.map(|e| decode_html_entities(&e.description).into_owned())
.collect();
sqlx::query(
r#"