✨ Add Type Date in feature
Changes
2 files changed, +145 -1
MODIFY
app/task/[id].tsx
+24 -1
@@ -9,6 +9,7 @@
9
9
import { taskService, projectService, tagService } from '@/services';
10
10
import { Colors } from '@/constants/theme';
11
11
import { useColorScheme } from '@/hooks/use-color-scheme';
12
+import { parseTaskTitle } from '@/utils/date-parser';
12
13
13
14
export default function TaskDetailScreen() {
14
15
const { id, dueDate: dueDateParam, projectId: projectIdParam } = useLocalSearchParams<{
@@ -165,6 +166,28 @@
165
166
setShowDatePicker(true);
166
167
};
167
168
169
+ const handleTitleChange = (text: string) => {
170
+ // Only parse if the text ends with a space (user finished typing a word)
171
+ const shouldParse = text.endsWith(' ');
172
+
173
+ if (shouldParse && text.trim()) {
174
+ const parsed = parseTaskTitle(text.trim());
175
+
176
+ // Only update if we actually found a date
177
+ if (parsed.detectedDate) {
178
+ setTask({
179
+ ...task,
180
+ title: '', // Clear the field so user can type the actual task
181
+ dueDate: parsed.detectedDate,
182
+ });
183
+ return;
184
+ }
185
+ }
186
+
187
+ // Otherwise just update the title
188
+ setTask({ ...task, title: text });
189
+ };
190
+
168
191
return (
169
192
<SafeAreaView style={styles.container}>
170
193
<ThemedView style={[styles.header, { borderBottomColor: colors.border }]}>
@@ -187,7 +210,7 @@
187
210
color: colors.text
188
211
}]}
189
212
value={task.title}
190
- onChangeText={(text) => setTask({ ...task, title: text })}
213
+ onChangeText={handleTitleChange}
191
214
placeholder="Enter task title"
192
215
placeholderTextColor={colors.placeholder}
193
216
autoFocus={isNew}
ADD
utils/date-parser.ts
+121 -0
@@ -0,0 +1,121 @@
1
+/**
2
+ * Date Parser Utility
3
+ * Intelligently parses dates from task titles
4
+ */
5
+
6
+interface ParseResult {
7
+ cleanedTitle: string;
8
+ detectedDate?: number;
9
+}
10
+
11
+// Weekday mappings
12
+const WEEKDAYS_DE = ['montag', 'dienstag', 'mittwoch', 'donnerstag', 'freitag', 'samstag', 'sonntag'];
13
+const WEEKDAYS_EN = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
14
+
15
+/**
16
+ * Get the next occurrence of a specific weekday
17
+ * @param targetDay - 0 (Monday) to 6 (Sunday)
18
+ * @returns timestamp for the next occurrence of that weekday
19
+ */
20
+function getNextWeekday(targetDay: number): number {
21
+ const today = new Date();
22
+ const currentDay = (today.getDay() + 6) % 7; // Convert Sunday=0 to Monday=0
23
+
24
+ let daysUntilTarget = targetDay - currentDay;
25
+
26
+ // If the target day is today or has passed this week, get next week's occurrence
27
+ if (daysUntilTarget <= 0) {
28
+ daysUntilTarget += 7;
29
+ }
30
+
31
+ const targetDate = new Date(today);
32
+ targetDate.setDate(today.getDate() + daysUntilTarget);
33
+ targetDate.setHours(23, 59, 59, 999);
34
+
35
+ return targetDate.getTime();
36
+}
37
+
38
+/**
39
+ * Parse task title for date keywords and extract them
40
+ * @param title - The task title to parse
41
+ * @returns Object with cleaned title and detected date
42
+ */
43
+export function parseTaskTitle(title: string): ParseResult {
44
+ let cleanedTitle = title;
45
+ let detectedDate: number | undefined;
46
+
47
+ const lowerTitle = title.toLowerCase();
48
+
49
+ // Check for "today" / "heute" first (higher priority)
50
+ if (/\b(heute|today)\b/i.test(lowerTitle)) {
51
+ const today = new Date();
52
+ today.setHours(23, 59, 59, 999);
53
+ detectedDate = today.getTime();
54
+ cleanedTitle = cleanedTitle.replace(/\b(heute|today)\b/gi, '').trim();
55
+ }
56
+
57
+ // Check for "tomorrow" / "morgen"
58
+ else if (/\b(morgen|tomorrow)\b/i.test(lowerTitle)) {
59
+ const tomorrow = new Date();
60
+ tomorrow.setDate(tomorrow.getDate() + 1);
61
+ tomorrow.setHours(23, 59, 59, 999);
62
+ detectedDate = tomorrow.getTime();
63
+ cleanedTitle = cleanedTitle.replace(/\b(morgen|tomorrow)\b/gi, '').trim();
64
+ }
65
+
66
+ // Check for German weekdays
67
+ else {
68
+ for (let index = 0; index < WEEKDAYS_DE.length; index++) {
69
+ const weekday = WEEKDAYS_DE[index];
70
+ const regex = new RegExp(`\\b${weekday}\\b`, 'gi');
71
+ if (regex.test(lowerTitle)) {
72
+ detectedDate = getNextWeekday(index);
73
+ cleanedTitle = cleanedTitle.replace(regex, '').trim();
74
+ break;
75
+ }
76
+ }
77
+ }
78
+
79
+ // Check for English weekdays (only if no German weekday was found)
80
+ if (!detectedDate) {
81
+ for (let index = 0; index < WEEKDAYS_EN.length; index++) {
82
+ const weekday = WEEKDAYS_EN[index];
83
+ const regex = new RegExp(`\\b${weekday}\\b`, 'gi');
84
+ if (regex.test(lowerTitle)) {
85
+ detectedDate = getNextWeekday(index);
86
+ cleanedTitle = cleanedTitle.replace(regex, '').trim();
87
+ break;
88
+ }
89
+ }
90
+ }
91
+
92
+ // Clean up extra spaces and trim
93
+ cleanedTitle = cleanedTitle.replace(/\s+/g, ' ').trim();
94
+
95
+ // If title is now empty, return original title without date
96
+ if (!cleanedTitle && title.trim()) {
97
+ return {
98
+ cleanedTitle: title.trim(),
99
+ detectedDate,
100
+ };
101
+ }
102
+
103
+ return {
104
+ cleanedTitle,
105
+ detectedDate,
106
+ };
107
+}
108
+
109
+/**
110
+ * Format a date to a readable string
111
+ * @param timestamp - Unix timestamp
112
+ * @returns Formatted date string
113
+ */
114
+export function formatDate(timestamp: number): string {
115
+ return new Date(timestamp).toLocaleDateString('de-DE', {
116
+ weekday: 'short',
117
+ day: '2-digit',
118
+ month: 'short',
119
+ year: 'numeric',
120
+ });
121
+}