1use crate::{Client, course::Course};
2use chrono::NaiveDateTime;
3use color_eyre::Result;
4use scraper::{Html, Selector};
5
6#[derive(Debug)]
7pub struct Report {
8 pub title: String,
9 pub state: ReportState,
10 pub start_date: Option<ReportDate>,
11 pub due_date: Option<ReportDate>,
12}
13
14#[derive(Debug)]
15pub struct ReportDate {
16 pub date: NaiveDateTime,
17 pub importance_level: ReportImportanceLevel,
18}
19
20impl ReportDate {
21 fn new(date: &str) -> Self {
22 const DATE_FORMAT: &str = "%Y-%m-%d %H:%M";
23 let date = NaiveDateTime::parse_from_str(date, DATE_FORMAT).unwrap();
24 let importance_level = ReportImportanceLevel::from(date);
25
26 ReportDate {
27 date,
28 importance_level,
29 }
30 }
31}
32
33#[derive(Debug, PartialEq, Eq)]
34pub enum ReportState {
35 Todo,
36 Done,
37}
38
39#[derive(Debug)]
40pub enum ReportImportanceLevel {
41 None,
42 Low,
43 Medium,
44 High,
45}
46
47impl From<NaiveDateTime> for ReportImportanceLevel {
48 fn from(date: NaiveDateTime) -> Self {
49 let today: chrono::NaiveDate = chrono::Local::now().date_naive();
50 let due_date: chrono::NaiveDate = date.date();
51
52 if due_date < today {
53 ReportImportanceLevel::None
54 } else {
55 let tomorrow = today + chrono::Duration::days(1);
56 let one_week_later = today + chrono::Duration::days(7);
57
58 if due_date <= tomorrow {
59 ReportImportanceLevel::High
60 } else if due_date <= one_week_later {
61 ReportImportanceLevel::Medium
62 } else {
63 ReportImportanceLevel::Low
64 }
65 }
66 }
67}
68
69impl Client {
70 pub async fn get_reports(&self, course: &Course) -> Result<Vec<Report>> {
71 let url = format!("{}/{}_report", self.base_url, course.id);
72
73 let request = self.request(reqwest::Method::GET, url);
74
75 let response = request.send().await?;
76 let body = response.text().await?;
77
78 let document = Html::parse_document(&body);
79
80 let selector = Selector::parse("table.stdlist tr:not(.title)").unwrap();
81 let reports_element = document.select(&selector);
82
83 let reports = reports_element
84 .map(|report_element| {
85 let selector = Selector::parse("td").unwrap();
86 let mut rows = report_element.select(&selector);
87
88 let title = {
89 let row = rows.next().unwrap();
90 let selector = Selector::parse("a").unwrap();
91 let report_title_element = row.select(&selector).next().unwrap();
92 report_title_element.inner_html()
93 };
94
95 let state = {
96 let row = rows.next().unwrap();
97 let selector = Selector::parse("strong").unwrap();
98
99 if row.select(&selector).next().is_some() {
100 ReportState::Done
101 } else {
102 ReportState::Todo
103 }
104 };
105
106 let start_date = {
107 let row = rows.next().unwrap();
108 let date = row.inner_html();
109 if date.is_empty() {
110 None
111 } else {
112 Some(ReportDate::new(&date))
113 }
114 };
115
116 let due_date = {
117 let row = rows.next().unwrap();
118 let date = row.inner_html();
119 if date.is_empty() {
120 None
121 } else {
122 Some(ReportDate::new(&date))
123 }
124 };
125
126 Report {
127 title,
128 state,
129 start_date,
130 due_date,
131 }
132 })
133 .collect::<Vec<Report>>();
134
135 Ok(reports)
136 }
137}