1use crate::collaboration::CollaborationManager;
7use anyhow::Result;
8use chrono::{DateTime, Duration, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use uuid::Uuid;
12
13#[derive(Debug, Clone)]
15pub struct TeamDashboard {
16 config: DashboardConfig,
18 activity_feed: Vec<ActivityEvent>,
20 metrics: TeamMetrics,
22 active_sessions: HashMap<Uuid, SessionActivity>,
24 notifications: NotificationSystem,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct DashboardConfig {
31 pub refresh_interval: u64,
33 pub max_activities: usize,
35 pub real_time_updates: bool,
37 pub show_detailed_metrics: bool,
39 pub widgets: Vec<WidgetConfig>,
41 pub theme: DashboardTheme,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct WidgetConfig {
48 pub id: String,
50 pub widget_type: WidgetType,
52 pub position: WidgetPosition,
54 pub size: WidgetSize,
56 pub settings: HashMap<String, serde_json::Value>,
58 pub visible: bool,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DashboardTheme {
65 pub primary_color: String,
67 pub secondary_color: String,
69 pub background_color: String,
71 pub text_color: String,
73 pub dark_mode: bool,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ActivityEvent {
80 pub id: Uuid,
82 pub event_type: ActivityType,
84 pub actor: Uuid,
86 pub target: ActivityTarget,
88 pub timestamp: DateTime<Utc>,
90 pub description: String,
92 pub metadata: HashMap<String, serde_json::Value>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct TeamMetrics {
99 pub active_members: usize,
101 pub reports_today: usize,
103 pub annotations_today: usize,
105 pub comments_today: usize,
107 pub avg_response_time: f64,
109 pub collaboration_score: f64,
111 pub top_contributors: Vec<ContributorMetric>,
113 pub activity_trends: ActivityTrends,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct ContributorMetric {
120 pub member_id: Uuid,
122 pub name: String,
124 pub reports_count: usize,
126 pub annotations_count: usize,
128 pub comments_count: usize,
130 pub activity_score: f64,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ActivityTrends {
137 pub daily_activity: Vec<DailyActivity>,
139 pub weekly_summary: WeeklyActivity,
141 pub growth_rate: f64,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct DailyActivity {
148 pub date: DateTime<Utc>,
150 pub reports: usize,
152 pub annotations: usize,
154 pub comments: usize,
156 pub active_users: usize,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct WeeklyActivity {
163 pub total_reports: usize,
165 pub total_annotations: usize,
167 pub total_comments: usize,
169 pub peak_day: String,
171 pub avg_daily_active_users: f64,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct SessionActivity {
178 pub session_id: Uuid,
180 pub session_type: String,
182 pub participants: Vec<Uuid>,
184 pub started_at: DateTime<Utc>,
186 pub last_activity: DateTime<Utc>,
188 pub activity_count: usize,
190 pub status: String,
192}
193
194#[derive(Debug, Clone)]
196pub struct NotificationSystem {
197 notifications: Vec<DashboardNotification>,
199 settings: NotificationSettings,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct DashboardNotification {
206 pub id: Uuid,
208 pub notification_type: NotificationType,
210 pub title: String,
212 pub message: String,
214 pub priority: NotificationPriority,
216 pub timestamp: DateTime<Utc>,
218 pub target_users: Vec<Uuid>,
220 pub read_by: Vec<Uuid>,
222 pub actions: Vec<NotificationAction>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct NotificationSettings {
229 pub browser_notifications: bool,
231 pub sound_alerts: bool,
233 pub auto_dismiss_time: u64,
235 pub max_notifications: usize,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub enum ActivityType {
242 ReportShared,
243 ReportUpdated,
244 AnnotationAdded,
245 CommentPosted,
246 SessionStarted,
247 SessionEnded,
248 MemberJoined,
249 MemberLeft,
250 IssueResolved,
251 Custom(String),
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub enum ActivityTarget {
257 Report(Uuid),
258 Annotation(Uuid),
259 Comment(Uuid),
260 Session(Uuid),
261 Member(Uuid),
262 Custom(String),
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub enum WidgetType {
268 ActivityFeed,
269 TeamMetrics,
270 ActiveSessions,
271 RecentReports,
272 TopContributors,
273 ActivityChart,
274 NotificationCenter,
275 QuickActions,
276 Custom(String),
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct WidgetPosition {
282 pub x: u32,
284 pub y: u32,
286 pub col: u32,
288 pub row: u32,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct WidgetSize {
295 pub width: u32,
297 pub height: u32,
299 pub min_width: u32,
301 pub min_height: u32,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub enum NotificationType {
308 NewReport,
309 NewAnnotation,
310 NewComment,
311 SessionInvite,
312 IssueAssigned,
313 SystemAlert,
314 Custom(String),
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
319pub enum NotificationPriority {
320 Low,
321 Normal,
322 High,
323 Urgent,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct NotificationAction {
329 pub label: String,
331 pub action_type: String,
333 pub data: HashMap<String, serde_json::Value>,
335}
336
337impl TeamDashboard {
338 pub fn new(config: DashboardConfig) -> Self {
340 Self {
341 config,
342 activity_feed: Vec::new(),
343 metrics: TeamMetrics::default(),
344 active_sessions: HashMap::new(),
345 notifications: NotificationSystem::new(),
346 }
347 }
348
349 pub fn update_from_collaboration(
351 &mut self,
352 collaboration: &CollaborationManager,
353 ) -> Result<()> {
354 self.update_metrics(collaboration)?;
356
357 self.update_activity_feed(collaboration)?;
359
360 self.update_active_sessions(collaboration)?;
362
363 Ok(())
364 }
365
366 pub fn add_activity_event(
368 &mut self,
369 event_type: ActivityType,
370 actor: Uuid,
371 target: ActivityTarget,
372 description: String,
373 ) -> Uuid {
374 let event_id = Uuid::new_v4();
375 let event = ActivityEvent {
376 id: event_id,
377 event_type,
378 actor,
379 target,
380 timestamp: Utc::now(),
381 description,
382 metadata: HashMap::new(),
383 };
384
385 self.activity_feed.insert(0, event);
386
387 if self.activity_feed.len() > self.config.max_activities {
389 self.activity_feed.truncate(self.config.max_activities);
390 }
391
392 event_id
393 }
394
395 pub fn get_dashboard_data(&self) -> DashboardData {
397 DashboardData {
398 config: self.config.clone(),
399 activity_feed: self.activity_feed.clone(),
400 metrics: self.metrics.clone(),
401 active_sessions: self.active_sessions.values().cloned().collect(),
402 notifications: self.notifications.get_unread_notifications(),
403 last_updated: Utc::now(),
404 }
405 }
406
407 pub fn send_notification(
409 &mut self,
410 notification_type: NotificationType,
411 title: String,
412 message: String,
413 priority: NotificationPriority,
414 target_users: Vec<Uuid>,
415 ) -> Uuid {
416 self.notifications.send_notification(
417 notification_type,
418 title,
419 message,
420 priority,
421 target_users,
422 )
423 }
424
425 pub fn mark_notification_read(&mut self, notification_id: Uuid, user_id: Uuid) -> Result<()> {
427 self.notifications.mark_read(notification_id, user_id)
428 }
429
430 pub fn get_activity_summary(&self, days: u32) -> ActivitySummary {
432 let cutoff_date = Utc::now() - Duration::days(days as i64);
433
434 let recent_activities: Vec<_> = self
435 .activity_feed
436 .iter()
437 .filter(|activity| activity.timestamp >= cutoff_date)
438 .collect();
439
440 let total_activities = recent_activities.len();
441 let unique_contributors: std::collections::HashSet<_> =
442 recent_activities.iter().map(|a| a.actor).collect();
443
444 ActivitySummary {
445 total_activities,
446 unique_contributors: unique_contributors.len(),
447 activity_by_type: self.count_activities_by_type(&recent_activities),
448 most_active_day: self.find_most_active_day(&recent_activities),
449 period_days: days,
450 }
451 }
452
453 fn update_metrics(&mut self, collaboration: &CollaborationManager) -> Result<()> {
455 let stats = collaboration.get_collaboration_stats();
456
457 let trends = self.calculate_activity_trends();
459
460 let collaboration_score = self.calculate_collaboration_score(&stats);
462
463 self.metrics = TeamMetrics {
464 active_members: stats.team_size,
465 reports_today: self.count_today_activities(ActivityType::ReportShared),
466 annotations_today: self.count_today_activities(ActivityType::AnnotationAdded),
467 comments_today: self.count_today_activities(ActivityType::CommentPosted),
468 avg_response_time: self.calculate_avg_response_time(),
469 collaboration_score,
470 top_contributors: self.calculate_top_contributors(),
471 activity_trends: trends,
472 };
473
474 Ok(())
475 }
476
477 fn update_activity_feed(&mut self, _collaboration: &CollaborationManager) -> Result<()> {
479 Ok(())
482 }
483
484 fn update_active_sessions(&mut self, _collaboration: &CollaborationManager) -> Result<()> {
486 let now = Utc::now();
488
489 let stale_cutoff = now - Duration::hours(1);
491 self.active_sessions.retain(|_, session| session.last_activity >= stale_cutoff);
492
493 Ok(())
494 }
495
496 fn calculate_activity_trends(&self) -> ActivityTrends {
498 let now = Utc::now();
499 let mut daily_activity = Vec::new();
500
501 for i in 0..7 {
503 let date = now - Duration::days(i);
504 let day_start = date.date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc();
505 let day_end = day_start + Duration::days(1);
506
507 let day_activities: Vec<_> = self
508 .activity_feed
509 .iter()
510 .filter(|a| a.timestamp >= day_start && a.timestamp < day_end)
511 .collect();
512
513 let daily = DailyActivity {
514 date: day_start,
515 reports: day_activities
516 .iter()
517 .filter(|a| matches!(a.event_type, ActivityType::ReportShared))
518 .count(),
519 annotations: day_activities
520 .iter()
521 .filter(|a| matches!(a.event_type, ActivityType::AnnotationAdded))
522 .count(),
523 comments: day_activities
524 .iter()
525 .filter(|a| matches!(a.event_type, ActivityType::CommentPosted))
526 .count(),
527 active_users: day_activities
528 .iter()
529 .map(|a| a.actor)
530 .collect::<std::collections::HashSet<_>>()
531 .len(),
532 };
533
534 daily_activity.push(daily);
535 }
536
537 let total_reports = daily_activity.iter().map(|d| d.reports).sum();
539 let total_annotations = daily_activity.iter().map(|d| d.annotations).sum();
540 let total_comments = daily_activity.iter().map(|d| d.comments).sum();
541 let avg_daily_active_users =
542 daily_activity.iter().map(|d| d.active_users as f64).sum::<f64>() / 7.0;
543
544 let weekly_summary = WeeklyActivity {
545 total_reports,
546 total_annotations,
547 total_comments,
548 peak_day: "Monday".to_string(), avg_daily_active_users,
550 };
551
552 ActivityTrends {
553 daily_activity,
554 weekly_summary,
555 growth_rate: 0.0, }
557 }
558
559 fn calculate_collaboration_score(
560 &self,
561 _stats: &crate::collaboration::CollaborationStats,
562 ) -> f64 {
563 50.0 + (self.activity_feed.len() as f64 * 0.1)
566 }
567
568 fn calculate_top_contributors(&self) -> Vec<ContributorMetric> {
569 let mut contributor_map: HashMap<Uuid, (usize, usize, usize)> = HashMap::new();
570
571 for activity in &self.activity_feed {
572 let entry = contributor_map.entry(activity.actor).or_insert((0, 0, 0));
573 match activity.event_type {
574 ActivityType::ReportShared => entry.0 += 1,
575 ActivityType::AnnotationAdded => entry.1 += 1,
576 ActivityType::CommentPosted => entry.2 += 1,
577 _ => {},
578 }
579 }
580
581 contributor_map
582 .into_iter()
583 .map(|(member_id, (reports, annotations, comments))| {
584 let activity_score =
585 reports as f64 * 3.0 + annotations as f64 * 2.0 + comments as f64;
586 ContributorMetric {
587 member_id,
588 name: format!("User {}", member_id.to_string()[0..8].to_uppercase()),
589 reports_count: reports,
590 annotations_count: annotations,
591 comments_count: comments,
592 activity_score,
593 }
594 })
595 .collect()
596 }
597
598 fn count_today_activities(&self, activity_type: ActivityType) -> usize {
599 let today = Utc::now().date_naive();
600 self.activity_feed
601 .iter()
602 .filter(|a| {
603 a.timestamp.date_naive() == today
604 && std::mem::discriminant(&a.event_type)
605 == std::mem::discriminant(&activity_type)
606 })
607 .count()
608 }
609
610 fn calculate_avg_response_time(&self) -> f64 {
611 15.0 }
614
615 fn count_activities_by_type(&self, activities: &[&ActivityEvent]) -> HashMap<String, usize> {
616 let mut counts = HashMap::new();
617 for activity in activities {
618 let type_name = match &activity.event_type {
619 ActivityType::ReportShared => "ReportShared",
620 ActivityType::AnnotationAdded => "AnnotationAdded",
621 ActivityType::CommentPosted => "CommentPosted",
622 ActivityType::SessionStarted => "SessionStarted",
623 ActivityType::Custom(name) => name,
624 _ => "Other",
625 };
626 *counts.entry(type_name.to_string()).or_insert(0) += 1;
627 }
628 counts
629 }
630
631 fn find_most_active_day(&self, activities: &[&ActivityEvent]) -> String {
632 let mut day_counts = HashMap::new();
633 for activity in activities {
634 let day = activity.timestamp.format("%A").to_string();
635 *day_counts.entry(day).or_insert(0) += 1;
636 }
637
638 day_counts
639 .into_iter()
640 .max_by_key(|(_, count)| *count)
641 .map(|(day, _)| day)
642 .unwrap_or_else(|| "Monday".to_string())
643 }
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
648pub struct DashboardData {
649 pub config: DashboardConfig,
650 pub activity_feed: Vec<ActivityEvent>,
651 pub metrics: TeamMetrics,
652 pub active_sessions: Vec<SessionActivity>,
653 pub notifications: Vec<DashboardNotification>,
654 pub last_updated: DateTime<Utc>,
655}
656
657#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct ActivitySummary {
660 pub total_activities: usize,
661 pub unique_contributors: usize,
662 pub activity_by_type: HashMap<String, usize>,
663 pub most_active_day: String,
664 pub period_days: u32,
665}
666
667impl Default for NotificationSystem {
668 fn default() -> Self {
669 Self::new()
670 }
671}
672
673impl NotificationSystem {
674 pub fn new() -> Self {
675 Self {
676 notifications: Vec::new(),
677 settings: NotificationSettings {
678 browser_notifications: true,
679 sound_alerts: false,
680 auto_dismiss_time: 5,
681 max_notifications: 50,
682 },
683 }
684 }
685
686 pub fn send_notification(
687 &mut self,
688 notification_type: NotificationType,
689 title: String,
690 message: String,
691 priority: NotificationPriority,
692 target_users: Vec<Uuid>,
693 ) -> Uuid {
694 let notification_id = Uuid::new_v4();
695 let notification = DashboardNotification {
696 id: notification_id,
697 notification_type,
698 title,
699 message,
700 priority,
701 timestamp: Utc::now(),
702 target_users,
703 read_by: Vec::new(),
704 actions: Vec::new(),
705 };
706
707 self.notifications.insert(0, notification);
708
709 if self.notifications.len() > self.settings.max_notifications {
711 self.notifications.truncate(self.settings.max_notifications);
712 }
713
714 notification_id
715 }
716
717 pub fn mark_read(&mut self, notification_id: Uuid, user_id: Uuid) -> Result<()> {
718 if let Some(notification) = self.notifications.iter_mut().find(|n| n.id == notification_id)
719 {
720 if !notification.read_by.contains(&user_id) {
721 notification.read_by.push(user_id);
722 }
723 Ok(())
724 } else {
725 Err(anyhow::anyhow!("Notification not found"))
726 }
727 }
728
729 pub fn get_unread_notifications(&self) -> Vec<DashboardNotification> {
730 self.notifications.clone()
731 }
732}
733
734impl Default for TeamMetrics {
735 fn default() -> Self {
736 Self {
737 active_members: 0,
738 reports_today: 0,
739 annotations_today: 0,
740 comments_today: 0,
741 avg_response_time: 0.0,
742 collaboration_score: 0.0,
743 top_contributors: Vec::new(),
744 activity_trends: ActivityTrends {
745 daily_activity: Vec::new(),
746 weekly_summary: WeeklyActivity {
747 total_reports: 0,
748 total_annotations: 0,
749 total_comments: 0,
750 peak_day: "Monday".to_string(),
751 avg_daily_active_users: 0.0,
752 },
753 growth_rate: 0.0,
754 },
755 }
756 }
757}
758
759impl Default for DashboardConfig {
760 fn default() -> Self {
761 Self {
762 refresh_interval: 30,
763 max_activities: 100,
764 real_time_updates: true,
765 show_detailed_metrics: true,
766 widgets: vec![
767 WidgetConfig {
768 id: "activity-feed".to_string(),
769 widget_type: WidgetType::ActivityFeed,
770 position: WidgetPosition {
771 x: 0,
772 y: 0,
773 col: 0,
774 row: 0,
775 },
776 size: WidgetSize {
777 width: 6,
778 height: 8,
779 min_width: 4,
780 min_height: 6,
781 },
782 settings: HashMap::new(),
783 visible: true,
784 },
785 WidgetConfig {
786 id: "team-metrics".to_string(),
787 widget_type: WidgetType::TeamMetrics,
788 position: WidgetPosition {
789 x: 6,
790 y: 0,
791 col: 6,
792 row: 0,
793 },
794 size: WidgetSize {
795 width: 6,
796 height: 4,
797 min_width: 4,
798 min_height: 3,
799 },
800 settings: HashMap::new(),
801 visible: true,
802 },
803 ],
804 theme: DashboardTheme {
805 primary_color: "#007acc".to_string(),
806 secondary_color: "#6c757d".to_string(),
807 background_color: "#ffffff".to_string(),
808 text_color: "#333333".to_string(),
809 dark_mode: false,
810 },
811 }
812 }
813}
814
815#[cfg(test)]
816mod tests {
817 use super::*;
818
819 #[test]
820 fn test_dashboard_creation() {
821 let config = DashboardConfig::default();
822 let dashboard = TeamDashboard::new(config);
823
824 assert_eq!(dashboard.activity_feed.len(), 0);
825 assert_eq!(dashboard.metrics.active_members, 0);
826 }
827
828 #[test]
829 fn test_activity_event_addition() {
830 let config = DashboardConfig::default();
831 let mut dashboard = TeamDashboard::new(config);
832
833 let actor = Uuid::new_v4();
834 let target = ActivityTarget::Report(Uuid::new_v4());
835
836 let event_id = dashboard.add_activity_event(
837 ActivityType::ReportShared,
838 actor,
839 target,
840 "Shared debugging report".to_string(),
841 );
842
843 assert_eq!(dashboard.activity_feed.len(), 1);
844 assert_eq!(dashboard.activity_feed[0].id, event_id);
845 }
846
847 #[test]
848 fn test_notification_system() {
849 let mut notification_system = NotificationSystem::new();
850
851 let notification_id = notification_system.send_notification(
852 NotificationType::NewReport,
853 "New Report".to_string(),
854 "A new debugging report has been shared".to_string(),
855 NotificationPriority::Normal,
856 vec![Uuid::new_v4()],
857 );
858
859 assert_eq!(notification_system.notifications.len(), 1);
860 assert_eq!(notification_system.notifications[0].id, notification_id);
861 }
862
863 #[test]
864 fn test_activity_summary() {
865 let config = DashboardConfig::default();
866 let mut dashboard = TeamDashboard::new(config);
867
868 for i in 0..5 {
870 dashboard.add_activity_event(
871 ActivityType::ReportShared,
872 Uuid::new_v4(),
873 ActivityTarget::Report(Uuid::new_v4()),
874 format!("Test activity {}", i),
875 );
876 }
877
878 let summary = dashboard.get_activity_summary(7);
879 assert_eq!(summary.total_activities, 5);
880 assert_eq!(summary.period_days, 7);
881 }
882}