fix: improved subtask expand/collapse in calendar and kanban views
Kanban cards now have inline subtask expand/collapse with progress bar, chevron toggle, and checkbox list. Calendar view subtask badge updated to match with ChevronDown icon, highlight when expanded, and wider progress bar. Consistent UX across both views. v1.55.2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
22a193180b
commit
1f90526b5b
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.55.1",
|
"version": "1.55.2",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1952,6 +1952,73 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Kanban stage color indicator on weekly tasks */
|
/* Kanban stage color indicator on weekly tasks */
|
||||||
|
.kanban-card-subtask-toggle {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px 0;
|
||||||
|
color: var(--weekly-text-muted, #9ca3af);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-btn:hover {
|
||||||
|
color: var(--weekly-text, #666);
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--weekly-border, #e5e7eb);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 60px;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-bar-fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-count {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 4px 0 2px 2px;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--weekly-text, #333);
|
||||||
|
padding: 1px 0;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-item input[type="checkbox"] {
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-item.completed {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.kanban-card-subtask-done {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
.kanban-stage-indicator {
|
.kanban-stage-indicator {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|||||||
@ -2078,6 +2078,7 @@ export default function WeeklyView() {
|
|||||||
const [kanbanAddingStageId, setKanbanAddingStageId] = useState<string | null>(null);
|
const [kanbanAddingStageId, setKanbanAddingStageId] = useState<string | null>(null);
|
||||||
const [kanbanNewTaskTitle, setKanbanNewTaskTitle] = useState("");
|
const [kanbanNewTaskTitle, setKanbanNewTaskTitle] = useState("");
|
||||||
const [kanbanDetailTask, setKanbanDetailTask] = useState<Task | null>(null);
|
const [kanbanDetailTask, setKanbanDetailTask] = useState<Task | null>(null);
|
||||||
|
const [kanbanExpandedCards, setKanbanExpandedCards] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const [protectEventTimes, setProtectEventTimes] = useState(false);
|
const [protectEventTimes, setProtectEventTimes] = useState(false);
|
||||||
const [unlockedEvents, setUnlockedEvents] = useState<Set<string>>(new Set());
|
const [unlockedEvents, setUnlockedEvents] = useState<Set<string>>(new Set());
|
||||||
@ -6447,7 +6448,15 @@ export default function WeeklyView() {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderKanbanCard = (task: Task) => (
|
const renderKanbanCard = (task: Task) => {
|
||||||
|
const subTasks = task.subTasks || [];
|
||||||
|
const subCompleted = subTasks.filter(s => s.completed).length;
|
||||||
|
const subTotal = subTasks.length;
|
||||||
|
const subPct = subTotal > 0 ? (subCompleted / subTotal) * 100 : 0;
|
||||||
|
const allSubDone = subTotal > 0 && subCompleted === subTotal;
|
||||||
|
const isExpanded = kanbanExpandedCards.has(task.id);
|
||||||
|
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
className={`kanban-card ${task.completed ? "kanban-card-done" : ""}`}
|
className={`kanban-card ${task.completed ? "kanban-card-done" : ""}`}
|
||||||
@ -6457,9 +6466,8 @@ export default function WeeklyView() {
|
|||||||
e.dataTransfer.effectAllowed = "move";
|
e.dataTransfer.effectAllowed = "move";
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// Don't open detail if clicking checkbox, contentEditable, or buttons
|
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (target.tagName === "INPUT" || target.contentEditable === "true" || target.closest("button")) return;
|
if (target.tagName === "INPUT" || target.contentEditable === "true" || target.closest("button") || target.closest(".kanban-card-subtask-list")) return;
|
||||||
setKanbanDetailTask(task);
|
setKanbanDetailTask(task);
|
||||||
}}
|
}}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
@ -6488,9 +6496,42 @@ export default function WeeklyView() {
|
|||||||
{task.title}
|
{task.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{task.subTasks && task.subTasks.length > 0 && (
|
{subTotal > 0 && (
|
||||||
<div className="kanban-card-subtasks" style={{ fontSize: "0.7rem", color: "#9ca3af", marginTop: "2px" }}>
|
<div className="kanban-card-subtask-toggle" onClick={(e) => e.stopPropagation()}>
|
||||||
{task.subTasks.filter(st => st.completed).length}/{task.subTasks.length} {language === "de" ? "Unteraufgaben" : "subtasks"}
|
<button
|
||||||
|
className="kanban-card-subtask-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setKanbanExpandedCards(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(task.id)) next.delete(task.id);
|
||||||
|
else next.add(task.id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronDown size={12} style={{ transform: isExpanded ? "rotate(0deg)" : "rotate(-90deg)", transition: "transform 0.2s" }} />
|
||||||
|
<span className="kanban-card-subtask-bar">
|
||||||
|
<span className="kanban-card-subtask-bar-fill" style={{ width: `${subPct}%`, background: allSubDone ? "#0d9488" : "var(--weekly-accent, #6366f1)" }} />
|
||||||
|
</span>
|
||||||
|
<span className="kanban-card-subtask-count" style={{ color: allSubDone ? "#0d9488" : undefined }}>
|
||||||
|
{subCompleted}/{subTotal}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="kanban-card-subtask-list">
|
||||||
|
{subTasks.map(sub => (
|
||||||
|
<div key={sub.id} className={`kanban-card-subtask-item ${sub.completed ? "completed" : ""}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={sub.completed}
|
||||||
|
onChange={() => toggleSubTask(sub.id)}
|
||||||
|
style={{ accentColor: "var(--weekly-accent, #6366f1)" }}
|
||||||
|
/>
|
||||||
|
<span className={sub.completed ? "kanban-card-subtask-done" : ""}>{sub.title}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{task.markdownContent && (
|
{task.markdownContent && (
|
||||||
@ -6515,6 +6556,7 @@ export default function WeeklyView() {
|
|||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const kanbanColumnDrop = (stageId: string | null) => ({
|
const kanbanColumnDrop = (stageId: string | null) => ({
|
||||||
onDragOver: (e: React.DragEvent) => {
|
onDragOver: (e: React.DragEvent) => {
|
||||||
@ -9710,9 +9752,9 @@ function TaskItem({
|
|||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "4px",
|
gap: "4px",
|
||||||
padding: "2px 8px 2px 6px",
|
padding: "2px 8px 2px 4px",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
background: "transparent",
|
background: expanded ? "rgba(99,102,241,0.08)" : "transparent",
|
||||||
color: allDone ? "var(--weekly-teal, #0d9488)" : "var(--weekly-text-light, #9ca3af)",
|
color: allDone ? "var(--weekly-teal, #0d9488)" : "var(--weekly-text-light, #9ca3af)",
|
||||||
border: "none",
|
border: "none",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
@ -9721,17 +9763,14 @@ function TaskItem({
|
|||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
transition: "color 0.15s",
|
transition: "all 0.15s",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ transform: expanded ? "rotate(90deg)" : "rotate(0deg)", transition: "transform 0.2s" }}>
|
<ChevronDown size={11} style={{ transform: expanded ? "rotate(0deg)" : "rotate(-90deg)", transition: "transform 0.2s" }} />
|
||||||
<polyline points="9 18 15 12 9 6" />
|
|
||||||
</svg>
|
|
||||||
{/* Mini progress bar */}
|
|
||||||
<span style={{
|
<span style={{
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
width: "20px",
|
width: "24px",
|
||||||
height: "3px",
|
height: "4px",
|
||||||
borderRadius: "2px",
|
borderRadius: "2px",
|
||||||
background: "var(--weekly-border, #e5e7eb)",
|
background: "var(--weekly-border, #e5e7eb)",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
@ -9744,7 +9783,7 @@ function TaskItem({
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
width: `${pct}%`,
|
width: `${pct}%`,
|
||||||
borderRadius: "2px",
|
borderRadius: "2px",
|
||||||
background: allDone ? "var(--weekly-teal, #0d9488)" : "#6366f1",
|
background: allDone ? "var(--weekly-teal, #0d9488)" : "var(--weekly-accent, #6366f1)",
|
||||||
transition: "width 0.3s ease",
|
transition: "width 0.3s ease",
|
||||||
}} />
|
}} />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user