Files
zopu-code/repos/native/app/(drawer)/todos.tsx
-Puter cc47007fa9 Slice 1 polish: archive dormant code, merge work-os into primitives, add futures
- Archive dormant apps (daemon, desktop, native, tui) and superseded
  packages/server into repos/ for reference
- Archive 21 dead web components (chat page shell, work-os cluster, strays)
  into repos/web/; keep reused message-rendering primitives in components/chat
- Merge @code/work-os into @code/primitives; work.ts absorbed via ./work
  subpath and barrel export; update all four importers
- Add docs/futures.md tracking audio/video (blocked at Flue + provider),
  web layout cleanup, and message rendering quality
- Repoint dev:zopu script to @code/agents (the live Flue server)
- Note repos/ reference-code convention in AGENTS.md
2026-07-27 16:34:17 +05:30

150 lines
5.1 KiB
TypeScript

import { api } from "@code/backend/convex/_generated/api";
import type { Doc, Id } from "@code/backend/convex/_generated/dataModel";
import { Ionicons } from "@expo/vector-icons";
import { useMutation, useQuery } from "convex/react";
import {
Button,
Checkbox,
Chip,
Spinner,
Surface,
Input,
TextField,
useThemeColor,
} from "heroui-native";
import { useState } from "react";
import { View, Text, ScrollView, Alert } from "react-native";
import { Container } from "@/components/container";
export default function TodosScreen() {
const [newTodoText, setNewTodoText] = useState("");
const todos = useQuery(api.todos.getAll);
const createTodoMutation = useMutation(api.todos.create);
const toggleTodoMutation = useMutation(api.todos.toggle);
const deleteTodoMutation = useMutation(api.todos.deleteTodo);
const mutedColor = useThemeColor("muted");
const dangerColor = useThemeColor("danger");
const foregroundColor = useThemeColor("foreground");
const handleAddTodo = async () => {
const text = newTodoText.trim();
if (!text) return;
await createTodoMutation({ text });
setNewTodoText("");
};
const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => {
toggleTodoMutation({ id, completed: !currentCompleted });
};
const handleDeleteTodo = (id: Id<"todos">) => {
Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
style: "destructive",
onPress: () => deleteTodoMutation({ id }),
},
]);
};
const isLoading = !todos;
const completedCount = todos?.filter((t: Doc<"todos">) => t.completed).length || 0;
const totalCount = todos?.length || 0;
return (
<Container>
<ScrollView className="flex-1" contentContainerClassName="p-4">
<View className="py-4 mb-4">
<View className="flex-row items-center justify-between">
<Text className="text-2xl font-semibold text-foreground tracking-tight">Tasks</Text>
{totalCount > 0 && (
<Chip variant="secondary" color="accent" size="sm">
<Chip.Label>
{completedCount}/{totalCount}
</Chip.Label>
</Chip>
)}
</View>
</View>
<Surface variant="secondary" className="mb-4 p-3 rounded-lg">
<View className="flex-row items-center gap-2">
<View className="flex-1">
<TextField>
<Input
value={newTodoText}
onChangeText={setNewTodoText}
placeholder="Add a new task..."
onSubmitEditing={handleAddTodo}
returnKeyType="done"
/>
</TextField>
</View>
<Button
isIconOnly
variant={!newTodoText.trim() ? "secondary" : "primary"}
isDisabled={!newTodoText.trim()}
onPress={handleAddTodo}
size="sm"
>
<Ionicons
name="add"
size={20}
color={newTodoText.trim() ? foregroundColor : mutedColor}
/>
</Button>
</View>
</Surface>
{isLoading && (
<View className="items-center justify-center py-12">
<Spinner size="lg" />
<Text className="text-muted text-sm mt-3">Loading tasks...</Text>
</View>
)}
{todos && todos.length === 0 && !isLoading && (
<Surface variant="secondary" className="items-center justify-center py-10 rounded-lg">
<Ionicons name="checkbox-outline" size={40} color={mutedColor} />
<Text className="text-foreground font-medium mt-3">No tasks yet</Text>
<Text className="text-muted text-xs mt-1">Add your first task to get started</Text>
</Surface>
)}
{todos && todos.length > 0 && (
<View className="gap-2">
{todos.map((todo: Doc<"todos">) => (
<Surface key={todo._id} variant="secondary" className="p-3 rounded-lg">
<View className="flex-row items-center gap-3">
<Checkbox
isSelected={todo.completed}
onSelectedChange={() => handleToggleTodo(todo._id, todo.completed)}
/>
<View className="flex-1">
<Text
className={`text-sm ${todo.completed ? "text-muted line-through" : "text-foreground"}`}
>
{todo.text}
</Text>
</View>
<Button
isIconOnly
variant="ghost"
onPress={() => handleDeleteTodo(todo._id)}
size="sm"
>
<Ionicons name="trash-outline" size={16} color={dangerColor} />
</Button>
</View>
</Surface>
))}
</View>
)}
</ScrollView>
</Container>
);
}