1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
11#[cfg_attr(feature = "ts", ts(export))]
12pub struct Key3Leaders {
13 pub scoutmaster: Option<Leader>,
14 pub committee_chair: Option<Leader>,
15 pub charter_org_rep: Option<Leader>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
21#[cfg_attr(feature = "ts", ts(export))]
22pub struct Leader {
23 pub first_name: String,
24 pub last_name: String,
25}
26
27impl Leader {
28 pub fn full_name(&self) -> String {
29 format!("{} {}", self.first_name, self.last_name)
30 }
31}
32
33#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
36#[cfg_attr(feature = "ts", ts(export))]
37pub struct UnitInfo {
38 pub name: Option<String>,
39 pub website: Option<String>,
40 pub registration_url: Option<String>,
41 pub district_name: Option<String>,
42 pub council_name: Option<String>,
43 pub charter_org_name: Option<String>,
44 pub charter_expiry: Option<String>,
45 pub meeting_location: Option<MeetingLocation>,
46 pub contacts: Vec<UnitContact>,
47 #[serde(default)]
49 pub charter_status_display: Option<String>,
50 #[serde(default)]
52 pub charter_expired: Option<bool>,
53}
54
55impl UnitInfo {
56 pub fn with_computed_fields(mut self) -> Self {
58 if let Some(ref expiry) = self.charter_expiry {
59 if let Some((status, formatted)) = crate::utils::check_expiration(expiry) {
60 self.charter_status_display = Some(status.format_expiry(&formatted));
61 self.charter_expired = Some(matches!(status, crate::utils::ExpirationStatus::Expired));
62 }
63 }
64 self
65 }
66}
67
68#[derive(Debug, Clone, Default, Serialize, Deserialize)]
70#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
71#[cfg_attr(feature = "ts", ts(export))]
72pub struct MeetingLocation {
73 pub address_line1: Option<String>,
74 pub address_line2: Option<String>,
75 pub city: Option<String>,
76 pub state: Option<String>,
77 pub zip: Option<String>,
78}
79
80impl MeetingLocation {
81 pub fn formatted(&self) -> Option<String> {
83 let mut parts = Vec::new();
84 if let Some(ref line1) = self.address_line1 {
85 if !line1.is_empty() {
86 parts.push(line1.clone());
87 }
88 }
89 if let Some(ref city) = self.city {
90 if !city.is_empty() {
91 let city_state = match &self.state {
92 Some(state) if !state.is_empty() => format!("{}, {}", city, state),
93 _ => city.clone(),
94 };
95 parts.push(city_state);
96 }
97 }
98 if parts.is_empty() {
99 None
100 } else {
101 Some(parts.join(", "))
102 }
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
109#[cfg_attr(feature = "ts", ts(export))]
110pub struct UnitContact {
111 pub first_name: Option<String>,
112 pub last_name: Option<String>,
113 pub email: Option<String>,
114 pub phone: Option<String>,
115}
116
117impl UnitContact {
118 pub fn full_name(&self) -> String {
119 format!(
120 "{} {}",
121 self.first_name.as_deref().unwrap_or(""),
122 self.last_name.as_deref().unwrap_or("")
123 )
124 .trim()
125 .to_string()
126 }
127}
128
129#[derive(Debug, Clone, Default, Serialize, Deserialize)]
131#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
132#[cfg_attr(feature = "ts", ts(export))]
133pub struct OrgProfile {
134 pub name: Option<String>,
135 pub full_name: Option<String>,
136 pub charter_org_name: Option<String>,
137 pub charter_exp_date: Option<String>,
138 pub charter_status: Option<String>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
144#[cfg_attr(feature = "ts", ts(export))]
145pub struct Commissioner {
146 pub first_name: Option<String>,
147 pub last_name: Option<String>,
148 pub position: Option<String>,
149}
150
151impl Commissioner {
152 pub fn full_name(&self) -> String {
153 format!(
154 "{} {}",
155 self.first_name.as_deref().unwrap_or(""),
156 self.last_name.as_deref().unwrap_or("")
157 )
158 .trim()
159 .to_string()
160 }
161
162 #[allow(dead_code)] pub fn position_display(&self) -> &str {
164 self.position.as_deref().unwrap_or("Unknown")
165 }
166}