
Some checks are pending
iOS UniFFI Build / build-ios (push) Waiting to run
Add church management API library with cross-platform support for iOS, Android, and WASM. Features include event management, bulletin handling, contact forms, and authentication.
159 lines
5.2 KiB
Rust
159 lines
5.2 KiB
Rust
use crate::models::ClientEvent;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FormattedEvent {
|
|
pub formatted_time: String,
|
|
pub formatted_date_time: String,
|
|
pub is_multi_day: bool,
|
|
pub formatted_date_range: String,
|
|
}
|
|
|
|
/// Format time range for display
|
|
pub fn format_time_range(start_time: &str, end_time: &str) -> String {
|
|
format!("{} - {}", start_time, end_time)
|
|
}
|
|
|
|
/// Check if event appears to be multi-day based on date format
|
|
pub fn is_multi_day_event(date: &str) -> bool {
|
|
date.contains(" - ")
|
|
}
|
|
|
|
/// Format date and time for display, handling multi-day events
|
|
pub fn format_date_time(date: &str, start_time: &str, end_time: &str) -> String {
|
|
if is_multi_day_event(date) {
|
|
// For multi-day events, integrate times with their respective dates
|
|
let components: Vec<&str> = date.split(" - ").collect();
|
|
if components.len() == 2 {
|
|
format!("{} at {} - {} at {}", components[0], start_time, components[1], end_time)
|
|
} else {
|
|
date.to_string() // Fallback to original date
|
|
}
|
|
} else {
|
|
// Single day events: return just the date (time displayed separately)
|
|
date.to_string()
|
|
}
|
|
}
|
|
|
|
/// Format a client event with all display formatting logic
|
|
pub fn format_event_for_display(event: &ClientEvent) -> FormattedEvent {
|
|
let start_time = &event.start_time;
|
|
let end_time = &event.end_time;
|
|
|
|
// Derive formatted date from start_time since ClientEvent no longer has date field
|
|
let formatted_date = format_date_from_timestamp(start_time);
|
|
|
|
FormattedEvent {
|
|
formatted_time: format_time_range(start_time, end_time),
|
|
formatted_date_time: format_date_time(&formatted_date, start_time, end_time),
|
|
is_multi_day: is_multi_day_event(&formatted_date),
|
|
formatted_date_range: formatted_date,
|
|
}
|
|
}
|
|
|
|
/// Extract formatted date from ISO timestamp
|
|
fn format_date_from_timestamp(timestamp: &str) -> String {
|
|
use chrono::{DateTime, FixedOffset};
|
|
|
|
if let Ok(dt) = DateTime::parse_from_str(timestamp, "%Y-%m-%dT%H:%M:%S%z") {
|
|
dt.format("%A, %B %d, %Y").to_string()
|
|
} else {
|
|
"Date TBD".to_string()
|
|
}
|
|
}
|
|
|
|
/// Format duration in minutes to human readable format
|
|
pub fn format_duration_minutes(minutes: i64) -> String {
|
|
if minutes < 60 {
|
|
format!("{} min", minutes)
|
|
} else {
|
|
let hours = minutes / 60;
|
|
let remaining_minutes = minutes % 60;
|
|
if remaining_minutes == 0 {
|
|
format!("{} hr", hours)
|
|
} else {
|
|
format!("{} hr {} min", hours, remaining_minutes)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Format spots remaining for events
|
|
pub fn format_spots_remaining(current: Option<u32>, max: Option<u32>) -> Option<String> {
|
|
match (current, max) {
|
|
(Some(current), Some(max)) => {
|
|
let remaining = max.saturating_sub(current);
|
|
if remaining == 0 {
|
|
Some("Event Full".to_string())
|
|
} else {
|
|
Some(format!("{} spots remaining", remaining))
|
|
}
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Check if event registration is full
|
|
pub fn is_event_full(current: Option<u32>, max: Option<u32>) -> bool {
|
|
match (current, max) {
|
|
(Some(current), Some(max)) => current >= max,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_format_time_range() {
|
|
assert_eq!(format_time_range("9:00 AM", "5:00 PM"), "9:00 AM - 5:00 PM");
|
|
assert_eq!(format_time_range("", ""), " - ");
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_multi_day_event() {
|
|
assert!(is_multi_day_event("Saturday, Aug 30, 2025 - Sunday, Aug 31, 2025"));
|
|
assert!(!is_multi_day_event("Saturday, Aug 30, 2025"));
|
|
assert!(!is_multi_day_event(""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_date_time() {
|
|
// Single day event
|
|
let result = format_date_time("Saturday, Aug 30, 2025", "6:00 PM", "8:00 PM");
|
|
assert_eq!(result, "Saturday, Aug 30, 2025");
|
|
|
|
// Multi-day event
|
|
let result = format_date_time(
|
|
"Saturday, Aug 30, 2025 - Sunday, Aug 31, 2025",
|
|
"6:00 PM",
|
|
"6:00 AM"
|
|
);
|
|
assert_eq!(result, "Saturday, Aug 30, 2025 at 6:00 PM - Sunday, Aug 31, 2025 at 6:00 AM");
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_duration_minutes() {
|
|
assert_eq!(format_duration_minutes(30), "30 min");
|
|
assert_eq!(format_duration_minutes(60), "1 hr");
|
|
assert_eq!(format_duration_minutes(90), "1 hr 30 min");
|
|
assert_eq!(format_duration_minutes(120), "2 hr");
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_spots_remaining() {
|
|
assert_eq!(format_spots_remaining(Some(8), Some(10)), Some("2 spots remaining".to_string()));
|
|
assert_eq!(format_spots_remaining(Some(10), Some(10)), Some("Event Full".to_string()));
|
|
assert_eq!(format_spots_remaining(None, Some(10)), None);
|
|
assert_eq!(format_spots_remaining(Some(5), None), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_event_full() {
|
|
assert!(is_event_full(Some(10), Some(10)));
|
|
assert!(is_event_full(Some(11), Some(10))); // Over capacity
|
|
assert!(!is_event_full(Some(9), Some(10)));
|
|
assert!(!is_event_full(None, Some(10)));
|
|
assert!(!is_event_full(Some(5), None));
|
|
}
|
|
} |