Replace project filter buttons with multi-select dropdown
Projects now appear as a dropdown with checkboxes instead of horizontal buttons. Supports All Projects, Clear All, and individual project toggle filtering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
19f669f7d1
commit
f30d3d603c
|
|
@ -38,7 +38,9 @@ function App() {
|
|||
const [connected, setConnected] = useState(false);
|
||||
const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
||||
const [selectedProjects, setSelectedProjects] = useState<Set<string> | null>(null); // null = all
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
const [draggedTask, setDraggedTask] = useState<AggregatedTask | null>(null);
|
||||
const [dragOverColumn, setDragOverColumn] = useState<string | null>(null);
|
||||
|
|
@ -205,13 +207,52 @@ function App() {
|
|||
};
|
||||
}, [connectWebSocket]);
|
||||
|
||||
// Close dropdown on outside click
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
||||
setDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", handleClick);
|
||||
return () => document.removeEventListener("click", handleClick);
|
||||
}, []);
|
||||
|
||||
const isProjectSelected = (name: string) => selectedProjects === null || selectedProjects.has(name);
|
||||
|
||||
const toggleProject = (name: string) => {
|
||||
const activeNames = projects.filter((p) => tasks.some((t) => t.projectName === p.name)).map((p) => p.name);
|
||||
if (selectedProjects === null) {
|
||||
const next = new Set(activeNames);
|
||||
next.delete(name);
|
||||
setSelectedProjects(next);
|
||||
} else if (selectedProjects.has(name)) {
|
||||
const next = new Set(selectedProjects);
|
||||
next.delete(name);
|
||||
setSelectedProjects(next);
|
||||
} else {
|
||||
const next = new Set(selectedProjects);
|
||||
next.add(name);
|
||||
setSelectedProjects(next.size === activeNames.length ? null : next);
|
||||
}
|
||||
};
|
||||
|
||||
const activeProjects = projects.filter((p) => tasks.some((t) => t.projectName === p.name));
|
||||
|
||||
const dropdownLabel = (() => {
|
||||
if (selectedProjects === null) return "All Projects";
|
||||
if (selectedProjects.size === 0) return "No Projects";
|
||||
if (selectedProjects.size === 1) return [...selectedProjects][0];
|
||||
return `${selectedProjects.size} Projects`;
|
||||
})();
|
||||
|
||||
// Group tasks by status
|
||||
const statuses = ["To Do", "In Progress", "Done", "Won't Do"];
|
||||
const tasksByStatus = statuses.reduce(
|
||||
(acc, status) => {
|
||||
acc[status] = tasks.filter((t) => {
|
||||
const statusMatch = t.status.toLowerCase() === status.toLowerCase();
|
||||
const projectMatch = !selectedProject || t.projectName === selectedProject;
|
||||
const projectMatch = selectedProjects === null || selectedProjects.has(t.projectName);
|
||||
const filterMatch =
|
||||
!filter ||
|
||||
t.title.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
|
|
@ -348,46 +389,150 @@ function App() {
|
|||
minWidth: "200px",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => setSelectedProject(null)}
|
||||
style={{
|
||||
padding: "0.5rem 1rem",
|
||||
borderRadius: "0.375rem",
|
||||
border: selectedProject === null ? "2px solid #3b82f6" : "1px solid #334155",
|
||||
backgroundColor: selectedProject === null ? "#1e40af" : "#0f172a",
|
||||
color: "#e2e8f0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
All Projects
|
||||
</button>
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project.path}
|
||||
onClick={() => setSelectedProject(project.name)}
|
||||
<div ref={dropdownRef} style={{ position: "relative" }}>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setDropdownOpen(!dropdownOpen);
|
||||
}}
|
||||
style={{
|
||||
padding: "0.5rem 1rem",
|
||||
borderRadius: "0.375rem",
|
||||
border: selectedProject === project.name ? `2px solid ${project.color}` : "1px solid #334155",
|
||||
backgroundColor: selectedProject === project.name ? project.color + "33" : "#0f172a",
|
||||
border: "1px solid #334155",
|
||||
backgroundColor: "#0f172a",
|
||||
color: "#e2e8f0",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
fontSize: "0.875rem",
|
||||
minWidth: "180px",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
<span style={{ display: "flex", gap: "0.25rem", alignItems: "center" }}>
|
||||
{selectedProjects !== null && selectedProjects.size > 0 && selectedProjects.size <= 5
|
||||
? [...selectedProjects].map((name) => {
|
||||
const proj = projects.find((p) => p.name === name);
|
||||
return proj ? (
|
||||
<span
|
||||
key={name}
|
||||
style={{
|
||||
width: selectedProjects.size === 1 ? "0.75rem" : "0.5rem",
|
||||
height: selectedProjects.size === 1 ? "0.75rem" : "0.5rem",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: proj.color,
|
||||
display: "inline-block",
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
})
|
||||
: null}
|
||||
</span>
|
||||
<span>{dropdownLabel}</span>
|
||||
<span style={{ marginLeft: "auto", fontSize: "0.625rem", transition: "transform 0.2s", transform: dropdownOpen ? "rotate(180deg)" : "none" }}>
|
||||
▼
|
||||
</span>
|
||||
</div>
|
||||
{dropdownOpen && (
|
||||
<div
|
||||
style={{
|
||||
width: "0.75rem",
|
||||
height: "0.75rem",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: project.color,
|
||||
position: "absolute",
|
||||
top: "calc(100% + 4px)",
|
||||
left: 0,
|
||||
minWidth: "220px",
|
||||
background: "#1e293b",
|
||||
border: "1px solid #334155",
|
||||
borderRadius: "0.5rem",
|
||||
boxShadow: "0 10px 25px rgba(0,0,0,0.4)",
|
||||
zIndex: 100,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
{project.name}
|
||||
</button>
|
||||
))}
|
||||
>
|
||||
<div style={{ display: "flex", borderBottom: "1px solid #334155" }}>
|
||||
<button
|
||||
onClick={() => setSelectedProjects(null)}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "0.5rem",
|
||||
background: "none",
|
||||
border: "none",
|
||||
borderRight: "1px solid #334155",
|
||||
color: "#94a3b8",
|
||||
fontSize: "0.75rem",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
All Projects
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedProjects(new Set())}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "0.5rem",
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "#94a3b8",
|
||||
fontSize: "0.75rem",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
{activeProjects.map((project) => {
|
||||
const selected = isProjectSelected(project.name);
|
||||
return (
|
||||
<div
|
||||
key={project.path}
|
||||
onClick={() => toggleProject(project.name)}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
padding: "0.5rem 0.75rem",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.875rem",
|
||||
background: "transparent",
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = "#334155")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: "1rem",
|
||||
height: "1rem",
|
||||
border: selected ? "1.5px solid #3b82f6" : "1.5px solid #475569",
|
||||
borderRadius: "3px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: selected ? "#3b82f6" : "transparent",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{selected && (
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
||||
<path d="M2 5L4 7L8 3" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
width: "0.75rem",
|
||||
height: "0.75rem",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: project.color,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
{project.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* View Content */}
|
||||
|
|
@ -486,7 +631,7 @@ function App() {
|
|||
<DependencyGraph
|
||||
tasks={tasks}
|
||||
projects={projects}
|
||||
selectedProject={selectedProject}
|
||||
selectedProjects={selectedProjects}
|
||||
filter={filter}
|
||||
showCompleted={showCompleted}
|
||||
/>
|
||||
|
|
@ -842,7 +987,7 @@ function TaskCard({ task, onArchive, onDelete, onUpdate, statuses, onDragStart,
|
|||
interface DependencyGraphProps {
|
||||
tasks: AggregatedTask[];
|
||||
projects: Project[];
|
||||
selectedProject: string | null;
|
||||
selectedProjects: Set<string> | null;
|
||||
filter: string;
|
||||
showCompleted: boolean;
|
||||
}
|
||||
|
|
@ -873,7 +1018,7 @@ function isCompleted(status: string): boolean {
|
|||
return lower.includes("done") || lower.includes("complete");
|
||||
}
|
||||
|
||||
function DependencyGraph({ tasks, projects, selectedProject, filter, showCompleted }: DependencyGraphProps) {
|
||||
function DependencyGraph({ tasks, projects, selectedProjects, filter, showCompleted }: DependencyGraphProps) {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [dimensions, setDimensions] = useState({ width: 800, height: 600 });
|
||||
|
|
@ -882,11 +1027,11 @@ function DependencyGraph({ tasks, projects, selectedProject, filter, showComplet
|
|||
const filteredTasks = useMemo(() => {
|
||||
return tasks.filter((t) => {
|
||||
if (!showCompleted && isCompleted(t.status)) return false;
|
||||
if (selectedProject && t.projectName !== selectedProject) return false;
|
||||
if (selectedProjects !== null && !selectedProjects.has(t.projectName)) return false;
|
||||
if (filter && !t.title.toLowerCase().includes(filter.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
}, [tasks, showCompleted, selectedProject, filter]);
|
||||
}, [tasks, showCompleted, selectedProjects, filter]);
|
||||
|
||||
// Build graph data - note: aggregated tasks don't have dependencies in YAML
|
||||
// So we'll just show all tasks as nodes without edges for now
|
||||
|
|
|
|||
|
|
@ -48,7 +48,34 @@
|
|||
color: #e2e8f0;
|
||||
min-width: 200px;
|
||||
}
|
||||
.project-btn, .action-btn {
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid #334155;
|
||||
background: #22c55e;
|
||||
border-color: #16a34a;
|
||||
color: #e2e8f0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.action-btn:hover { background: #16a34a; }
|
||||
.project-dot {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Project Dropdown Filter */
|
||||
.project-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.project-dropdown-trigger {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid #334155;
|
||||
|
|
@ -59,14 +86,97 @@
|
|||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
min-width: 180px;
|
||||
user-select: none;
|
||||
}
|
||||
.project-btn.active { border-width: 2px; border-color: #3b82f6; background: #1e40af; }
|
||||
.action-btn { background: #22c55e; border-color: #16a34a; font-weight: 500; }
|
||||
.action-btn:hover { background: #16a34a; }
|
||||
.project-dot {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 50%;
|
||||
.project-dropdown-trigger:hover {
|
||||
border-color: #475569;
|
||||
background: #1e293b;
|
||||
}
|
||||
.project-dropdown-trigger .arrow {
|
||||
margin-left: auto;
|
||||
font-size: 0.625rem;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.project-dropdown.open .arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.project-dropdown-trigger .selected-dots {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
}
|
||||
.project-dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
min-width: 220px;
|
||||
background: #1e293b;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.4);
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
.project-dropdown.open .project-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
.project-dropdown-actions {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #334155;
|
||||
}
|
||||
.project-dropdown-actions button {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #94a3b8;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.project-dropdown-actions button:hover {
|
||||
background: #334155;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.project-dropdown-actions button:first-child {
|
||||
border-right: 1px solid #334155;
|
||||
}
|
||||
.project-dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.project-dropdown-item:hover {
|
||||
background: #334155;
|
||||
}
|
||||
.project-dropdown-item .checkbox {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border: 1.5px solid #475569;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
.project-dropdown-item.selected .checkbox {
|
||||
background: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
.project-dropdown-item.selected .checkbox::after {
|
||||
content: '';
|
||||
width: 0.35rem;
|
||||
height: 0.6rem;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg) translateY(-1px);
|
||||
}
|
||||
.board {
|
||||
padding: 2rem;
|
||||
|
|
@ -516,7 +626,20 @@
|
|||
|
||||
<div class="filter-bar" id="filter-bar">
|
||||
<input type="text" id="filter" class="filter-input" placeholder="Filter tasks...">
|
||||
<button class="project-btn active" id="all-projects">All Projects</button>
|
||||
<div class="project-dropdown" id="project-dropdown">
|
||||
<div class="project-dropdown-trigger" id="project-dropdown-trigger">
|
||||
<span class="selected-dots" id="selected-dots"></span>
|
||||
<span id="dropdown-label">All Projects</span>
|
||||
<span class="arrow">▼</span>
|
||||
</div>
|
||||
<div class="project-dropdown-menu" id="project-dropdown-menu">
|
||||
<div class="project-dropdown-actions">
|
||||
<button id="select-all-projects">All Projects</button>
|
||||
<button id="clear-all-projects">Clear All</button>
|
||||
</div>
|
||||
<div id="project-dropdown-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="stats-toggle" id="stats-toggle" style="margin-left: auto;">📊 Velocity</button>
|
||||
<button class="action-btn" id="new-task-btn">+ New Task</button>
|
||||
</div>
|
||||
|
|
@ -660,7 +783,7 @@
|
|||
<script>
|
||||
let projects = [];
|
||||
let tasks = [];
|
||||
let selectedProject = null;
|
||||
let selectedProjects = null; // null = all projects, Set = specific projects
|
||||
let ws = null;
|
||||
let draggedTask = null;
|
||||
let currentView = 'kanban';
|
||||
|
|
@ -775,26 +898,107 @@
|
|||
}
|
||||
|
||||
function renderProjects() {
|
||||
const filterBar = document.getElementById('filter-bar');
|
||||
const oldBtns = filterBar.querySelectorAll('.project-btn:not(#all-projects)');
|
||||
oldBtns.forEach(btn => btn.remove());
|
||||
const list = document.getElementById('project-dropdown-list');
|
||||
list.innerHTML = '';
|
||||
|
||||
const newTaskBtn = document.getElementById('new-task-btn');
|
||||
projects.filter(p => tasks.some(t => t.projectName === p.name)).forEach(p => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'project-btn';
|
||||
btn.innerHTML = `<span class="project-dot" style="background:${p.color}"></span>${p.name}`;
|
||||
btn.onclick = () => {
|
||||
selectedProject = p.name;
|
||||
document.querySelectorAll('.project-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
renderTasks();
|
||||
const activeProjects = projects.filter(p => tasks.some(t => t.projectName === p.name));
|
||||
activeProjects.forEach(p => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'project-dropdown-item' + (isProjectSelected(p.name) ? ' selected' : '');
|
||||
item.dataset.project = p.name;
|
||||
item.innerHTML = `<span class="checkbox"></span><span class="project-dot" style="background:${p.color}"></span>${p.name}`;
|
||||
item.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
toggleProject(p.name);
|
||||
};
|
||||
filterBar.insertBefore(btn, newTaskBtn);
|
||||
list.appendChild(item);
|
||||
});
|
||||
|
||||
updateDropdownLabel();
|
||||
|
||||
document.getElementById('stats').textContent =
|
||||
`${projects.filter(p => tasks.some(t => t.projectName === p.name)).length} active projects | ${tasks.length} tasks | Updated ${new Date().toLocaleTimeString()}`;
|
||||
`${activeProjects.length} active projects | ${tasks.length} tasks | Updated ${new Date().toLocaleTimeString()}`;
|
||||
}
|
||||
|
||||
function isProjectSelected(name) {
|
||||
if (selectedProjects === null) return true;
|
||||
return selectedProjects.has(name);
|
||||
}
|
||||
|
||||
function toggleProject(name) {
|
||||
const activeProjects = projects.filter(p => tasks.some(t => t.projectName === p.name));
|
||||
// If currently "all", switch to all-except-this
|
||||
if (selectedProjects === null) {
|
||||
selectedProjects = new Set(activeProjects.map(p => p.name));
|
||||
selectedProjects.delete(name);
|
||||
} else if (selectedProjects.has(name)) {
|
||||
selectedProjects.delete(name);
|
||||
// If none selected, keep as empty set (shows nothing)
|
||||
} else {
|
||||
selectedProjects.add(name);
|
||||
// If all now selected, switch back to null (all)
|
||||
if (selectedProjects.size === activeProjects.length) {
|
||||
selectedProjects = null;
|
||||
}
|
||||
}
|
||||
renderProjects();
|
||||
renderTasks();
|
||||
}
|
||||
|
||||
function selectAllProjects() {
|
||||
selectedProjects = null;
|
||||
renderProjects();
|
||||
renderTasks();
|
||||
}
|
||||
|
||||
function clearAllProjects() {
|
||||
selectedProjects = new Set();
|
||||
renderProjects();
|
||||
renderTasks();
|
||||
}
|
||||
|
||||
function updateDropdownLabel() {
|
||||
const label = document.getElementById('dropdown-label');
|
||||
const dotsContainer = document.getElementById('selected-dots');
|
||||
dotsContainer.innerHTML = '';
|
||||
|
||||
const activeProjects = projects.filter(p => tasks.some(t => t.projectName === p.name));
|
||||
|
||||
if (selectedProjects === null || selectedProjects.size === activeProjects.length) {
|
||||
label.textContent = 'All Projects';
|
||||
} else if (selectedProjects.size === 0) {
|
||||
label.textContent = 'No Projects';
|
||||
} else if (selectedProjects.size === 1) {
|
||||
const name = [...selectedProjects][0];
|
||||
const proj = projects.find(p => p.name === name);
|
||||
if (proj) {
|
||||
dotsContainer.innerHTML = `<span class="project-dot" style="background:${proj.color}"></span>`;
|
||||
}
|
||||
label.textContent = name;
|
||||
} else {
|
||||
// Show colored dots for selected projects
|
||||
[...selectedProjects].forEach(name => {
|
||||
const proj = projects.find(p => p.name === name);
|
||||
if (proj) {
|
||||
const dot = document.createElement('span');
|
||||
dot.className = 'project-dot';
|
||||
dot.style.background = proj.color;
|
||||
dot.style.width = '0.5rem';
|
||||
dot.style.height = '0.5rem';
|
||||
dotsContainer.appendChild(dot);
|
||||
}
|
||||
});
|
||||
label.textContent = `${selectedProjects.size} Projects`;
|
||||
}
|
||||
|
||||
// Update checkbox states
|
||||
document.querySelectorAll('.project-dropdown-item').forEach(item => {
|
||||
if (isProjectSelected(item.dataset.project)) {
|
||||
item.classList.add('selected');
|
||||
} else {
|
||||
item.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function archiveTask(projectPath, taskId) {
|
||||
|
|
@ -931,7 +1135,7 @@
|
|||
let counts = { todo: 0, progress: 0, done: 0, wontdo: 0 };
|
||||
|
||||
tasks.forEach(task => {
|
||||
if (selectedProject && task.projectName !== selectedProject) return;
|
||||
if (selectedProjects !== null && !selectedProjects.has(task.projectName)) return;
|
||||
if (filter && !task.title.toLowerCase().includes(filter) &&
|
||||
!task.projectName.toLowerCase().includes(filter)) return;
|
||||
|
||||
|
|
@ -1199,11 +1403,29 @@
|
|||
}
|
||||
|
||||
document.getElementById('filter').addEventListener('input', renderTasks);
|
||||
document.getElementById('all-projects').onclick = () => {
|
||||
selectedProject = null;
|
||||
document.querySelectorAll('.project-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('all-projects').classList.add('active');
|
||||
renderTasks();
|
||||
|
||||
// Project dropdown toggle
|
||||
document.getElementById('project-dropdown-trigger').onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
document.getElementById('project-dropdown').classList.toggle('open');
|
||||
};
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
const dd = document.getElementById('project-dropdown');
|
||||
if (!dd.contains(e.target)) {
|
||||
dd.classList.remove('open');
|
||||
}
|
||||
});
|
||||
// Prevent dropdown menu clicks from closing
|
||||
document.getElementById('project-dropdown-menu').onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
// All / Clear buttons
|
||||
document.getElementById('select-all-projects').onclick = () => {
|
||||
selectAllProjects();
|
||||
};
|
||||
document.getElementById('clear-all-projects').onclick = () => {
|
||||
clearAllProjects();
|
||||
};
|
||||
|
||||
// Velocity Statistics Toggle
|
||||
|
|
@ -1244,8 +1466,8 @@
|
|||
let timesToStart = [];
|
||||
let weeklyCompletions = [0, 0, 0, 0]; // Last 4 weeks
|
||||
|
||||
const filteredTasks = selectedProject
|
||||
? tasks.filter(t => t.projectName === selectedProject)
|
||||
const filteredTasks = selectedProjects !== null
|
||||
? tasks.filter(t => selectedProjects.has(t.projectName))
|
||||
: tasks;
|
||||
|
||||
filteredTasks.forEach(task => {
|
||||
|
|
@ -1370,8 +1592,8 @@
|
|||
container.innerHTML = '';
|
||||
|
||||
// Filter tasks - show ALL tasks, not just those with dependencies
|
||||
let filteredTasks = selectedProject
|
||||
? tasks.filter(t => t.projectName === selectedProject)
|
||||
let filteredTasks = selectedProjects !== null
|
||||
? tasks.filter(t => selectedProjects.has(t.projectName))
|
||||
: tasks;
|
||||
|
||||
if (!showCompleted) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue