feat: add list selector to task detail screen

Allow viewing and changing a task's list from the task detail page.
Uses the same chip-style selector with list icons and colors as the
new task screen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-02-21 20:55:29 -05:00
parent d31b6f45fc
commit c58a4dce2d

View file

@ -10,7 +10,13 @@ import {
Platform,
} from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { ArrowLeft, Plus, Trash2, Calendar, X, Repeat, Download } from 'lucide-react-native';
import {
ArrowLeft, Plus, Trash2, Calendar, X, Repeat, Download,
List, ShoppingCart, Briefcase, Home, Heart, Star, BookOpen,
GraduationCap, Dumbbell, Utensils, Plane, Music, Code, Wrench,
Gift, Camera, Palette, Dog, Leaf, Zap,
} from 'lucide-react-native';
import type { LucideIcon } from 'lucide-react-native';
import { useTranslation } from 'react-i18next';
import * as Haptics from 'expo-haptics';
import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
@ -29,6 +35,13 @@ import {
toggleComplete,
} from '@/src/db/repository/tasks';
import { getAllTags, getTagsForTask, setTagsForTask } from '@/src/db/repository/tags';
import { getAllLists } from '@/src/db/repository/lists';
const ICON_MAP: Record<string, LucideIcon> = {
List, ShoppingCart, Briefcase, Home, Heart, Star, BookOpen,
GraduationCap, Dumbbell, Utensils, Plane, Music, Code, Wrench,
Gift, Camera, Palette, Dog, Leaf, Zap,
};
import TagChip from '@/src/components/task/TagChip';
import { exportAndShareICS } from '@/src/services/icsExport';
@ -64,6 +77,8 @@ export default function TaskDetailScreen() {
const [newSubtask, setNewSubtask] = useState('');
const [availableTags, setAvailableTags] = useState<{ id: string; name: string; color: string }[]>([]);
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
const [lists, setLists] = useState<{ id: string; name: string; color: string | null; icon: string | null; isInbox: boolean }[]>([]);
const [selectedListId, setSelectedListId] = useState<string>('');
useEffect(() => {
if (!isValidUUID(id)) {
@ -73,6 +88,7 @@ export default function TaskDetailScreen() {
loadTask();
loadSubtasks();
getAllTags().then(setAvailableTags);
getAllLists().then(setLists);
}, [id]);
const loadTask = async () => {
@ -84,6 +100,7 @@ export default function TaskDetailScreen() {
setPriority(result.priority);
setDueDate(result.dueDate ? new Date(result.dueDate) : null);
setRecurrence(result.recurrence ?? null);
setSelectedListId(result.listId);
const taskTags = await getTagsForTask(id!);
setSelectedTagIds(taskTags.map((t) => t.id));
@ -102,6 +119,7 @@ export default function TaskDetailScreen() {
priority,
dueDate,
recurrence,
listId: selectedListId,
});
await setTagsForTask(task.id, selectedTagIds);
router.back();
@ -298,6 +316,46 @@ export default function TaskDetailScreen() {
</>
)}
{/* List selector */}
{lists.length > 1 && (
<>
<Text className={`mb-2 mt-6 text-xs uppercase tracking-wide ${isDark ? 'text-[#A0A0A0]' : 'text-[#6B6B6B]'}`} style={{ fontFamily: 'Inter_600SemiBold' }}>
{t('nav.lists')}
</Text>
<View className="flex-row flex-wrap">
{lists.map((list) => {
const isSelected = selectedListId === list.id;
const chipColor = isSelected ? colors.bleu.DEFAULT : isDark ? '#A0A0A0' : '#6B6B6B';
const IconComp = list.icon && ICON_MAP[list.icon] ? ICON_MAP[list.icon] : null;
return (
<Pressable
key={list.id}
onPress={() => setSelectedListId(list.id)}
className={`mb-2 mr-2 flex-row items-center rounded-full border px-3 py-1.5 ${
isSelected ? 'border-bleu bg-bleu/10' : isDark ? 'border-[#3A3A3A]' : 'border-[#E5E7EB]'
}`}
>
{IconComp ? (
<IconComp size={14} color={isSelected ? colors.bleu.DEFAULT : list.color || chipColor} />
) : list.color ? (
<View className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: isSelected ? colors.bleu.DEFAULT : list.color }} />
) : null}
<Text
className={`text-sm ${IconComp || list.color ? 'ml-1.5' : ''}`}
style={{
fontFamily: isSelected ? 'Inter_600SemiBold' : 'Inter_400Regular',
color: chipColor,
}}
>
{list.isInbox ? t('list.inbox') : list.name}
</Text>
</Pressable>
);
})}
</View>
</>
)}
{/* Subtasks */}
<Text className={`mb-2 mt-6 text-xs uppercase tracking-wide ${isDark ? 'text-[#A0A0A0]' : 'text-[#6B6B6B]'}`} style={{ fontFamily: 'Inter_600SemiBold' }}>
{t('task.subtasks')}