feat: task detail panel with progress notes
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { useTasks } from "./hooks/useTasks";
|
import { useTasks } from "./hooks/useTasks";
|
||||||
import { TaskCard } from "./components/TaskCard";
|
import { TaskCard } from "./components/TaskCard";
|
||||||
|
import { TaskDetailPanel } from "./components/TaskDetailPanel";
|
||||||
import { CreateTaskModal } from "./components/CreateTaskModal";
|
import { CreateTaskModal } from "./components/CreateTaskModal";
|
||||||
import { LoginPage } from "./components/LoginPage";
|
import { LoginPage } from "./components/LoginPage";
|
||||||
import { useSession, signOut } from "./lib/auth-client";
|
import { useSession, signOut } from "./lib/auth-client";
|
||||||
import { updateTask, reorderTasks, createTask } from "./lib/api";
|
import { updateTask, reorderTasks, createTask } from "./lib/api";
|
||||||
import type { TaskStatus } from "./lib/types";
|
import type { Task, TaskStatus } from "./lib/types";
|
||||||
|
|
||||||
// Token stored in localStorage for bearer-token admin operations
|
// Token stored in localStorage for bearer-token admin operations
|
||||||
function getToken(): string {
|
function getToken(): string {
|
||||||
@@ -16,6 +17,7 @@ function Dashboard() {
|
|||||||
const { tasks, loading, error, refresh } = useTasks(5000);
|
const { tasks, loading, error, refresh } = useTasks(5000);
|
||||||
const [showCreate, setShowCreate] = useState(false);
|
const [showCreate, setShowCreate] = useState(false);
|
||||||
const [showCompleted, setShowCompleted] = useState(false);
|
const [showCompleted, setShowCompleted] = useState(false);
|
||||||
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||||
const [tokenInput, setTokenInput] = useState("");
|
const [tokenInput, setTokenInput] = useState("");
|
||||||
const [showTokenInput, setShowTokenInput] = useState(false);
|
const [showTokenInput, setShowTokenInput] = useState(false);
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
@@ -23,6 +25,12 @@ function Dashboard() {
|
|||||||
const token = getToken();
|
const token = getToken();
|
||||||
const hasToken = !!token;
|
const hasToken = !!token;
|
||||||
|
|
||||||
|
// Keep selected task in sync with refreshed data
|
||||||
|
const selectedTaskData = useMemo(() => {
|
||||||
|
if (!selectedTask) return null;
|
||||||
|
return tasks.find((t) => t.id === selectedTask.id) || null;
|
||||||
|
}, [tasks, selectedTask]);
|
||||||
|
|
||||||
const activeTasks = useMemo(() => tasks.filter((t) => t.status === "active"), [tasks]);
|
const activeTasks = useMemo(() => tasks.filter((t) => t.status === "active"), [tasks]);
|
||||||
const queuedTasks = useMemo(() => tasks.filter((t) => t.status === "queued"), [tasks]);
|
const queuedTasks = useMemo(() => tasks.filter((t) => t.status === "queued"), [tasks]);
|
||||||
const blockedTasks = useMemo(() => tasks.filter((t) => t.status === "blocked"), [tasks]);
|
const blockedTasks = useMemo(() => tasks.filter((t) => t.status === "blocked"), [tasks]);
|
||||||
@@ -195,6 +203,7 @@ function Dashboard() {
|
|||||||
task={task}
|
task={task}
|
||||||
onStatusChange={handleStatusChange}
|
onStatusChange={handleStatusChange}
|
||||||
isActive
|
isActive
|
||||||
|
onClick={() => setSelectedTask(task)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -213,6 +222,7 @@ function Dashboard() {
|
|||||||
key={task.id}
|
key={task.id}
|
||||||
task={task}
|
task={task}
|
||||||
onStatusChange={handleStatusChange}
|
onStatusChange={handleStatusChange}
|
||||||
|
onClick={() => setSelectedTask(task)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -239,6 +249,7 @@ function Dashboard() {
|
|||||||
onMoveDown={() => handleMoveDown(i)}
|
onMoveDown={() => handleMoveDown(i)}
|
||||||
isFirst={i === 0}
|
isFirst={i === 0}
|
||||||
isLast={i === queuedTasks.length - 1}
|
isLast={i === queuedTasks.length - 1}
|
||||||
|
onClick={() => setSelectedTask(task)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -260,6 +271,7 @@ function Dashboard() {
|
|||||||
key={task.id}
|
key={task.id}
|
||||||
task={task}
|
task={task}
|
||||||
onStatusChange={handleStatusChange}
|
onStatusChange={handleStatusChange}
|
||||||
|
onClick={() => setSelectedTask(task)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -267,6 +279,19 @@ function Dashboard() {
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Task Detail Panel */}
|
||||||
|
{selectedTaskData && (
|
||||||
|
<TaskDetailPanel
|
||||||
|
task={selectedTaskData}
|
||||||
|
onClose={() => setSelectedTask(null)}
|
||||||
|
onStatusChange={(id, status) => {
|
||||||
|
handleStatusChange(id, status);
|
||||||
|
setSelectedTask(null);
|
||||||
|
}}
|
||||||
|
hasToken={hasToken}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="text-center text-xs text-gray-300 py-4">
|
<footer className="text-center text-xs text-gray-300 py-4">
|
||||||
Hammer Queue v0.1 · Auto-refreshes every 5s
|
Hammer Queue v0.1 · Auto-refreshes every 5s
|
||||||
|
|||||||
@@ -1 +1,16 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@keyframes slide-in-right {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-right {
|
||||||
|
animation: slide-in-right 0.25s ease-out;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user