gitshark

Clone repository

git clone https://git.vilip.de/git/vilip/taskflow.git
git clone git@git.vilip.de:vilip/taskflow.git

← Commits

✨ Add Type Date in feature

89f678ea32fe74eb5670b55d5c1943c80b180ac0 · vvilip · 2025-11-06T22:14:05Z

Changes

2 files changed, +145 -1

MODIFY app/task/[id].tsx +24 -1
diff --git "a/app/task/\133id\135.tsx" "b/app/task/\133id\135.tsx"
index 2fad44e..0df1d83 100644
--- "a/app/task/\133id\135.tsx"
+++ "b/app/task/\133id\135.tsx"
@@ -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
diff --git a/utils/date-parser.ts b/utils/date-parser.ts
new file mode 100644
index 0000000..cf9f1ae
--- /dev/null
+++ b/utils/date-parser.ts
@@ -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 +}

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog