1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum Priority {
11 Low,
13 #[default]
15 Medium,
16 High,
18 Urgent,
20}
21
22impl Priority {
23 pub const fn all() -> &'static [Self] {
25 &[Self::Low, Self::Medium, Self::High, Self::Urgent]
26 }
27
28 pub const fn name(&self) -> &'static str {
30 match self {
31 Self::Low => "Low",
32 Self::Medium => "Medium",
33 Self::High => "High",
34 Self::Urgent => "Urgent",
35 }
36 }
37
38 pub const fn icon(&self) -> &'static str {
40 match self {
41 Self::Low => "○",
42 Self::Medium => "◐",
43 Self::High => "●",
44 Self::Urgent => "◉",
45 }
46 }
47
48 pub fn next(&self) -> Self {
50 match self {
51 Self::Low => Self::Medium,
52 Self::Medium => Self::High,
53 Self::High => Self::Urgent,
54 Self::Urgent => Self::Low,
55 }
56 }
57
58 pub fn prev(&self) -> Self {
60 match self {
61 Self::Low => Self::Urgent,
62 Self::Medium => Self::Low,
63 Self::High => Self::Medium,
64 Self::Urgent => Self::High,
65 }
66 }
67}
68
69impl std::fmt::Display for Priority {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(f, "{}", self.name())
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct Task {
78 pub id: Uuid,
80 pub title: String,
82 pub description: Option<String>,
84 pub url: Option<String>,
86 pub priority: Priority,
88 pub completed: bool,
90 pub list_id: Uuid,
92 pub tag_ids: Vec<Uuid>,
94 pub created_at: DateTime<Utc>,
96 pub updated_at: DateTime<Utc>,
98 pub completed_at: Option<DateTime<Utc>>,
100 pub due_date: Option<DateTime<Utc>>,
102}
103
104impl Task {
105 pub fn new(title: impl Into<String>, list_id: Uuid) -> Self {
107 let now = Utc::now();
108 Self {
109 id: Uuid::new_v4(),
110 title: title.into(),
111 description: None,
112 url: None,
113 priority: Priority::default(),
114 completed: false,
115 list_id,
116 tag_ids: Vec::new(),
117 created_at: now,
118 updated_at: now,
119 completed_at: None,
120 due_date: None,
121 }
122 }
123
124 pub fn complete(&mut self) {
126 self.completed = true;
127 self.completed_at = Some(Utc::now());
128 self.updated_at = Utc::now();
129 }
130
131 pub fn uncomplete(&mut self) {
133 self.completed = false;
134 self.completed_at = None;
135 self.updated_at = Utc::now();
136 }
137
138 pub fn toggle(&mut self) {
140 if self.completed {
141 self.uncomplete();
142 } else {
143 self.complete();
144 }
145 }
146
147 pub fn with_description(mut self, description: impl Into<String>) -> Self {
149 self.description = Some(description.into());
150 self
151 }
152
153 pub fn with_url(mut self, url: impl Into<String>) -> Self {
155 self.url = Some(url.into());
156 self
157 }
158
159 pub fn with_priority(mut self, priority: Priority) -> Self {
161 self.priority = priority;
162 self
163 }
164
165 pub fn with_tag(mut self, tag_id: Uuid) -> Self {
167 if !self.tag_ids.contains(&tag_id) {
168 self.tag_ids.push(tag_id);
169 }
170 self
171 }
172
173 pub fn with_due_date(mut self, due_date: DateTime<Utc>) -> Self {
175 self.due_date = Some(due_date);
176 self
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct List {
183 pub id: Uuid,
185 pub name: String,
187 pub description: Option<String>,
189 pub icon: String,
191 pub color: Option<String>,
193 pub is_inbox: bool,
195 pub created_at: DateTime<Utc>,
197 pub updated_at: DateTime<Utc>,
199 pub sort_order: i32,
201}
202
203impl List {
204 pub fn new(name: impl Into<String>) -> Self {
206 let now = Utc::now();
207 Self {
208 id: Uuid::new_v4(),
209 name: name.into(),
210 description: None,
211 icon: "📋".to_string(),
212 color: None,
213 is_inbox: false,
214 created_at: now,
215 updated_at: now,
216 sort_order: 0,
217 }
218 }
219
220 pub fn inbox() -> Self {
222 let now = Utc::now();
223 Self {
224 id: Uuid::new_v4(),
225 name: "Inbox".to_string(),
226 description: Some("Default list for new tasks".to_string()),
227 icon: "📥".to_string(),
228 color: None,
229 is_inbox: true,
230 created_at: now,
231 updated_at: now,
232 sort_order: -1, }
234 }
235
236 pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
238 self.icon = icon.into();
239 self
240 }
241
242 pub fn with_color(mut self, color: impl Into<String>) -> Self {
244 self.color = Some(color.into());
245 self
246 }
247
248 pub fn with_description(mut self, description: impl Into<String>) -> Self {
250 self.description = Some(description.into());
251 self
252 }
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct Tag {
258 pub id: Uuid,
260 pub name: String,
262 pub color: String,
264 pub created_at: DateTime<Utc>,
266}
267
268impl Tag {
269 pub fn new(name: impl Into<String>) -> Self {
271 Self {
272 id: Uuid::new_v4(),
273 name: name.into(),
274 color: Self::random_color(),
275 created_at: Utc::now(),
276 }
277 }
278
279 pub fn with_color(mut self, color: impl Into<String>) -> Self {
281 self.color = color.into();
282 self
283 }
284
285 fn random_color() -> String {
287 const COLORS: &[&str] = &[
288 "#f38ba8", "#fab387", "#f9e2af", "#a6e3a1", "#94e2d5", "#89b4fa", "#cba6f7", "#f5c2e7", "#eba0ac", "#89dceb", ];
299 use std::time::{SystemTime, UNIX_EPOCH};
300 let seed = SystemTime::now()
301 .duration_since(UNIX_EPOCH)
302 .unwrap()
303 .as_nanos() as usize;
304 COLORS[seed % COLORS.len()].to_string()
305 }
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
310#[serde(rename_all = "lowercase")]
311pub enum ExportFormat {
312 Json,
314 TodoTxt,
316 Markdown,
318 Csv,
320}
321
322impl ExportFormat {
323 pub const fn all() -> &'static [Self] {
325 &[Self::Json, Self::TodoTxt, Self::Markdown, Self::Csv]
326 }
327
328 pub const fn name(&self) -> &'static str {
330 match self {
331 Self::Json => "JSON",
332 Self::TodoTxt => "todo.txt",
333 Self::Markdown => "Markdown",
334 Self::Csv => "CSV",
335 }
336 }
337
338 pub const fn extension(&self) -> &'static str {
340 match self {
341 Self::Json => "json",
342 Self::TodoTxt => "txt",
343 Self::Markdown => "md",
344 Self::Csv => "csv",
345 }
346 }
347}