1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AdminConfig {
8 pub title: String,
10 pub base_path: String,
12 pub theme: Theme,
14 pub items_per_page: usize,
16 pub max_items_per_page: usize,
18 pub require_auth: bool,
20 pub enable_search: bool,
22 pub enable_export: bool,
24 pub date_format: String,
26 pub datetime_format: String,
28 pub logo_url: Option<String>,
30 pub favicon_url: Option<String>,
32 pub custom_css: Option<String>,
34 pub custom_js: Option<String>,
36 pub footer_text: Option<String>,
38}
39
40impl Default for AdminConfig {
41 fn default() -> Self {
42 Self {
43 title: "Admin Dashboard".to_string(),
44 base_path: "/admin".to_string(),
45 theme: Theme::default(),
46 items_per_page: 25,
47 max_items_per_page: 100,
48 require_auth: true,
49 enable_search: true,
50 enable_export: true,
51 date_format: "%Y-%m-%d".to_string(),
52 datetime_format: "%Y-%m-%d %H:%M:%S".to_string(),
53 logo_url: None,
54 favicon_url: None,
55 custom_css: None,
56 custom_js: None,
57 footer_text: None,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Theme {
65 pub name: ThemePreset,
67 pub primary_color: String,
69 pub secondary_color: String,
71 pub accent_color: String,
73 pub background_color: String,
75 pub surface_color: String,
77 pub text_color: String,
79 pub text_muted_color: String,
81 pub border_color: String,
83 pub success_color: String,
85 pub warning_color: String,
87 pub error_color: String,
89 pub sidebar_width: String,
91 pub border_radius: String,
93 pub font_family: String,
95}
96
97impl Default for Theme {
98 fn default() -> Self {
99 Self::dark()
100 }
101}
102
103impl Theme {
104 pub fn dark() -> Self {
106 Self {
107 name: ThemePreset::Dark,
108 primary_color: "#6366f1".to_string(), secondary_color: "#8b5cf6".to_string(), accent_color: "#22d3ee".to_string(), background_color: "#0f172a".to_string(), surface_color: "#1e293b".to_string(), text_color: "#f8fafc".to_string(), text_muted_color: "#94a3b8".to_string(), border_color: "#334155".to_string(), success_color: "#22c55e".to_string(), warning_color: "#f59e0b".to_string(), error_color: "#ef4444".to_string(), sidebar_width: "260px".to_string(),
120 border_radius: "0.5rem".to_string(),
121 font_family: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif".to_string(),
122 }
123 }
124
125 pub fn light() -> Self {
127 Self {
128 name: ThemePreset::Light,
129 primary_color: "#4f46e5".to_string(), secondary_color: "#7c3aed".to_string(), accent_color: "#0891b2".to_string(), background_color: "#f8fafc".to_string(), surface_color: "#ffffff".to_string(), text_color: "#0f172a".to_string(), text_muted_color: "#64748b".to_string(), border_color: "#e2e8f0".to_string(), success_color: "#16a34a".to_string(), warning_color: "#d97706".to_string(), error_color: "#dc2626".to_string(), sidebar_width: "260px".to_string(),
141 border_radius: "0.5rem".to_string(),
142 font_family: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif".to_string(),
143 }
144 }
145
146 pub fn corporate() -> Self {
148 Self {
149 name: ThemePreset::Corporate,
150 primary_color: "#2563eb".to_string(), secondary_color: "#1d4ed8".to_string(), accent_color: "#0ea5e9".to_string(), background_color: "#f1f5f9".to_string(), surface_color: "#ffffff".to_string(),
155 text_color: "#1e293b".to_string(), text_muted_color: "#64748b".to_string(), border_color: "#cbd5e1".to_string(), success_color: "#059669".to_string(), warning_color: "#ca8a04".to_string(), error_color: "#dc2626".to_string(), sidebar_width: "240px".to_string(),
162 border_radius: "0.375rem".to_string(),
163 font_family: "'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif".to_string(),
164 }
165 }
166
167 pub fn to_css_variables(&self) -> String {
169 format!(
170 r#":root {{
171 --admin-primary: {};
172 --admin-secondary: {};
173 --admin-accent: {};
174 --admin-bg: {};
175 --admin-surface: {};
176 --admin-text: {};
177 --admin-text-muted: {};
178 --admin-border: {};
179 --admin-success: {};
180 --admin-warning: {};
181 --admin-error: {};
182 --admin-sidebar-width: {};
183 --admin-radius: {};
184 --admin-font: {};
185}}"#,
186 self.primary_color,
187 self.secondary_color,
188 self.accent_color,
189 self.background_color,
190 self.surface_color,
191 self.text_color,
192 self.text_muted_color,
193 self.border_color,
194 self.success_color,
195 self.warning_color,
196 self.error_color,
197 self.sidebar_width,
198 self.border_radius,
199 self.font_family,
200 )
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
206pub enum ThemePreset {
207 #[default]
208 Dark,
209 Light,
210 Corporate,
211 Custom,
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_default_config() {
220 let config = AdminConfig::default();
221 assert_eq!(config.title, "Admin Dashboard");
222 assert_eq!(config.base_path, "/admin");
223 assert_eq!(config.items_per_page, 25);
224 }
225
226 #[test]
227 fn test_theme_presets() {
228 let dark = Theme::dark();
229 assert_eq!(dark.name, ThemePreset::Dark);
230
231 let light = Theme::light();
232 assert_eq!(light.name, ThemePreset::Light);
233 }
234
235 #[test]
236 fn test_css_variables() {
237 let theme = Theme::dark();
238 let css = theme.to_css_variables();
239 assert!(css.contains("--admin-primary:"));
240 assert!(css.contains("--admin-bg:"));
241 }
242}
243