gitshark

Clone repository

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

← Commits

๐Ÿ’„ UI improvements

dd9469adbf4e97e12b294f2bcef17a04d0beccaa ยท vvilip ยท 2025-11-11T12:53:30Z

Changes

6 files changed, +628 -21

MODIFY app/(tabs)/projects.tsx +2 -2
diff --git "a/app/\050tabs\051/projects.tsx" "b/app/\050tabs\051/projects.tsx"
index b164c49..0723bac 100644
--- "a/app/\050tabs\051/projects.tsx"
+++ "b/app/\050tabs\051/projects.tsx"
@@ -37,11 +37,11 @@
37 37 };
38 38
39 39 const handleProjectPress = (project: Project) => {
40 - router.push(`/project/${project.id}`);
40 + router.push({ pathname: '/project-modal', params: { projectId: project.id } });
41 41 };
42 42
43 43 const handleAddProject = () => {
44 - router.push('/modal');
44 + router.push({ pathname: '/project-modal', params: { projectId: 'new' } });
45 45 };
46 46
47 47 const renderProject = ({ item }: { item: Project }) => (
MODIFY app/_layout.tsx +8 -0
diff --git a/app/_layout.tsx b/app/_layout.tsx
index c2f2ae3..2df38ed 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -29,6 +29,14 @@
29 29 headerShown: false
30 30 }}
31 31 />
32 + <Stack.Screen
33 + name="project-modal"
34 + options={{
35 + presentation: 'transparentModal',
36 + animation: 'fade',
37 + headerShown: false
38 + }}
39 + />
32 40 <Stack.Screen name="webdav-setup" options={{ headerShown: false }} />
33 41 </Stack>
34 42 <StatusBar style="auto" />
MODIFY app/modal.tsx +23 -13
diff --git a/app/modal.tsx b/app/modal.tsx
index 0f357c9..5e7cc3d 100644
--- a/app/modal.tsx
+++ b/app/modal.tsx
@@ -19,9 +19,15 @@
19 19 const translateY = useSharedValue(0);
20 20 const colorScheme = useColorScheme() ?? 'light';
21 21 const colors = Colors[colorScheme];
22 - const { taskId } = useLocalSearchParams<{ taskId?: string }>();
22 + const { taskId, projectId } = useLocalSearchParams<{ taskId?: string; projectId?: string }>();
23 + const taskFormRef = React.useRef<any>(null);
23 24
24 - const handleClose = () => {
25 + const handleClose = async () => {
26 + // Try to save before closing
27 + if (taskFormRef.current?.handleSave) {
28 + const saved = await taskFormRef.current.handleSave();
29 + }
30 +
25 31 if (router.canDismiss()) {
26 32 router.dismiss();
27 33 } else if (router.canGoBack()) {
@@ -38,17 +44,20 @@
38 44 runOnJS(dismissKeyboard)();
39 45 })
40 46 .onUpdate((event) => {
41 - translateY.value = Math.max(0, event.translationY);
47 + if (event.translationY > 0) {
48 + translateY.value = event.translationY;
49 + }
42 50 })
43 - .onEnd(() => {
44 - if (translateY.value > SCREEN_HEIGHT * 0.3) {
51 + .onEnd((event) => {
52 + if (translateY.value > SCREEN_HEIGHT * 0.3 || event.velocityY > 500) {
45 53 translateY.value = withTiming(SCREEN_HEIGHT, {}, () => {
46 54 runOnJS(handleClose)();
47 55 });
48 56 } else {
49 57 translateY.value = withTiming(0);
50 58 }
51 - });
59 + })
60 + .simultaneousWithExternalGesture();
52 61
53 62 const animatedStyle = useAnimatedStyle(() => {
54 63 return {
@@ -63,16 +72,16 @@
63 72 onPress={handleClose}
64 73 activeOpacity={1}
65 74 />
66 - <Animated.View style={[styles.modal, animatedStyle]}>
67 - <GestureDetector gesture={pan}>
75 + <GestureDetector gesture={pan}>
76 + <Animated.View style={[styles.modal, animatedStyle]}>
68 77 <ThemedView style={styles.handleContainer}>
69 78 <ThemedView style={styles.handle} />
70 79 </ThemedView>
71 - </GestureDetector>
72 - <ThemedView style={[styles.formContainer, { backgroundColor: colors.background }]}>
73 - <TaskForm id={taskId ?? 'new'} onSave={handleClose} />
74 - </ThemedView>
75 - </Animated.View>
80 + <ThemedView style={[styles.formContainer, { backgroundColor: colors.background }]}>
81 + <TaskForm ref={taskFormRef} id={taskId ?? 'new'} projectId={projectId} />
82 + </ThemedView>
83 + </Animated.View>
84 + </GestureDetector>
76 85 </ThemedView>
77 86 );
78 87 }
@@ -93,6 +102,7 @@
93 102 borderTopRightRadius: 30,
94 103 paddingTop: 16,
95 104 backgroundColor: 'white',
105 + overflow: 'hidden',
96 106 },
97 107 handle: {
98 108 width: 40,
ADD app/project-modal.tsx +123 -0
diff --git a/app/project-modal.tsx b/app/project-modal.tsx
new file mode 100644
index 0000000..b2396dc
--- /dev/null
+++ b/app/project-modal.tsx
@@ -0,0 +1,123 @@
1 +import React from 'react';
2 +import { StyleSheet, TouchableOpacity, Platform, Dimensions, Keyboard } from 'react-native';
3 +import { ThemedView } from '@/components/themed-view';
4 +import { router, useLocalSearchParams } from 'expo-router';
5 +import { Gesture, GestureDetector } from 'react-native-gesture-handler';
6 +import Animated, {
7 + useSharedValue,
8 + useAnimatedStyle,
9 + withTiming,
10 + runOnJS,
11 +} from 'react-native-reanimated';
12 +import { ProjectForm } from '@/components/project-form';
13 +import { useColorScheme } from '@/hooks/use-color-scheme';
14 +import { Colors } from '@/constants/theme';
15 +
16 +const { height: SCREEN_HEIGHT } = Dimensions.get('window');
17 +
18 +export default function ProjectModalScreen() {
19 + const translateY = useSharedValue(0);
20 + const colorScheme = useColorScheme() ?? 'light';
21 + const colors = Colors[colorScheme];
22 + const { projectId } = useLocalSearchParams<{ projectId?: string }>();
23 + const projectFormRef = React.useRef<any>(null);
24 +
25 + const handleClose = async () => {
26 + // Try to save before closing
27 + if (projectFormRef.current?.handleSave) {
28 + await projectFormRef.current.handleSave();
29 + }
30 +
31 + if (router.canDismiss()) {
32 + router.dismiss();
33 + } else if (router.canGoBack()) {
34 + router.back();
35 + }
36 + };
37 +
38 + const dismissKeyboard = () => {
39 + Keyboard.dismiss();
40 + };
41 +
42 + const pan = Gesture.Pan()
43 + .onStart(() => {
44 + runOnJS(dismissKeyboard)();
45 + })
46 + .onUpdate((event) => {
47 + if (event.translationY > 0) {
48 + translateY.value = event.translationY;
49 + }
50 + })
51 + .onEnd((event) => {
52 + if (translateY.value > SCREEN_HEIGHT * 0.3 || event.velocityY > 500) {
53 + translateY.value = withTiming(SCREEN_HEIGHT, {}, () => {
54 + runOnJS(handleClose)();
55 + });
56 + } else {
57 + translateY.value = withTiming(0);
58 + }
59 + })
60 + .simultaneousWithExternalGesture();
61 +
62 + const animatedStyle = useAnimatedStyle(() => {
63 + return {
64 + transform: [{ translateY: translateY.value }],
65 + };
66 + });
67 +
68 + return (
69 + <ThemedView style={styles.container}>
70 + <TouchableOpacity
71 + style={styles.overlay}
72 + onPress={handleClose}
73 + activeOpacity={1}
74 + />
75 + <GestureDetector gesture={pan}>
76 + <Animated.View style={[styles.modal, animatedStyle]}>
77 + <ThemedView style={styles.handleContainer}>
78 + <ThemedView style={styles.handle} />
79 + </ThemedView>
80 + <ThemedView style={[styles.formContainer, { backgroundColor: colors.background }]}>
81 + <ProjectForm ref={projectFormRef} id={projectId ?? 'new'} onSave={handleClose} />
82 + </ThemedView>
83 + </Animated.View>
84 + </GestureDetector>
85 + </ThemedView>
86 + );
87 +}
88 +
89 +const styles = StyleSheet.create({
90 + container: {
91 + flex: 1,
92 + justifyContent: 'flex-end',
93 + backgroundColor: 'transparent',
94 + },
95 + overlay: {
96 + ...StyleSheet.absoluteFillObject,
97 + backgroundColor: 'rgba(0,0,0,0.4)',
98 + },
99 + modal: {
100 + height: '90%',
101 + borderTopLeftRadius: 30,
102 + borderTopRightRadius: 30,
103 + paddingTop: 16,
104 + backgroundColor: 'white',
105 + overflow: 'hidden',
106 + },
107 + handle: {
108 + width: 40,
109 + height: 5,
110 + borderRadius: 2.5,
111 + backgroundColor: 'rgba(0,0,0,0.2)',
112 + alignSelf: 'center',
113 + },
114 + handleContainer: {
115 + paddingTop: 8,
116 + paddingBottom: 16,
117 + alignItems: 'center',
118 + },
119 + formContainer: {
120 + flex: 1,
121 + overflow: 'hidden',
122 + },
123 +});
ADD components/project-form.tsx +466 -0
diff --git a/components/project-form.tsx b/components/project-form.tsx
new file mode 100644
index 0000000..1075d58
--- /dev/null
+++ b/components/project-form.tsx
@@ -0,0 +1,466 @@
1 +import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
2 +import { StyleSheet, Alert, ScrollView, TouchableOpacity, TextInput, View } from 'react-native';
3 +import { Ionicons } from '@expo/vector-icons';
4 +import { ThemedView } from '@/components/themed-view';
5 +import { ThemedText } from '@/components/themed-text';
6 +import { TaskList } from '@/components/task-list';
7 +import { Project, Task } from '@/types/gtd';
8 +import { projectService, taskService } from '@/services';
9 +import { Colors } from '@/constants/theme';
10 +import { useColorScheme } from '@/hooks/use-color-scheme';
11 +import { router, useFocusEffect } from 'expo-router';
12 +
13 +interface ProjectFormProps {
14 + id?: string;
15 + onSave?: (projectId: string) => void;
16 + onClose?: () => void;
17 +}
18 +
19 +export const ProjectForm = forwardRef(({ id, onSave, onClose }: ProjectFormProps, ref) => {
20 + const isNew = id === 'new';
21 + const colorScheme = useColorScheme() ?? 'light';
22 + const colors = Colors[colorScheme];
23 +
24 + const [project, setProject] = useState<Partial<Project>>({
25 + name: '',
26 + description: '',
27 + goal: '',
28 + color: '#0a7ea4',
29 + archived: false,
30 + });
31 +
32 + const [tasks, setTasks] = useState<Task[]>([]);
33 + const [loading, setLoading] = useState(!isNew);
34 + const [isEditing, setIsEditing] = useState(isNew);
35 +
36 + useImperativeHandle(ref, () => ({
37 + handleSave,
38 + }));
39 +
40 + useEffect(() => {
41 + loadData();
42 + }, [id]);
43 +
44 + useFocusEffect(
45 + React.useCallback(() => {
46 + if (!isNew && id) {
47 + loadData();
48 + }
49 + }, [id, isNew])
50 + );
51 +
52 + const loadData = async () => {
53 + try {
54 + if (!isNew && id) {
55 + const foundProject = await projectService.getProjectById(id);
56 + if (foundProject) {
57 + setProject(foundProject);
58 + const projectTasks = await taskService.getTasksByProject(id);
59 + setTasks(projectTasks);
60 + } else {
61 + Alert.alert('Error', 'Project not found');
62 + onClose?.();
63 + }
64 + }
65 + } catch (error) {
66 + Alert.alert('Error', 'Failed to load project');
67 + } finally {
68 + setLoading(false);
69 + }
70 + };
71 +
72 + const handleSave = async () => {
73 + if (!project.name?.trim()) {
74 + return false;
75 + }
76 +
77 + try {
78 + let savedProject: Project | undefined;
79 + if (isNew) {
80 + savedProject = await projectService.createProject(project);
81 + if (savedProject) {
82 + onSave?.(savedProject.id);
83 + }
84 + } else if (id) {
85 + savedProject = await projectService.updateProject(id, project);
86 + setIsEditing(false);
87 + await loadData();
88 + }
89 +
90 + return !!savedProject;
91 + } catch (error) {
92 + Alert.alert('Error', 'Failed to save project');
93 + return false;
94 + }
95 + };
96 +
97 + const handleDelete = async () => {
98 + Alert.alert(
99 + 'Delete Project',
100 + 'Are you sure you want to delete this project? Tasks will be kept but unassigned.',
101 + [
102 + { text: 'Cancel', style: 'cancel' },
103 + {
104 + text: 'Delete',
105 + style: 'destructive',
106 + onPress: async () => {
107 + try {
108 + if (id && id !== 'new') {
109 + await projectService.deleteProject(id);
110 + onClose?.();
111 + }
112 + } catch (error) {
113 + Alert.alert('Error', 'Failed to delete project');
114 + }
115 + },
116 + },
117 + ]
118 + );
119 + };
120 +
121 + const handleArchive = async () => {
122 + if (!id || id === 'new') return;
123 +
124 + try {
125 + if (project.archived) {
126 + await projectService.unarchiveProject(id);
127 + } else {
128 + await projectService.archiveProject(id);
129 + }
130 + const updatedProject = await projectService.getProjectById(id);
131 + if (updatedProject) {
132 + setProject(updatedProject);
133 + }
134 + } catch (error) {
135 + Alert.alert('Error', 'Failed to archive project');
136 + }
137 + };
138 +
139 + const handleToggleComplete = async (taskId: string) => {
140 + try {
141 + const task = tasks.find(t => t.id === taskId);
142 + if (!task) return;
143 +
144 + if (task.completed) {
145 + await taskService.uncompleteTask(taskId);
146 + } else {
147 + await taskService.completeTask(taskId);
148 + }
149 +
150 + await loadData();
151 + } catch (error) {
152 + Alert.alert('Error', 'Failed to update task');
153 + }
154 + };
155 +
156 + const handleTaskPress = (task: Task) => {
157 + router.push({ pathname: '/modal', params: { taskId: task.id } });
158 + };
159 +
160 + const handleAddTask = () => {
161 + router.push({
162 + pathname: '/modal',
163 + params: { taskId: 'new', projectId: id },
164 + });
165 + };
166 +
167 + const colorOptions = ['#0a7ea4', '#ef4444', '#f59e0b', '#10b981', '#8b5cf6', '#ec4899', '#64748b'];
168 +
169 + if (!isNew && !isEditing) {
170 + return (
171 + <ScrollView
172 + style={styles.scrollView}
173 + contentContainerStyle={styles.scrollViewContent}
174 + showsVerticalScrollIndicator={true}
175 + scrollEnabled={true}
176 + nestedScrollEnabled={true}
177 + keyboardShouldPersistTaps="handled"
178 + >
179 + <ThemedView style={styles.viewHeader}>
180 + <View style={styles.projectTitleContainer}>
181 + {project.color && (
182 + <View style={[styles.colorIndicator, { backgroundColor: project.color }]} />
183 + )}
184 + <ThemedText type="title">{project.name}</ThemedText>
185 + </View>
186 + <TouchableOpacity onPress={() => setIsEditing(true)} style={styles.iconButton}>
187 + <Ionicons name="create-outline" size={24} color={colors.tint} />
188 + </TouchableOpacity>
189 + </ThemedView>
190 +
191 + {project.archived && (
192 + <ThemedView style={styles.archivedBadgeContainer}>
193 + <View style={[styles.archivedBadge, { backgroundColor: colors.secondaryBackground }]}>
194 + <ThemedText style={styles.archivedText}>Archived</ThemedText>
195 + </View>
196 + </ThemedView>
197 + )}
198 +
199 + {project.description && (
200 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
201 + <ThemedText style={styles.infoLabel}>Description</ThemedText>
202 + <ThemedText style={styles.infoText}>{project.description}</ThemedText>
203 + </ThemedView>
204 + )}
205 +
206 + {project.goal && (
207 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
208 + <ThemedText style={styles.infoLabel}>Goal</ThemedText>
209 + <ThemedText style={styles.infoText}>{project.goal}</ThemedText>
210 + </ThemedView>
211 + )}
212 +
213 + <ThemedView style={styles.tasksSection}>
214 + <View style={styles.tasksSectionHeader}>
215 + <ThemedText type="subtitle">Tasks</ThemedText>
216 + <TouchableOpacity onPress={handleAddTask} style={styles.iconButton}>
217 + <Ionicons name="add-circle-outline" size={24} color={colors.tint} />
218 + </TouchableOpacity>
219 + </View>
220 +
221 + <TaskList
222 + tasks={tasks}
223 + onTaskPress={handleTaskPress}
224 + onToggleComplete={handleToggleComplete}
225 + emptyMessage="No tasks in this project yet"
226 + scrollable={false}
227 + />
228 + </ThemedView>
229 + </ScrollView>
230 + );
231 + }
232 +
233 + return (
234 + <ScrollView
235 + style={styles.scrollView}
236 + contentContainerStyle={styles.scrollViewContent}
237 + showsVerticalScrollIndicator={true}
238 + scrollEnabled={true}
239 + nestedScrollEnabled={true}
240 + keyboardShouldPersistTaps="handled"
241 + >
242 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
243 + <ThemedText style={styles.label}>Project Name *</ThemedText>
244 + <TextInput
245 + style={[styles.input, {
246 + backgroundColor: colors.inputBackground,
247 + borderColor: colors.inputBorder,
248 + color: colors.text
249 + }]}
250 + value={project.name}
251 + onChangeText={(text) => setProject({ ...project, name: text })}
252 + placeholder="Enter project name"
253 + placeholderTextColor={colors.placeholder}
254 + autoFocus={isNew}
255 + />
256 + </ThemedView>
257 +
258 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
259 + <ThemedText style={styles.label}>Description</ThemedText>
260 + <TextInput
261 + style={[styles.input, styles.textArea, {
262 + backgroundColor: colors.inputBackground,
263 + borderColor: colors.inputBorder,
264 + color: colors.text
265 + }]}
266 + value={project.description}
267 + onChangeText={(text) => setProject({ ...project, description: text })}
268 + placeholder="Add description"
269 + placeholderTextColor={colors.placeholder}
270 + multiline
271 + numberOfLines={3}
272 + textAlignVertical="top"
273 + />
274 + </ThemedView>
275 +
276 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
277 + <ThemedText style={styles.label}>Goal</ThemedText>
278 + <TextInput
279 + style={[styles.input, styles.textArea, {
280 + backgroundColor: colors.inputBackground,
281 + borderColor: colors.inputBorder,
282 + color: colors.text
283 + }]}
284 + value={project.goal}
285 + onChangeText={(text) => setProject({ ...project, goal: text })}
286 + placeholder="What's the desired outcome?"
287 + placeholderTextColor={colors.placeholder}
288 + multiline
289 + numberOfLines={3}
290 + textAlignVertical="top"
291 + />
292 + </ThemedView>
293 +
294 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
295 + <ThemedText style={styles.label}>Color</ThemedText>
296 + <View style={styles.colorContainer}>
297 + {colorOptions.map((color) => (
298 + <TouchableOpacity
299 + key={color}
300 + style={[
301 + styles.colorButton,
302 + { backgroundColor: color },
303 + project.color === color && styles.colorButtonActive,
304 + ]}
305 + onPress={() => setProject({ ...project, color })}
306 + />
307 + ))}
308 + </View>
309 + </ThemedView>
310 +
311 + {!isNew && (
312 + <>
313 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
314 + <TouchableOpacity
315 + style={[styles.archiveButton, { backgroundColor: colors.secondaryBackground }]}
316 + onPress={handleArchive}
317 + >
318 + <ThemedText style={styles.archiveButtonText}>
319 + {project.archived ? 'Unarchive Project' : 'Archive Project'}
320 + </ThemedText>
321 + </TouchableOpacity>
322 + </ThemedView>
323 +
324 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
325 + <TouchableOpacity
326 + style={[styles.deleteButton, { backgroundColor: colors.dangerBackground }]}
327 + onPress={handleDelete}
328 + >
329 + <ThemedText style={[styles.deleteButtonText, { color: colors.danger }]}>
330 + Delete Project
331 + </ThemedText>
332 + </TouchableOpacity>
333 + </ThemedView>
334 + </>
335 + )}
336 + </ScrollView>
337 + );
338 +});
339 +
340 +const styles = StyleSheet.create({
341 + scrollView: {
342 + flex: 1,
343 + },
344 + scrollViewContent: {
345 + paddingBottom: 120,
346 + },
347 + section: {
348 + paddingHorizontal: 16,
349 + paddingVertical: 16,
350 + borderBottomWidth: StyleSheet.hairlineWidth,
351 + },
352 + label: {
353 + fontSize: 14,
354 + fontWeight: '600',
355 + marginBottom: 8,
356 + opacity: 0.7,
357 + },
358 + input: {
359 + fontSize: 16,
360 + paddingVertical: 8,
361 + paddingHorizontal: 12,
362 + borderWidth: 1,
363 + borderRadius: 8,
364 + minHeight: 44,
365 + },
366 + textArea: {
367 + minHeight: 80,
368 + paddingTop: 12,
369 + },
370 + colorContainer: {
371 + flexDirection: 'row',
372 + gap: 12,
373 + flexWrap: 'wrap',
374 + },
375 + colorButton: {
376 + width: 40,
377 + height: 40,
378 + borderRadius: 20,
379 + borderWidth: 3,
380 + borderColor: 'transparent',
381 + },
382 + colorButtonActive: {
383 + borderColor: '#fff',
384 + shadowColor: '#000',
385 + shadowOffset: { width: 0, height: 2 },
386 + shadowOpacity: 0.2,
387 + shadowRadius: 4,
388 + elevation: 4,
389 + },
390 + archiveButton: {
391 + paddingVertical: 16,
392 + borderRadius: 8,
393 + alignItems: 'center',
394 + },
395 + archiveButtonText: {
396 + fontSize: 16,
397 + fontWeight: '600',
398 + },
399 + deleteButton: {
400 + paddingVertical: 16,
401 + borderRadius: 8,
402 + alignItems: 'center',
403 + },
404 + deleteButtonText: {
405 + fontSize: 16,
406 + fontWeight: '600',
407 + },
408 + viewHeader: {
409 + paddingHorizontal: 16,
410 + paddingTop: 16,
411 + paddingBottom: 16,
412 + flexDirection: 'row',
413 + justifyContent: 'space-between',
414 + alignItems: 'center',
415 + },
416 + projectTitleContainer: {
417 + flexDirection: 'row',
418 + alignItems: 'center',
419 + gap: 12,
420 + flex: 1,
421 + },
422 + colorIndicator: {
423 + width: 6,
424 + height: 32,
425 + borderRadius: 3,
426 + },
427 + iconButton: {
428 + padding: 4,
429 + },
430 + archivedBadgeContainer: {
431 + paddingHorizontal: 16,
432 + paddingBottom: 12,
433 + },
434 + archivedBadge: {
435 + paddingHorizontal: 12,
436 + paddingVertical: 4,
437 + borderRadius: 12,
438 + alignSelf: 'flex-start',
439 + },
440 + archivedText: {
441 + fontSize: 12,
442 + fontWeight: '600',
443 + opacity: 0.6,
444 + },
445 + infoLabel: {
446 + fontSize: 12,
447 + fontWeight: '600',
448 + opacity: 0.5,
449 + marginBottom: 4,
450 + textTransform: 'uppercase',
451 + },
452 + infoText: {
453 + fontSize: 16,
454 + lineHeight: 22,
455 + },
456 + tasksSection: {
457 + paddingTop: 8,
458 + },
459 + tasksSectionHeader: {
460 + flexDirection: 'row',
461 + justifyContent: 'space-between',
462 + alignItems: 'center',
463 + paddingHorizontal: 16,
464 + paddingBottom: 8,
465 + },
466 +});
MODIFY components/task-form.tsx +6 -6
diff --git a/components/task-form.tsx b/components/task-form.tsx
index 45513e8..be1bb9b 100644
--- a/components/task-form.tsx
+++ b/components/task-form.tsx
@@ -11,11 +11,12 @@
11 11
12 12 interface TaskFormProps {
13 13 id?: string;
14 + projectId?: string;
14 15 onSave?: (taskId: string) => void;
15 16 onClose?: () => void;
16 17 }
17 18
18 -export const TaskForm = forwardRef(({ id, onSave, onClose }: TaskFormProps, ref) => {
19 +export const TaskForm = forwardRef(({ id, projectId, onSave, onClose }: TaskFormProps, ref) => {
19 20 const isNew = id === 'new';
20 21 const colorScheme = useColorScheme() ?? 'light';
21 22 const colors = Colors[colorScheme];
@@ -26,7 +27,7 @@
26 27 status: undefined,
27 28 priority: undefined,
28 29 dueDate: undefined,
29 - projectId: undefined,
30 + projectId: projectId,
30 31 tagIds: [],
31 32 completed: false,
32 33 });
@@ -73,7 +74,7 @@
73 74
74 75 const handleSave = async () => {
75 76 if (!task.title?.trim()) {
76 - return;
77 + return false;
77 78 }
78 79
79 80 try {
@@ -84,11 +85,10 @@
84 85 savedTask = await taskService.updateTask(id, task);
85 86 }
86 87
87 - if (savedTask) {
88 - onSave?.(savedTask.id);
89 - }
88 + return !!savedTask;
90 89 } catch (error) {
91 90 Alert.alert('Error', 'Failed to save task');
91 + return false;
92 92 }
93 93 };
94 94

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog