2026-02-19 15:33:45 +00:00
|
|
|
interface StoryTodos {
|
|
|
|
|
storyId: string;
|
|
|
|
|
storyName: string | null;
|
|
|
|
|
items: string[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TodoPanelProps {
|
|
|
|
|
todos: StoryTodos[];
|
|
|
|
|
isTodoLoading: boolean;
|
|
|
|
|
todoError: string | null;
|
|
|
|
|
lastTodoRefresh: Date | null;
|
|
|
|
|
onRefresh: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formatTimestamp = (value: Date | null): string => {
|
|
|
|
|
if (!value) return "\u2014";
|
|
|
|
|
return value.toLocaleTimeString([], {
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
minute: "2-digit",
|
|
|
|
|
second: "2-digit",
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function TodoPanel({
|
|
|
|
|
todos,
|
|
|
|
|
isTodoLoading,
|
|
|
|
|
todoError,
|
|
|
|
|
lastTodoRefresh,
|
|
|
|
|
onRefresh,
|
|
|
|
|
}: TodoPanelProps) {
|
|
|
|
|
const totalTodos = todos.reduce((sum, s) => sum + s.items.length, 0);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
border: "1px solid #333",
|
|
|
|
|
borderRadius: "10px",
|
|
|
|
|
padding: "12px 16px",
|
|
|
|
|
background: "#1f1f1f",
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
gap: "8px",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
justifyContent: "space-between",
|
|
|
|
|
gap: "12px",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
gap: "8px",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ fontWeight: 600 }}>Story TODOs</div>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={onRefresh}
|
|
|
|
|
disabled={isTodoLoading}
|
|
|
|
|
style={{
|
|
|
|
|
padding: "4px 10px",
|
|
|
|
|
borderRadius: "999px",
|
|
|
|
|
border: "1px solid #333",
|
|
|
|
|
background: isTodoLoading ? "#2a2a2a" : "#2f2f2f",
|
|
|
|
|
color: isTodoLoading ? "#777" : "#aaa",
|
|
|
|
|
cursor: isTodoLoading ? "not-allowed" : "pointer",
|
|
|
|
|
fontSize: "0.75em",
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Refresh
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
alignItems: "flex-end",
|
|
|
|
|
gap: "2px",
|
|
|
|
|
fontSize: "0.85em",
|
|
|
|
|
color: totalTodos === 0 ? "#7ee787" : "#aaa",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div>{totalTodos} remaining</div>
|
|
|
|
|
<div style={{ fontSize: "0.8em", color: "#777" }}>
|
|
|
|
|
Updated {formatTimestamp(lastTodoRefresh)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{isTodoLoading ? (
|
|
|
|
|
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
|
|
|
|
|
Loading story TODOs...
|
|
|
|
|
</div>
|
|
|
|
|
) : todoError ? (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: "0.85em",
|
|
|
|
|
color: "#ff7b72",
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
gap: "8px",
|
|
|
|
|
flexWrap: "wrap",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span>{todoError}</span>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={onRefresh}
|
|
|
|
|
disabled={isTodoLoading}
|
|
|
|
|
style={{
|
|
|
|
|
padding: "4px 10px",
|
|
|
|
|
borderRadius: "999px",
|
|
|
|
|
border: "1px solid #333",
|
|
|
|
|
background: isTodoLoading ? "#2a2a2a" : "#2f2f2f",
|
|
|
|
|
color: isTodoLoading ? "#777" : "#aaa",
|
|
|
|
|
cursor: isTodoLoading ? "not-allowed" : "pointer",
|
|
|
|
|
fontSize: "0.75em",
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Retry
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
) : totalTodos === 0 ? (
|
|
|
|
|
<div style={{ fontSize: "0.85em", color: "#7ee787" }}>
|
|
|
|
|
All acceptance criteria complete.
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
gap: "6px",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{todos
|
|
|
|
|
.filter((s) => s.items.length > 0)
|
|
|
|
|
.map((story) => (
|
|
|
|
|
<div key={story.storyId}>
|
2026-02-19 16:04:05 +00:00
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: "0.8em",
|
|
|
|
|
color: "#777",
|
|
|
|
|
marginBottom: "4px",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{story.storyName ?? story.storyId}
|
|
|
|
|
</div>
|
2026-02-19 15:33:45 +00:00
|
|
|
<ul
|
|
|
|
|
style={{
|
|
|
|
|
margin: "0 0 0 16px",
|
|
|
|
|
padding: 0,
|
|
|
|
|
fontSize: "0.85em",
|
|
|
|
|
color: "#ccc",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{story.items.map((item) => (
|
|
|
|
|
<li key={`todo-${story.storyId}-${item}`}>{item}</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|