1use crate::{AdminInstance, StatCard, QuickAction};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DashboardView {
9 pub title: String,
11 pub stats: Vec<StatCard>,
13 pub quick_actions: Vec<QuickAction>,
15 pub recent_activity: Vec<ActivityItem>,
17 pub model_summaries: Vec<ModelSummary>,
19}
20
21impl DashboardView {
22 pub fn new(admin: &AdminInstance) -> Self {
24 let model_summaries = admin
25 .models()
26 .iter()
27 .map(|m| ModelSummary {
28 name: m.name.clone(),
29 verbose_name: m.verbose_name.clone(),
30 icon: m.icon.clone(),
31 count: 0, recent_count: 0,
33 url: format!("{}/{}", admin.config.base_path, m.name),
34 })
35 .collect();
36
37 Self {
38 title: admin.config.title.clone(),
39 stats: vec![
40 StatCard {
41 title: "Total Records".to_string(),
42 value: "0".to_string(),
43 change: None,
44 icon: Some("database".to_string()),
45 color: None,
46 link: None,
47 },
48 ],
49 quick_actions: admin
50 .models()
51 .iter()
52 .filter(|m| m.can_add)
53 .take(4)
54 .map(|m| QuickAction {
55 label: format!("Add {}", m.verbose_name_singular),
56 url: format!("{}/{}/add", admin.config.base_path, m.name),
57 icon: Some("plus".to_string()),
58 css_class: None,
59 })
60 .collect(),
61 recent_activity: Vec::new(),
62 model_summaries,
63 }
64 }
65
66 pub fn with_stats(mut self, stats: Vec<StatCard>) -> Self {
68 self.stats = stats;
69 self
70 }
71
72 pub fn with_activity(mut self, activity: Vec<ActivityItem>) -> Self {
74 self.recent_activity = activity;
75 self
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ActivityItem {
82 pub action: ActivityAction,
84 pub model: String,
86 pub record: String,
88 pub record_id: String,
90 pub user: Option<String>,
92 pub timestamp: chrono::DateTime<chrono::Utc>,
94 pub url: Option<String>,
96}
97
98impl ActivityItem {
99 pub fn new(action: ActivityAction, model: impl Into<String>, record: impl Into<String>) -> Self {
101 Self {
102 action,
103 model: model.into(),
104 record: record.into(),
105 record_id: String::new(),
106 user: None,
107 timestamp: chrono::Utc::now(),
108 url: None,
109 }
110 }
111
112 pub fn record_id(mut self, id: impl Into<String>) -> Self {
114 self.record_id = id.into();
115 self
116 }
117
118 pub fn user(mut self, user: impl Into<String>) -> Self {
120 self.user = Some(user.into());
121 self
122 }
123
124 pub fn url(mut self, url: impl Into<String>) -> Self {
126 self.url = Some(url.into());
127 self
128 }
129
130 pub fn description(&self) -> String {
132 match self.action {
133 ActivityAction::Create => format!("Created {} \"{}\"", self.model, self.record),
134 ActivityAction::Update => format!("Updated {} \"{}\"", self.model, self.record),
135 ActivityAction::Delete => format!("Deleted {} \"{}\"", self.model, self.record),
136 ActivityAction::View => format!("Viewed {} \"{}\"", self.model, self.record),
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
143pub enum ActivityAction {
144 Create,
145 Update,
146 Delete,
147 View,
148}
149
150impl ActivityAction {
151 pub fn icon(&self) -> &'static str {
153 match self {
154 Self::Create => "plus-circle",
155 Self::Update => "edit",
156 Self::Delete => "trash",
157 Self::View => "eye",
158 }
159 }
160
161 pub fn color(&self) -> &'static str {
163 match self {
164 Self::Create => "success",
165 Self::Update => "warning",
166 Self::Delete => "error",
167 Self::View => "info",
168 }
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ModelSummary {
175 pub name: String,
177 pub verbose_name: String,
179 pub icon: Option<String>,
181 pub count: usize,
183 pub recent_count: usize,
185 pub url: String,
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_activity_description() {
195 let activity = ActivityItem::new(ActivityAction::Create, "User", "Alice");
196 assert_eq!(activity.description(), "Created User \"Alice\"");
197 }
198
199 #[test]
200 fn test_activity_action_icon() {
201 assert_eq!(ActivityAction::Create.icon(), "plus-circle");
202 assert_eq!(ActivityAction::Delete.icon(), "trash");
203 }
204}
205