feat: implement CSV export of scholarships

This commit is contained in:
alphafetus committed 2026-02-27 07:39:31 -05:00
1 parent be54aa8fd2
commit c98621aefa
6 files changed
+309 -122

No files matched your search

+41 -116
View File
@@ -1,17 +1,21 @@
use chrono::DateTime;
use crate::common::{DateInfo, DateRange};
use crate::components::ActionButton;
use crate::pages::api::exports::get_scholarship_csv;
use crate::pages::api::{CreateDates, CreateTestComparisons, exports::GetScholarshipCsv};
use chrono::DateTime;
use leptos::prelude::*;
use crate::common::{DateInfo, DateRange};
use crate::pages::api::{CreateDates, CreateTestComparisons};
use leptos::task::spawn_local;
use leptos::wasm_bindgen::JsCast;
use leptos::web_sys::{Blob, HtmlAnchorElement, Url, js_sys};
fn get_important_dates() -> Vec<DateInfo> {
vec! [
vec![
DateInfo {
id: uuid::Uuid::new_v4().to_string(),
title: "Provider Forms Open".to_string(),
date: DateRange::Range(
DateTime::parse_from_rfc3339("2026-01-20T07:15:00-05:00").unwrap(),
DateTime::parse_from_rfc3339("2026-03-03T23:59:00-05:00").unwrap()
DateTime::parse_from_rfc3339("2026-03-03T23:59:00-05:00").unwrap(),
),
description: "".to_string(),
},
@@ -20,7 +24,7 @@ fn get_important_dates() -> Vec<DateInfo> {
title: "Student Forms Open".to_string(),
date: DateRange::Range(
DateTime::parse_from_rfc3339("2026-03-03T07:15:00-05:00").unwrap(),
DateTime::parse_from_rfc3339("2026-03-27T14:05:00-05:00").unwrap()
DateTime::parse_from_rfc3339("2026-03-27T14:05:00-05:00").unwrap(),
),
description: "".to_string(),
},
@@ -29,7 +33,7 @@ fn get_important_dates() -> Vec<DateInfo> {
title: "Scholarship Decision Window".to_string(),
date: DateRange::Range(
DateTime::parse_from_rfc3339("2026-04-10T07:15:00-05:00").unwrap(),
DateTime::parse_from_rfc3339("2026-05-01T23:59:00-05:00").unwrap()
DateTime::parse_from_rfc3339("2026-05-01T23:59:00-05:00").unwrap(),
),
description: "Start date may change if available sooner.".to_string(),
},
@@ -52,127 +56,48 @@ fn get_important_dates() -> Vec<DateInfo> {
]
}
#[server]
async fn create_scholarship_export() -> Result<Vec<u8>, ServerFnError> {
// Exports olarship information - specifically the name of the scholarship, the name of the
// provider, and the scholarship's requirements.
// This means we'll need to get all the relations, all the scholarships, and all the contact info.
use crate::utils::server::create_dynamo_client;
use aws_sdk_dynamodb::client::Client;
use crate::pages::api::{get_comparison_info, SCHOLARSHIPS_TABLE, PROVIDER_CONTACT_TABLE};
use std::collections::HashMap;
use crate::common::ValueType;
let scan_table_items = async |client: &Client, table_name: &str| {
client.scan()
.table_name(table_name)
.send()
.await
.ok().and_then(|res| res.items)
.unwrap_or_default()
};
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct ScholarshipInfo {
name: String,
provider_name: String,
relations: Vec<String>,
}
let client = create_dynamo_client().await;
let scholarships = dbg!(scan_table_items(&client, SCHOLARSHIPS_TABLE)
.await
.iter().map(|item| {
item.into_iter()
.map(|(k, v)| (k.clone(), ValueType::from(v)))
.collect()
}).collect::<Vec<HashMap<String, ValueType>>>()
);
let contacts = dbg!(
scan_table_items(&client, PROVIDER_CONTACT_TABLE).await
.iter().map(|item| {
item.into_iter()
.map(|(k, v)| (k.clone(), ValueType::from(v)))
.collect()
}).collect::<Vec<HashMap<String, ValueType>>>()
);
let db_relations = dbg!(
get_comparison_info().await.unwrap_or_default()
);
// Build a new structure containing some of the scholarship's information.
let resolved = scholarships.into_iter()
.map(|scholarship| {
// We just want the scholarship's name, the relations, and the provider's name.
// In here, we're going to resolve all the scholarship's information to provide this in
// a readable format.
let name = scholarship.get("name")
.unwrap_or(&ValueType::String(None))
.to_string();
// The list of relations - we're going to search the master list for each comparison's
// name. It's structured as a map of lists, or a ValueType::Map(Option<HashMap<String, ValueType::List(Option<Vec<ValueType::String()>>)>>)
let relations = scholarship.get("requirements")
.unwrap_or(&ValueType::Map(None))
.as_map().ok().flatten()
.unwrap_or_default() // We have our map of categories, now we need all the values inside each category.
.iter()
.map(|(_, list)| {
list.as_list().ok().flatten()
.unwrap_or_default()
})
.flatten()
.collect::<Vec<ValueType>>() // This is a Vec<ValueType::String>
.iter().map(|val| val.clone().as_string().ok().flatten().unwrap_or_default())
.collect::<Vec<String>>();
// The provider ID, which we'll use to search for the provider's name.
let provider_id = scholarship.get("provider_id")
.unwrap_or(&ValueType::String(None))
.to_string();
// This is a mess, we'll clean it up later.
let parsed_relations = relations
.iter()
.filter_map(|relation| {
db_relations.iter().find(|search| {
search.id == *relation
})
.and_then(|search| Some(search.display_text.clone()))
})
.collect::<Vec<String>>();
ScholarshipInfo {
name,
provider_name: provider_id,
relations: parsed_relations,
}
})
.collect::<Vec<ScholarshipInfo>>();
// TODO Convert into CSV and send to client. For now I think I'm just going to serialize to json and print it.
leptos::logging::log!("Scholarships list: {:?}", resolved);
Ok(Vec::new())
}
#[component]
pub fn AdminUtilsPage() -> impl IntoView {
let create_comparisons = ServerAction::<CreateTestComparisons>::new();
let create_dates = ServerAction::<CreateDates>::new();
let create_export = ServerAction::<CreateScholarshipExport>::new();
let create_export = ServerAction::<GetScholarshipCsv>::new();
let on_click_comparisons = move |_| {
create_comparisons.dispatch(CreateTestComparisons{});
create_comparisons.dispatch(CreateTestComparisons {});
};
let on_click_dates = move |_| {
create_dates.dispatch(CreateDates{dates: get_important_dates()});
create_dates.dispatch(CreateDates {
dates: get_important_dates(),
});
};
let on_click_export = move |_| {
create_export.dispatch(CreateScholarshipExport{});
spawn_local(async move {
let file_bytes = get_scholarship_csv().await.unwrap_or_default();
let uint_arr = js_sys::Uint8Array::new_with_length(file_bytes.len() as u32);
uint_arr.copy_from(&file_bytes);
let blob_parts = js_sys::Array::new();
blob_parts.push(&uint_arr);
let blob = Blob::new_with_u8_array_sequence(&blob_parts).unwrap();
let blob_url = Url::create_object_url_with_blob(&blob).unwrap();
let window = window();
let document = window.document().unwrap();
let a: HtmlAnchorElement = document.create_element("a").unwrap().dyn_into().unwrap();
a.set_href(&blob_url);
a.set_download("scholarships.csv");
document.body().unwrap().append_child(&a).unwrap();
a.click();
document.body().unwrap().remove_child(&a).unwrap();
Url::revoke_object_url(&blob_url).unwrap();
});
};
view! {