feat: update ProjectE application

This commit is contained in:
2026-07-18 19:05:52 -04:00
parent 8f55626e03
commit 4c1cc50231
68 changed files with 1586 additions and 697 deletions
+92 -78
View File
@@ -7,12 +7,14 @@ import {
TableCell,
TableHead,
TableHeader,
TableRow,
TableRow
} from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
import { Calendar, MoreHorizontal } from 'lucide-react';
import { toast } from 'sonner';
import { TaskDetailPanel } from './task-detail-panel';
interface Task {
@@ -39,12 +41,14 @@ export function TasksListView() {
async function fetchTasks() {
try {
const response = await fetch('/api/tasks?sort=-created');
if (response.ok) {
const data = await response.json();
setTasks(data.items || []);
if (!response.ok) {
throw new Error('Unable to load tasks');
}
const data = await response.json();
setTasks(data.items || []);
} catch (error) {
console.error('Failed to fetch tasks:', error);
toast.error('Unable to load tasks');
} finally {
setLoading(false);
}
@@ -53,14 +57,19 @@ export function TasksListView() {
async function toggleTaskComplete(task: Task) {
const newStatus = task.status === 'done' ? 'todo' : 'done';
try {
await fetch(`/api/tasks/${task.id}`, {
const response = await fetch(`/api/tasks/${task.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus }),
body: JSON.stringify({ status: newStatus })
});
fetchTasks();
if (!response.ok) throw new Error('Unable to update task');
await fetchTasks();
toast.success(
`Marked ${task.title} as ${newStatus === 'done' ? 'complete' : 'incomplete'}`
);
} catch (error) {
console.error('Failed to toggle task:', error);
toast.error(`Unable to update ${task.title}`);
}
}
@@ -70,77 +79,82 @@ export function TasksListView() {
return (
<>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]"></TableHead>
<TableHead>Task</TableHead>
<TableHead>Priority</TableHead>
<TableHead>Domain</TableHead>
<TableHead>Due Date</TableHead>
<TableHead className="w-[50px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tasks.map((task) => (
<TableRow key={task.id}>
<TableCell>
<Checkbox
checked={task.status === 'done'}
onCheckedChange={() => toggleTaskComplete(task)}
aria-label={`Mark "${task.title}" as ${task.status === 'done' ? 'incomplete' : 'complete'}`}
/>
</TableCell>
<TableCell>
<button
onClick={() => setSelectedTask(task)}
className={`text-left font-medium hover:underline ${
task.status === 'done'
? 'line-through text-muted-foreground'
: ''
}`}
>
{task.title}
</button>
</TableCell>
<TableCell>
<Badge
variant={
task.priority === 'urgent'
? 'destructive'
: task.priority === 'high'
? 'default'
: 'secondary'
}
>
{task.priority}
</Badge>
</TableCell>
<TableCell>
<Badge variant="outline">{task.domain}</Badge>
</TableCell>
<TableCell>
{task.due_date && (
<span className="flex items-center gap-1 text-sm text-muted-foreground">
<Calendar className="h-3 w-3" />
{new Date(task.due_date).toLocaleDateString()}
</span>
)}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="icon"
className="h-11 w-11"
aria-label={`More options for ${task.title}`}
>
<MoreHorizontal className="h-4 w-4" aria-hidden="true" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<ScrollArea className="w-full">
<div className="min-w-[700px]">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]"></TableHead>
<TableHead>Task</TableHead>
<TableHead>Priority</TableHead>
<TableHead>Domain</TableHead>
<TableHead>Due Date</TableHead>
<TableHead className="w-[50px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tasks.map((task) => (
<TableRow key={task.id}>
<TableCell>
<Checkbox
checked={task.status === 'done'}
onCheckedChange={() => toggleTaskComplete(task)}
aria-label={`Mark "${task.title}" as ${task.status === 'done' ? 'incomplete' : 'complete'}`}
/>
</TableCell>
<TableCell>
<button
onClick={() => setSelectedTask(task)}
className={`text-left font-medium hover:underline ${
task.status === 'done'
? 'line-through text-muted-foreground'
: ''
}`}
>
{task.title}
</button>
</TableCell>
<TableCell>
<Badge
variant={
task.priority === 'urgent'
? 'destructive'
: task.priority === 'high'
? 'default'
: 'secondary'
}
>
{task.priority}
</Badge>
</TableCell>
<TableCell>
<Badge variant="outline">{task.domain}</Badge>
</TableCell>
<TableCell>
{task.due_date && (
<span className="flex items-center gap-1 text-sm text-muted-foreground">
<Calendar className="h-3 w-3" />
{new Date(task.due_date).toLocaleDateString()}
</span>
)}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="icon"
className="h-11 w-11"
aria-label={`More options for ${task.title}`}
>
<MoreHorizontal className="h-4 w-4" aria-hidden="true" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
{selectedTask && (
<TaskDetailPanel