Skip to content

Commit 953e1be

Browse files
committed
fix: implement task update and deletion functionality with API integration
1 parent a090315 commit 953e1be

File tree

2 files changed

+257
-94
lines changed

2 files changed

+257
-94
lines changed

frontend_vapi/components/TaskList.tsx

Lines changed: 199 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { usePrivy } from "@privy-io/react-auth";
2-
import { Calendar, Clock, Plus } from "lucide-react";
2+
import { Calendar, Clock, Plus, RefreshCw, Trash2 } from "lucide-react";
33
import { useRouter } from "next/router";
44
import { useCallback, useEffect, useMemo, useState } from "react";
55
import { VAPIClient } from "../lib/api-client";
6-
import { ActionResponse, getActions } from "../lib/oto-api";
6+
import { ActionResponse, getActions, updateAction } from "../lib/oto-api";
77
import { LoadingSpinner } from "./LoadingSpinner";
88
import { useToast } from "./Toast";
99
import { Button } from "./ui/button";
@@ -186,21 +186,38 @@ export function TaskList({ className }: TaskListProps) {
186186
const task = tasks.find((t) => t.id === taskId);
187187
if (!task) return;
188188

189-
const updatedTask = { ...task, completed: !task.completed };
190-
191-
// TODO: Implement API call to update task status
192-
// For now, only update local state since we don't have an update endpoint yet
193-
// In the future, you would call something like:
194-
// await updateActionStatus(taskId, updatedTask.completed ? "completed" : "created", userId, apiKey, apiEndpoint);
195-
196-
setTasks((prevTasks) => prevTasks.map((t) => (t.id === taskId ? updatedTask : t)));
189+
if (!apiEndpoint || !apiKey || !userId) {
190+
showToast({
191+
type: "error",
192+
title: "Cannot update task - API not configured",
193+
});
194+
return;
195+
}
197196

198-
showToast({
199-
type: updatedTask.completed ? "success" : "success",
200-
title: updatedTask.completed
201-
? "Task marked as completed (local only)"
202-
: "Task marked as incomplete (local only)",
203-
});
197+
const newStatus = task.completed ? "created" : "completed";
198+
199+
console.log("🔄 Updating task status...", { taskId, newStatus });
200+
201+
// Call the API to update the task status
202+
const result = await updateAction(taskId, newStatus, userId, apiKey, apiEndpoint);
203+
204+
if (result.success) {
205+
// Update local state only if API call was successful
206+
const updatedTask = { ...task, completed: !task.completed };
207+
setTasks((prevTasks) => prevTasks.map((t) => (t.id === taskId ? updatedTask : t)));
208+
209+
showToast({
210+
type: "success",
211+
title: updatedTask.completed
212+
? "Task marked as completed"
213+
: "Task marked as incomplete",
214+
});
215+
} else {
216+
showToast({
217+
type: "error",
218+
title: "Failed to update task status",
219+
});
220+
}
204221
} catch (err) {
205222
console.error("Failed to update task:", err);
206223
showToast({
@@ -230,6 +247,50 @@ export function TaskList({ className }: TaskListProps) {
230247
}
231248
};
232249

250+
/**
251+
* Handles task deletion by marking it as deleted
252+
*/
253+
const handleDeleteTask = async (taskId: string) => {
254+
try {
255+
const task = tasks.find((t) => t.id === taskId);
256+
if (!task) return;
257+
258+
if (!apiEndpoint || !apiKey || !userId) {
259+
showToast({
260+
type: "error",
261+
title: "Cannot delete task - API not configured",
262+
});
263+
return;
264+
}
265+
266+
console.log("🗑️ Deleting task...", { taskId });
267+
268+
// Call the API to mark the task as deleted
269+
const result = await updateAction(taskId, "deleted", userId, apiKey, apiEndpoint);
270+
271+
if (result.success) {
272+
// Remove the task from local state
273+
setTasks((prevTasks) => prevTasks.filter((t) => t.id !== taskId));
274+
275+
showToast({
276+
type: "success",
277+
title: "Task deleted successfully",
278+
});
279+
} else {
280+
showToast({
281+
type: "error",
282+
title: "Failed to delete task",
283+
});
284+
}
285+
} catch (err) {
286+
console.error("Failed to delete task:", err);
287+
showToast({
288+
type: "error",
289+
title: "Failed to delete task",
290+
});
291+
}
292+
};
293+
233294
const getPriorityColor = (priority?: Task["priority"]) => {
234295
switch (priority) {
235296
case "high":
@@ -277,7 +338,19 @@ export function TaskList({ className }: TaskListProps) {
277338
<div className={`p-6 ${className || ""}`}>
278339
<div className="max-w-4xl mx-auto">
279340
<div className="mb-6">
280-
<h1 className="text-3xl font-bold text-gray-900 mb-2">Task List</h1>
341+
<div className="flex items-center justify-between mb-2">
342+
<h1 className="text-3xl font-bold text-gray-900">Task List</h1>
343+
<Button
344+
onClick={loadTasks}
345+
variant="outline"
346+
size="sm"
347+
className="flex items-center gap-2"
348+
disabled={loading}
349+
>
350+
<RefreshCw size={16} className={loading ? "animate-spin" : ""} />
351+
Refresh
352+
</Button>
353+
</div>
281354
<p className="text-gray-600">View and manage tasks extracted from voice conversations</p>
282355
</div>
283356

@@ -299,92 +372,124 @@ export function TaskList({ className }: TaskListProps) {
299372
</Button>
300373
</div>
301374
) : (
302-
<div className="space-y-4">
303-
{tasks.map((task) => (
304-
<div
305-
key={task.id}
306-
className={`bg-white rounded-lg shadow-sm border transition-all duration-200 ${
307-
task.completed ? "opacity-75" : "hover:shadow-md"
308-
}`}
309-
>
310-
<div className="p-6">
311-
<div className="flex items-start justify-between mb-3">
312-
<div className="flex items-start gap-3 flex-1">
313-
<input
314-
type="checkbox"
315-
checked={task.completed}
316-
onChange={() => handleToggleComplete(task.id)}
317-
className="mt-1 h-4 w-4 text-violet-600 rounded border-gray-300 focus:ring-violet-500"
318-
/>
319-
<div className="flex-1">
320-
<h3
321-
className={`text-lg font-medium mb-1 ${
322-
task.completed ? "line-through text-gray-500" : "text-gray-900"
323-
}`}
324-
>
325-
{task.title}
326-
</h3>
327-
<p
328-
className={`text-sm mb-3 ${
329-
task.completed ? "text-gray-400" : "text-gray-600"
330-
}`}
331-
>
332-
{task.description}
333-
</p>
375+
<div>
376+
{/* Task Statistics */}
377+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
378+
<div className="bg-white p-4 rounded-lg shadow-sm border">
379+
<div className="text-2xl font-bold text-gray-900">{tasks.length}</div>
380+
<div className="text-sm text-gray-600">Total Tasks</div>
381+
</div>
382+
<div className="bg-white p-4 rounded-lg shadow-sm border">
383+
<div className="text-2xl font-bold text-green-600">
384+
{tasks.filter(t => t.completed).length}
385+
</div>
386+
<div className="text-sm text-gray-600">Completed</div>
387+
</div>
388+
<div className="bg-white p-4 rounded-lg shadow-sm border">
389+
<div className="text-2xl font-bold text-orange-600">
390+
{tasks.filter(t => !t.completed).length}
391+
</div>
392+
<div className="text-sm text-gray-600">Pending</div>
393+
</div>
394+
</div>
395+
396+
{/* Task List */}
397+
<div className="space-y-4">
398+
{tasks.map((task) => (
399+
<div
400+
key={task.id}
401+
className={`bg-white rounded-lg shadow-sm border transition-all duration-200 ${
402+
task.completed ? "opacity-75" : "hover:shadow-md"
403+
}`}
404+
>
405+
<div className="p-6">
406+
<div className="flex items-start justify-between mb-3">
407+
<div className="flex items-start gap-3 flex-1">
408+
<input
409+
type="checkbox"
410+
checked={task.completed}
411+
onChange={() => handleToggleComplete(task.id)}
412+
className="mt-1 h-4 w-4 text-violet-600 rounded border-gray-300 focus:ring-violet-500"
413+
/>
414+
<div className="flex-1">
415+
<h3
416+
className={`text-lg font-medium mb-1 ${
417+
task.completed ? "line-through text-gray-500" : "text-gray-900"
418+
}`}
419+
>
420+
{task.title}
421+
</h3>
422+
<p
423+
className={`text-sm mb-3 ${
424+
task.completed ? "text-gray-400" : "text-gray-600"
425+
}`}
426+
>
427+
{task.description}
428+
</p>
429+
</div>
334430
</div>
431+
<span
432+
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${getPriorityColor(task.priority)}`}
433+
>
434+
{task.priority === "high"
435+
? "High"
436+
: task.priority === "medium"
437+
? "Med"
438+
: task.priority === "low"
439+
? "Low"
440+
: "-"}
441+
</span>
335442
</div>
336-
<span
337-
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${getPriorityColor(task.priority)}`}
338-
>
339-
{task.priority === "high"
340-
? "High"
341-
: task.priority === "medium"
342-
? "Med"
343-
: task.priority === "low"
344-
? "Low"
345-
: "-"}
346-
</span>
347-
</div>
348443

349-
<div className="flex items-center justify-between text-sm text-gray-500">
350-
<div className="flex items-center gap-4">
351-
<div className="flex items-center gap-1">
352-
<Clock size={14} />
353-
<span>Created: {formatDate(task.createdAt)}</span>
354-
</div>
355-
{task.dueDate && (
444+
<div className="flex items-center justify-between text-sm text-gray-500">
445+
<div className="flex items-center gap-4">
356446
<div className="flex items-center gap-1">
357-
<Calendar size={14} />
358-
<span>Due: {formatDate(task.dueDate)}</span>
447+
<Clock size={14} />
448+
<span>Created: {formatDate(task.createdAt)}</span>
449+
</div>
450+
{task.dueDate && (
451+
<div className="flex items-center gap-1">
452+
<Calendar size={14} />
453+
<span>Due: {formatDate(task.dueDate)}</span>
454+
</div>
455+
)}
456+
<span className="px-2 py-1 bg-gray-100 rounded text-xs">{task.type}</span>
457+
</div>
458+
459+
{!task.completed && (
460+
<div className="flex gap-2">
461+
<Button
462+
size="sm"
463+
variant="outline"
464+
onClick={() => handleAddToCalendar(task, "google")}
465+
className="text-xs"
466+
>
467+
Google Calendar
468+
</Button>
469+
<Button
470+
size="sm"
471+
variant="outline"
472+
onClick={() => handleAddToCalendar(task, "ios")}
473+
className="text-xs"
474+
>
475+
iOS Calendar
476+
</Button>
477+
<Button
478+
size="sm"
479+
variant="outline"
480+
onClick={() => handleDeleteTask(task.id)}
481+
className="text-xs text-red-600 hover:text-red-700 hover:border-red-300"
482+
>
483+
<Trash2 size={12} className="mr-1" />
484+
Delete
485+
</Button>
359486
</div>
360487
)}
361-
<span className="px-2 py-1 bg-gray-100 rounded text-xs">{task.type}</span>
362488
</div>
363-
364-
{!task.completed && (
365-
<div className="flex gap-2">
366-
<Button
367-
size="sm"
368-
variant="outline"
369-
onClick={() => handleAddToCalendar(task, "google")}
370-
className="text-xs"
371-
>
372-
Google Calendar
373-
</Button>
374-
<Button
375-
size="sm"
376-
variant="outline"
377-
onClick={() => handleAddToCalendar(task, "ios")}
378-
className="text-xs"
379-
>
380-
iOS Calendar
381-
</Button>
382-
</div>
383-
)}
384489
</div>
385490
</div>
386-
</div>
387-
))}
491+
))}
492+
</div>
388493
</div>
389494
)}
390495
</div>

0 commit comments

Comments
 (0)