diff --git a/projects/msp-tools/bug-tracker/dashboard.html b/projects/msp-tools/bug-tracker/dashboard.html index 99c3d88f..33e3eeba 100644 --- a/projects/msp-tools/bug-tracker/dashboard.html +++ b/projects/msp-tools/bug-tracker/dashboard.html @@ -200,7 +200,20 @@ const SECTIONS = [ { id: 'gc-all', label: 'GuruConnect', repos: ['guru-connect'], filter: () => true }, { id: 'ct-bugs', label: 'Harness / Skill Bugs', repos: ['claudetools'], filter: i => getType(i)==='bug' || getType(i)==='security' }, { id: 'ct-skills', label: 'Skill Work', repos: ['claudetools'], filter: i => getType(i)==='skill' }, - { id: 'ct-projects', label: 'Projects', repos: ['claudetools'], filter: i => getType(i)==='project' || getType(i)==='feature' }, + { id: 'ct-projects', label: 'Projects', repos: ['claudetools'], filter: i => (getType(i)==='project' || getType(i)==='feature') && !isClientProject(i) }, +]; +// Client project sections — built dynamically from labels +const CLIENT_SECTIONS = [ + { id: 'cp-all', label: 'All Client Projects', client: null }, + { id: 'cp-cascades', label: 'Cascades', client: 'client:cascades' }, + { id: 'cp-glaztech', label: 'GlazTech', client: 'client:glaztech' }, + { id: 'cp-sif', label: 'SIF / Oidak', client: 'client:sif-oidak' }, + { id: 'cp-goldstein', label: 'Goldstein', client: 'client:goldstein' }, + { id: 'cp-prairie', label: 'Prairie Schooner', client: 'client:prairie-schooner' }, + { id: 'cp-cryoweave', label: 'CryoWeave', client: 'client:cryoweave' }, + { id: 'cp-tedards', label: 'Tedards Law', client: 'client:tedards' }, + { id: 'cp-vwp', label: 'Valley Water Pumps', client: 'client:vwp' }, + { id: 'cp-acg', label: 'ACG Internal', client: 'client:acg' }, ]; const ALL_REPOS = ['gururmm','guru-rmm','guru-connect','claudetools']; const REPO_NAMES = { 'gururmm':'GuruRMM', 'guru-rmm':'GuruRMM', 'guru-connect':'GuruConnect', 'claudetools':'ClaudeTools' }; @@ -288,6 +301,15 @@ function getType(i) { for(const t of ['bug','feature','skill','project','security']) if(i.labels.some(l=>l.name===t)) return t; return null; } +function isClientProject(i) { return i.labels.some(l => l.name === 'client-project'); } +function getAssignee(i) { + const a = i.labels.find(l => l.name.startsWith('assigned:')); + return a ? a.name.split(':')[1] : null; +} +function getClient(i) { + const c = i.labels.find(l => l.name.startsWith('client:')); + return c ? c.name : null; +} function getDiscovered(i) { // Parse "**Discovered:** YYYY-MM-DD" from issue body if (!i.body) return null; @@ -306,9 +328,19 @@ function tagStyle(l) { return `background:#${l.color}20;color:#${l.color};`; } function getFiltered() { let items = allIssues; - if (currentSection !== 'all' && currentSection !== 'activity') { + if (currentSection.startsWith('cp-')) { + // Client project section + const cs = CLIENT_SECTIONS.find(s=>s.id===currentSection); + if (cs) { + items = items.filter(i => isClientProject(i)); + if (cs.client) items = items.filter(i => i.labels.some(l => l.name === cs.client)); + } + } else if (currentSection === 'all') { + // Exclude client projects from "all issues" + items = items.filter(i => !isClientProject(i)); + } else if (currentSection !== 'activity') { const sec = SECTIONS.find(s=>s.id===currentSection); - if (sec) items = items.filter(i => sec.repos.includes(i._repo) && sec.filter(i)); + if (sec) items = items.filter(i => sec.repos.includes(i._repo) && sec.filter(i) && !isClientProject(i)); } if (searchText) { const q = searchText.toLowerCase(); @@ -355,6 +387,27 @@ function renderSidebar() { ${sec.label} ${countFor(sec)} `; } + + // Client projects section + const cpCount = (cs) => { + let items = allIssues.filter(i => i.state !== 'closed' && isClientProject(i)); + if (cs && cs.client) items = items.filter(i => i.labels.some(l => l.name === cs.client)); + return items.length; + }; + const totalCp = cpCount(null); + html += `
`; + html += ``; + for (const cs of CLIENT_SECTIONS.filter(s=>s.client)) { + const cnt = cpCount(cs); + if (cnt > 0) { + html += ``; + } + } + sb.innerHTML = html; } @@ -407,11 +460,14 @@ function renderStats(items) { function makeCard(i) { const p=getPriority(i); const st=getStatus(i); - const tags=i.labels.filter(l=>!l.name.match(/^P\d/)&&!['in-progress','blocked','dropped','fixed','verified','wontfix'].includes(l.name)); + const tags=i.labels.filter(l=>!l.name.match(/^P\d/)&&!['in-progress','blocked','dropped','fixed','verified','wontfix','client-project'].includes(l.name)&&!l.name.startsWith('assigned:')&&!l.name.startsWith('client:')); + const assignee=getAssignee(i); + const isCp=isClientProject(i); + const clientLabel=getClient(i); return `