diff --git a/app/task/[id].tsx b/app/task/[id].tsx index bafe8a2..4c10db5 100644 --- a/app/task/[id].tsx +++ b/app/task/[id].tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { View, Text, @@ -8,6 +8,7 @@ import { useColorScheme, Alert, Platform, + KeyboardAvoidingView, } from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; import { @@ -79,6 +80,8 @@ export default function TaskDetailScreen() { const [selectedTagIds, setSelectedTagIds] = useState([]); const [lists, setLists] = useState<{ id: string; name: string; color: string | null; icon: string | null; isInbox: boolean }[]>([]); const [selectedListId, setSelectedListId] = useState(''); + const [saving, setSaving] = useState(false); + const scrollRef = useRef(null); useEffect(() => { if (!isValidUUID(id)) { @@ -112,17 +115,23 @@ export default function TaskDetailScreen() { }; const handleSave = async () => { + if (saving) return; if (!task || !title.trim()) return; - await updateTask(task.id, { - title: title.trim(), - notes: notes.trim() || undefined, - priority, - dueDate, - recurrence, - listId: selectedListId, - }); - await setTagsForTask(task.id, selectedTagIds); - router.back(); + setSaving(true); + try { + await updateTask(task.id, { + title: title.trim(), + notes: notes.trim() || undefined, + priority, + dueDate, + recurrence, + listId: selectedListId, + }); + await setTagsForTask(task.id, selectedTagIds); + router.back(); + } catch { + setSaving(false); + } }; const handleDelete = () => { @@ -196,13 +205,14 @@ export default function TaskDetailScreen() { - + {t('common.save')} - + + {/* Title */} setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 300)} placeholder={t('task.addSubtask')} placeholderTextColor={isDark ? '#A0A0A0' : '#6B6B6B'} className={`ml-2 flex-1 text-base ${isDark ? 'text-[#F5F5F5]' : 'text-[#1A1A1A]'}`} @@ -398,8 +409,9 @@ export default function TaskDetailScreen() { /> - + + ); } diff --git a/app/task/new.tsx b/app/task/new.tsx index 55b025d..618bd18 100644 --- a/app/task/new.tsx +++ b/app/task/new.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { View, Text, @@ -7,10 +7,11 @@ import { ScrollView, useColorScheme, Platform, + KeyboardAvoidingView, } from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; import { - X, Calendar, Repeat, + X, Calendar, Repeat, Plus, List, ShoppingCart, Briefcase, Home, Heart, Star, BookOpen, GraduationCap, Dumbbell, Utensils, Plane, Music, Code, Wrench, Gift, Camera, Palette, Dog, Leaf, Zap, @@ -55,6 +56,10 @@ export default function NewTaskScreen() { const [recurrence, setRecurrence] = useState(null); const [availableTags, setAvailableTags] = useState<{ id: string; name: string; color: string }[]>([]); const [selectedTagIds, setSelectedTagIds] = useState([]); + const [saving, setSaving] = useState(false); + const [pendingSubtasks, setPendingSubtasks] = useState([]); + const [newSubtask, setNewSubtask] = useState(''); + const scrollRef = useRef(null); useEffect(() => { getAllLists().then(setLists); @@ -62,7 +67,9 @@ export default function NewTaskScreen() { }, []); const handleSave = async () => { + if (saving) return; if (!title.trim()) return; + setSaving(true); try { const taskId = await createTask({ title: title.trim(), @@ -75,9 +82,13 @@ export default function NewTaskScreen() { if (selectedTagIds.length > 0) { await setTagsForTask(taskId, selectedTagIds); } + for (const sub of pendingSubtasks) { + await createTask({ title: sub, listId: selectedListId, parentId: taskId }); + } router.back(); } catch { // FK constraint or other DB error — fallback to inbox + setSaving(false); setSelectedListId(getInboxId()); } }; @@ -87,6 +98,16 @@ export default function NewTaskScreen() { if (date) setDueDate(date); }; + const handleAddPendingSubtask = () => { + if (!newSubtask.trim()) return; + setPendingSubtasks((prev) => [...prev, newSubtask.trim()]); + setNewSubtask(''); + }; + + const handleRemovePendingSubtask = (index: number) => { + setPendingSubtasks((prev) => prev.filter((_, i) => i !== index)); + }; + const toggleTag = (tagId: string) => { setSelectedTagIds((prev) => prev.includes(tagId) ? prev.filter((id) => id !== tagId) : [...prev, tagId] @@ -110,14 +131,15 @@ export default function NewTaskScreen() { > {t('task.newTask')} - + {t('common.save')} - + + {/* Title */} )} - + {/* Subtasks */} + + {t('task.subtasks')} + + {pendingSubtasks.map((sub, index) => ( + + + + {sub} + + handleRemovePendingSubtask(index)} className="p-1"> + + + + ))} + + {/* Add subtask */} + + + setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 300)} + placeholder={t('task.addSubtask')} + placeholderTextColor={isDark ? '#A0A0A0' : '#6B6B6B'} + className={`ml-2 flex-1 text-base ${isDark ? 'text-[#F5F5F5]' : 'text-[#1A1A1A]'}`} + style={{ fontFamily: 'Inter_400Regular' }} + /> + + + + ); }