import { Link } from "@tanstack/react-router";
import {
ArrowDown,
ArrowUp,
ArrowUpRight,
Check,
ChevronDown,
Circle,
ListChecks,
MessageCircle,
Pause,
RotateCcw,
Scale,
} from "lucide-react";
import { useState } from "react";
import { Button } from "#/components/ui/button";
import { Checkbox } from "#/components/ui/checkbox";
import { Input } from "#/components/ui/input";
import { Textarea } from "#/components/ui/textarea";
import { conversations } from "#/features/support/mock-conversations";
import {
type JournalEntry,
type Task,
useWorkspaceState,
} from "#/features/workspace/workspace-state";
type ExecutionWorkbenchProps = {
selectedId?: string;
onSelect: (id: string) => void;
};
export function ExecutionWorkbench({ selectedId, onSelect }: ExecutionWorkbenchProps) {
const { tasks, setTasks, statuses, entries } = useWorkspaceState();
const currentEntries = entries.filter(
(entry) => !entries.some((other) => other.correctionOf === entry.id),
);
const weightEntry = currentEntries.find((entry) => entry.kind === "体重");
const replies = conversations.filter(
(conversation) => conversation.replyRequired || statuses[conversation.id] === "now",
);
function toggle(id: string) {
onSelect(selectedId === id ? "" : id);
}
function finish(id: string) {
setTasks((current) =>
current.map((task) =>
task.id === id ? { ...task, status: task.status === "todo" ? "done" : "todo" } : task,
),
);
}
return (
{(
[
{ id: "focus", label: "Start here", subtitle: "Just the next thing" },
{ id: "routine", label: "Routines", subtitle: "Find your rhythm" },
{ id: "optional", label: "If there’s room", subtitle: "No need to fit it all in" },
] as const
).map((section) => (
{tasks
.filter((task) => task.section === section.id)
.map((task) => {
const id = `task:${task.id}`;
const expanded = selectedId === id;
const steps = task.steps ?? [];
const checked = steps.filter((step) => task.completedSteps?.includes(step)).length;
return (
0 && checked < steps.length
}
onClick={() => finish(task.id)}
>
{task.status === "done" ? (
) : task.status === "later" ? (
) : (
)}
toggle(id)}
>
{task.title}
{task.status === "done"
? "Completed"
: task.status === "later"
? "Set aside"
: steps.length
? `${checked} of ${steps.length} steps · ${steps.find((step) => !task.completedSteps?.includes(step)) ?? "Ready to finish"}`
: task.id === "review"
? "Start with the first screen"
: "At your own pace"}
{expanded && (
onSelect("")} />
)}
);
})}
{section.id === "focus" &&
replies.slice(0, 2).map((conversation) => (
{statuses[conversation.id] === "sent" ||
statuses[conversation.id] === "dismissed" ? (
) : (
)}
Reply to {conversation.name}
{statuses[conversation.id] === "sent"
? "Reply recorded · Preview only"
: statuses[conversation.id] === "dismissed"
? "No reply needed"
: statuses[conversation.id] === "later"
? "Set aside"
: statuses[conversation.id] === "waiting"
? "Waiting for their reply"
: conversation.subject}
))}
{section.id === "routine" && (
{weightEntry ? : }
toggle("weight")}
>
Record weight
{weightEntry ? `Recorded · ${weightEntry.text} kg` : "A quick check-in"}
{selectedId === "weight" && (
onSelect("")}
/>
)}
)}
{!tasks.some((task) => task.section === section.id && task.status === "todo") &&
(section.id !== "focus" || !replies.length) &&
section.id !== "routine" && (
A little breathing room.
)}
))}
);
}
function TaskDetail({ task, onAction }: { task: Task; onAction: () => void }) {
const { setTasks, entries, setEntries } = useWorkspaceState();
const source = `task:${task.id}`;
const savedNote = entries.find((entry) => entry.kind === "再開地点" && entry.source === source);
const [note, setNote] = useState(savedNote?.text ?? "");
const [notice, setNotice] = useState("");
const steps = task.steps ?? [];
const checked = steps.filter((step) => task.completedSteps?.includes(step)).length;
function setStatus(status: Task["status"]) {
setTasks((current) =>
current.map((item) => (item.id === task.id ? { ...item, status } : item)),
);
onAction();
}
function move(direction: -1 | 1) {
setTasks((current) => {
const position = current.findIndex((item) => item.id === task.id);
const siblings = current
.map((item, index) => ({ item, index }))
.filter(({ item }) => item.section === task.section && item.status === task.status);
const sibling = siblings.findIndex(({ item }) => item.id === task.id);
const target = siblings[sibling + direction];
const origin = current[position];
if (!target || !origin) return current;
const updated = [...current];
updated[position] = target.item;
updated[target.index] = origin;
return updated;
});
}
return (
<>
{task.section === "routine"
? "Your routine"
: task.section === "optional"
? "If there’s room"
: "One next step"}
{task.title}
{steps.length > 0 ? (
<>
{checked} of {steps.length} steps
{task.status === "done"
? "Completed"
: checked === steps.length
? "Ready to finish"
: "At your pace"}
{steps.map((step, index) => (
setTasks((current) =>
current.map((item) =>
item.id === task.id
? {
...item,
completedSteps: value
? Array.from(new Set([...(item.completedSteps ?? []), step]))
: (item.completedSteps ?? []).filter(
(completed) => completed !== step,
),
}
: item,
),
)
}
/>
{step}
{String(index + 1).padStart(2, "0")}
))}
>
) : (
<>
{task.id === "review"
? "Look through the first-time experience. Leave a short note on anything that feels unclear."
: task.section === "optional"
? "There’s room to decide later. Start whenever it feels right."
: "Choose a small first step. You can leave a note if you need to pause."}
{task.id === "review" && (
Start here
Open the materials and look at the first screen.
)}
>
)}
{task.status !== "done" && (
setStatus("done")}
disabled={steps.length > 0 && checked < steps.length}
>
{steps.length > 0 ? "Finish routine" : "Mark complete"}
)}
{task.status === "todo" && (
setStatus("later")}>
Later
)}
{task.status !== "todo" && (
setStatus("todo")}>
Bring back
)}
Order in your list
move(-1)}
>
move(1)}
>
>
);
}
function WeightDetail({ entry, onAction }: { entry?: JournalEntry; onAction: () => void }) {
const { setEntries } = useWorkspaceState();
const [weight, setWeight] = useState(entry?.text ?? "");
const [error, setError] = useState("");
return (
<>
A quick check-in
Record your weight
Just a measurement. No target to hit.
{entry && (
)}
Your measurement also appears in Journal.
>
);
}