armature_admin/
dashboard.rs

1//! Dashboard views for admin
2
3use crate::{AdminInstance, StatCard, QuickAction};
4use serde::{Deserialize, Serialize};
5
6/// Dashboard view data
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DashboardView {
9    /// Page title
10    pub title: String,
11    /// Statistics cards
12    pub stats: Vec<StatCard>,
13    /// Quick actions
14    pub quick_actions: Vec<QuickAction>,
15    /// Recent activity
16    pub recent_activity: Vec<ActivityItem>,
17    /// Model summaries
18    pub model_summaries: Vec<ModelSummary>,
19}
20
21impl DashboardView {
22    /// Create a new dashboard view
23    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, // Would be populated from database
32                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    /// Set statistics
67    pub fn with_stats(mut self, stats: Vec<StatCard>) -> Self {
68        self.stats = stats;
69        self
70    }
71
72    /// Set recent activity
73    pub fn with_activity(mut self, activity: Vec<ActivityItem>) -> Self {
74        self.recent_activity = activity;
75        self
76    }
77}
78
79/// Activity item for dashboard
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ActivityItem {
82    /// Action type
83    pub action: ActivityAction,
84    /// Model name
85    pub model: String,
86    /// Record identifier/name
87    pub record: String,
88    /// Record ID
89    pub record_id: String,
90    /// User who performed the action
91    pub user: Option<String>,
92    /// Timestamp
93    pub timestamp: chrono::DateTime<chrono::Utc>,
94    /// URL to the record
95    pub url: Option<String>,
96}
97
98impl ActivityItem {
99    /// Create a new activity item
100    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    /// Set record ID
113    pub fn record_id(mut self, id: impl Into<String>) -> Self {
114        self.record_id = id.into();
115        self
116    }
117
118    /// Set user
119    pub fn user(mut self, user: impl Into<String>) -> Self {
120        self.user = Some(user.into());
121        self
122    }
123
124    /// Set URL
125    pub fn url(mut self, url: impl Into<String>) -> Self {
126        self.url = Some(url.into());
127        self
128    }
129
130    /// Get action description
131    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/// Activity action type
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
143pub enum ActivityAction {
144    Create,
145    Update,
146    Delete,
147    View,
148}
149
150impl ActivityAction {
151    /// Get icon for action
152    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    /// Get color for action
162    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/// Model summary for dashboard
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ModelSummary {
175    /// Model name
176    pub name: String,
177    /// Verbose name
178    pub verbose_name: String,
179    /// Icon
180    pub icon: Option<String>,
181    /// Total record count
182    pub count: usize,
183    /// Recent record count
184    pub recent_count: usize,
185    /// URL to list view
186    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