import React, { useState } from 'react'; import { View, Text, ScrollView, TouchableOpacity, TextInput, ActivityIndicator, KeyboardAvoidingView, Platform, Alert, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Plus, Pencil, Trash2, Check, X } from 'lucide-react-native'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useFonts, Inter_400Regular, Inter_600SemiBold } from '@expo-google-fonts/inter'; const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; const ALL_TYPES = ['Kurk', 'Berk', '3D'] as const; type InsoleType = (typeof ALL_TYPES)[number]; const TYPE_COLORS: Record = { Kurk: { bg: '#FEF9C3', border: '#FDE047', text: '#854D0E' }, Berk: { bg: '#DCFCE7', border: '#86EFAC', text: '#166534' }, '3D': { bg: '#EDE9FE', border: '#C4B5FD', text: '#5B21B6' }, }; function TypeToggle({ type, selected, onPress, }: { type: InsoleType; selected: boolean; onPress: () => void; }) { const c = TYPE_COLORS[type]; return ( {selected && } {type} ); } function TypeBadge({ type }: { type: InsoleType }) { const c = TYPE_COLORS[type]; return ( {type} ); } export default function TasksScreen() { const insets = useSafeAreaInsets(); const queryClient = useQueryClient(); const [fontsLoaded, fontError] = useFonts({ Inter_400Regular, Inter_600SemiBold }); const [newTaskName, setNewTaskName] = useState(''); const [newTaskTypes, setNewTaskTypes] = useState(['Kurk', 'Berk', '3D']); const [editingId, setEditingId] = useState(null); const [editingName, setEditingName] = useState(''); const [editingTypes, setEditingTypes] = useState([]); const { data: tasks = [], isLoading } = useQuery({ queryKey: ['tasks'], queryFn: async () => { const res = await fetch(`${BASE_URL}/api/tasks`); if (!res.ok) throw new Error('Failed to fetch tasks'); return res.json(); }, }); const addTaskMutation = useMutation({ mutationFn: async ({ name, insole_types }: { name: string; insole_types: string[] }) => { const res = await fetch(`${BASE_URL}/api/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, insole_types }), }); if (!res.ok) throw new Error('Failed to add task'); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks'] }); setNewTaskName(''); setNewTaskTypes(['Kurk', 'Berk', '3D']); }, }); const updateTaskMutation = useMutation({ mutationFn: async ({ id, name, insole_types, }: { id: number; name: string; insole_types: string[]; }) => { const res = await fetch(`${BASE_URL}/api/tasks/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, insole_types }), }); if (!res.ok) throw new Error('Failed to update task'); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks'] }); setEditingId(null); }, }); const deleteTaskMutation = useMutation({ mutationFn: async (id: number) => { const res = await fetch(`${BASE_URL}/api/tasks/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete task'); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks'] }); queryClient.invalidateQueries({ queryKey: ['logs'] }); }, }); const toggleNewType = (type: InsoleType) => { setNewTaskTypes((prev) => prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type] ); }; const toggleEditType = (type: InsoleType) => { setEditingTypes((prev) => prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type] ); }; const handleAddTask = () => { if (!newTaskName.trim() || newTaskTypes.length === 0) return; addTaskMutation.mutate({ name: newTaskName.trim(), insole_types: newTaskTypes }); }; const handleStartEdit = (task: any) => { setEditingId(task.id); setEditingName(task.name); setEditingTypes(Array.isArray(task.insole_types) ? task.insole_types : ['Kurk', 'Berk', '3D']); }; const handleConfirmEdit = () => { if (!editingName.trim() || editingId === null || editingTypes.length === 0) return; updateTaskMutation.mutate({ id: editingId, name: editingName.trim(), insole_types: editingTypes, }); }; const handleCancelEdit = () => { setEditingId(null); setEditingName(''); setEditingTypes([]); }; const handleDelete = (task: any) => { Alert.alert( 'Taak verwijderen', `"${task.name}" verwijderen? Alle tijdsregistraties voor deze taak worden ook verwijderd.`, [ { text: 'Annuleren', style: 'cancel' }, { text: 'Verwijderen', style: 'destructive', onPress: () => deleteTaskMutation.mutate(task.id), }, ] ); }; if (!fontsLoaded && !fontError) return null; return ( {/* Header */} Instellingen Beheer handelingen per zooltype {/* Add New Task */} Nieuwe handeling toevoegen {/* Name input */} {/* Insole type toggles */} Van toepassing op {ALL_TYPES.map((type) => ( toggleNewType(type)} /> ))} 0 ? '#2563EB' : '#E5E7EB', borderRadius: 10, paddingVertical: 12, alignItems: 'center', justifyContent: 'center', flexDirection: 'row', gap: 8, }} > {addTaskMutation.isPending ? ( ) : ( <> 0 ? 'white' : '#9CA3AF'} size={18} /> 0 ? 'white' : '#9CA3AF', fontSize: 15, fontWeight: '600', fontFamily: 'Inter_600SemiBold', }} > Stap toevoegen )} {/* Task List */} Huidige stappen ({tasks.length}) {isLoading ? ( ) : tasks.length === 0 ? ( Nog geen stappen. Voeg er een toe hierboven. ) : ( tasks.map((task: any) => { const types: InsoleType[] = Array.isArray(task.insole_types) ? task.insole_types : []; const isEditing = editingId === task.id; return ( {isEditing ? ( <> {/* Edit name */} {/* Edit insole types */} Van toepassing op {ALL_TYPES.map((type) => ( toggleEditType(type)} /> ))} {/* Confirm / Cancel */} {updateTaskMutation.isPending ? ( ) : ( <> Opslaan )} Annuleren ) : ( <> {/* Task name + actions */} {task.name} handleStartEdit(task)} style={{ backgroundColor: '#EFF6FF', borderRadius: 8, width: 36, height: 36, alignItems: 'center', justifyContent: 'center', }} > handleDelete(task)} disabled={deleteTaskMutation.isPending} style={{ backgroundColor: '#FEF2F2', borderRadius: 8, width: 36, height: 36, alignItems: 'center', justifyContent: 'center', }} > {/* Insole type badges */} {types.map((type) => ( ))} )} ); }) )} ); }