create-pmos 0.179.0 → 0.180.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-report-setup-test.js +190 -0
- package/workspace/c3-reports-test.js +30 -97
- package/workspace/guide/document-sheet.md +14 -0
- package/workspace/guide/index.md +3 -2
- package/workspace/guide/reports-dashboards.md +17 -28
- package/workspace/guide/reports-page.md +10 -5
- package/workspace/guide/reports-set-up.md +56 -0
- package/workspace/guide/reports.md +13 -1
- package/workspace/guide/settings.md +11 -2
- package/workspace/lib/doc-sync.js +28 -6
- package/workspace/lib/docs-cli.js +7 -11
- package/workspace/lib/four-panel.js +1 -1
- package/workspace/lib/headless.js +21 -2
- package/workspace/lib/report-checks.js +103 -0
- package/workspace/lib/report-cli.js +89 -4
- package/workspace/lib/report-export.js +347 -0
- package/workspace/lib/report-finder.js +156 -0
- package/workspace/lib/report-needs.js +1 -1
- package/workspace/lib/report-setups.js +743 -0
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-c3-reports.js +35 -17
- package/workspace/lib/routes-doc-health.js +15 -1
- package/workspace/lib/routes-report-setups.js +368 -0
- package/workspace/lib/schedule-logic.js +5 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-demo-rep2.js +15 -0
- package/workspace/public/pm/c3-demo-rs.js +281 -0
- package/workspace/public/pm/c3-demo.js +1 -1
- package/workspace/public/pm/c3-doc-formats.js +152 -0
- package/workspace/public/pm/c3-doc-sheet.js +115 -25
- package/workspace/public/pm/c3-quiet.js +2 -2
- package/workspace/public/pm/c3-report-print.css +20 -0
- package/workspace/public/pm/c3-report-setup.css +62 -0
- package/workspace/public/pm/c3-report-setup.js +260 -0
- package/workspace/public/pm/c3-report-store.js +208 -0
- package/workspace/public/pm/c3-reports.css +4 -0
- package/workspace/public/pm/c3-reports.js +47 -181
- package/workspace/public/pm/c3-settings.js +4 -3
- package/workspace/public/pm/c3-shell.js +15 -31
- package/workspace/server.js +66 -5
- package/workspace/skills/doc-sync.skill +17 -8
- package/workspace/skills/report-find.skill +75 -0
- package/workspace/skills/report-setup.skill +67 -0
- package/workspace/test-files.json +10 -0
- package/workspace/test-lists/report-workspace-ui.json +5 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Flow reference: the restructure mockup's 33-reports.js (dashboards with scope buttons, edit mode, library, share sheet). Not a pixel spec.
|
|
3
3
|
*
|
|
4
4
|
* mountReports(host, ctx) -> { dispose() }
|
|
5
|
-
* ctx = { ws, pmGet?, deck?(hostEl),
|
|
5
|
+
* ctx = { ws, pmGet?, deck?(hostEl), goInit?(id, title) }
|
|
6
6
|
* ws the workspace id; every read names it
|
|
7
7
|
* pmGet the shell's GET helper (default: http.js pmGet)
|
|
8
8
|
* deck mounts the page's headline deck into hostEl, above the tab bar (the shell mounts its Plan deck for the Reports scope).
|
|
@@ -11,21 +11,12 @@
|
|
|
11
11
|
* first time the Drafts tab opens. Without it the Drafts tab says the drafts are not available on this page.
|
|
12
12
|
* goInit opens an initiative from a row inside a report (a bet, a commitment). Default: the Initiative dashboard of it.
|
|
13
13
|
*
|
|
14
|
-
* Three tabs: Dashboards |
|
|
15
|
-
* Dashboards scope buttons Portfolio | Board | Initiative, a chooser
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* /api/report-dashboards/layout { key, reports } (reports: null resets). A failed POST is toasted; the page keeps the change.
|
|
21
|
-
* Share: a sheet (c3-sheet.js) with the report frame (Headline, Sections, Needs your call, Sources) for Leadership or Team.
|
|
22
|
-
* "Make a draft now" queues ONE draft through POST /api/outbox/draft. Nothing is sent. Schedule: Not scheduled, weekly or monthly,
|
|
23
|
-
* for the sheet's audience, saved by POST /api/report-dashboards/schedule { key, plan, audience, ws }. A run makes a ready draft
|
|
24
|
-
* and a notice on the bell (lib/report-schedules.js); nothing is sent.
|
|
25
|
-
* Library one row per report (data-home): the title, the question, the source and where it is used. The row opens the card in place.
|
|
26
|
-
* Drafts the dashboard drafts the Share sheet made (GET /api/outbox, subject "<dashboard> for <audience>"; Preview, Discard with Undo through
|
|
27
|
-
* POST /api/outbox/discard; "Made by schedule" or "Made by you"), Scheduled (each schedule and its next draft), ctx.more(host), then "Reports to send": ctx.drafts(host), the old
|
|
28
|
-
* report families, their claims check and the outbox queue.
|
|
14
|
+
* Three tabs, one rule each: Dashboards (live) | Reports (sent) | Charts (building blocks). Old ?tab=lib and ?tab=drafts land on Charts and Reports.
|
|
15
|
+
* Dashboards scope buttons Portfolio | Board | Initiative, a chooser, then one widget per chart of the layout (or the scope's default). A widget is the
|
|
16
|
+
* Calendar's own builder, drawn from GET /api/report-dashboards/data. Edit: arrows, Remove (with Undo), Add from Charts, Reset.
|
|
17
|
+
* "Send this as a report..." opens the set-up sheet (c3-report-setup.js openSendSheet). Nothing is sent.
|
|
18
|
+
* Reports c3-report-setup.js: Ask PMOS, Found in your work, Set up, Documents.
|
|
19
|
+
* Charts one row per chart (data-home): title, question, source, and the dashboards that use it. The row opens the card in place.
|
|
29
20
|
* DATA: GET /api/report-dashboards?ws= -> { library, layouts, defaults, boards, initiatives, schedules }. In demo mode c3-demo-rep3.js answers both reads
|
|
30
21
|
* and the layout and schedule POSTs. Nothing here invents a number: a report with no rows in the scope draws its builder's own empty card. */
|
|
31
22
|
import { el } from './dom.js';
|
|
@@ -36,10 +27,13 @@ import { PANEL_BUILDERS, todayStr } from './c3-cal-strategy.js';
|
|
|
36
27
|
import { makeXp, attachExpand } from './c3-expand.js';
|
|
37
28
|
import { openSheet } from './c3-sheet.js';
|
|
38
29
|
import { toast } from './c3-toast.js';
|
|
30
|
+
import { mountReportSetup, openSendSheet } from './c3-report-setup.js';
|
|
31
|
+
import { RS, loadReports, onReports, draftsWaiting } from './c3-report-store.js';
|
|
39
32
|
import '/lib/report-frame.js'; // sets self.ReportFrame (lib/report-frame.js, shared with the scheduled run)
|
|
40
33
|
|
|
41
34
|
const SCOPES = [['portfolio', 'Portfolio'], ['board', 'Board'], ['initiative', 'Initiative']];
|
|
42
|
-
const TABS = [['dash', 'Dashboards', 'report'], ['
|
|
35
|
+
const TABS = [['dash', 'Dashboards', 'report'], ['reports', 'Reports', 'doc'], ['charts', 'Charts', 'cols']];
|
|
36
|
+
const TAB_OF = { lib: 'charts', library: 'charts', drafts: 'reports', rs: 'reports' }; // the old tab names keep working
|
|
43
37
|
const ICON = { north: 'target', outcomes: 'target', signals: 'alert', delivery: 'clock', maturity: 'flag', bets: 'flag', synergy: 'init' };
|
|
44
38
|
const TTL = 60000;
|
|
45
39
|
|
|
@@ -115,39 +109,24 @@ function trimHead(card) {
|
|
|
115
109
|
if (line) h.replaceChildren(line); else h.remove();
|
|
116
110
|
return card;
|
|
117
111
|
}
|
|
118
|
-
|
|
119
|
-
const dateOf = (iso) => { const d = new Date(iso); return isNaN(d) ? '' : DAY[d.getUTCDay()] + ' ' + d.getUTCDate() + ' ' + MON[d.getUTCMonth()]; };
|
|
120
|
-
/* A dashboard draft is an outbox entry the Share sheet made: its subject is "<dashboard> for <audience>". */
|
|
121
|
-
const DRAFT_RE = /^(.*) for (Leadership|Team)$/;
|
|
122
|
-
/* A schedule per dashboard (lib/report-schedules.js PLANS): it makes a ready draft and a notice on the bell. Nothing is sent. */
|
|
123
|
-
const SCHEDULES = [['none', 'Not scheduled'], ['weekly', 'Weekly, Mondays 08:00'], ['monthly', 'Monthly, 1st at 08:00']];
|
|
124
|
-
const SCHED_TXT = Object.fromEntries(SCHEDULES);
|
|
125
|
-
/* The next slot, host-local, as lib/schedule-logic.js nextSlot reads PLANS: Mondays 08:00, or the 1st at 08:00. */
|
|
126
|
-
export function nextDraftOf(plan, now = new Date()) {
|
|
127
|
-
const d = new Date(now); d.setHours(8, 0, 0, 0);
|
|
128
|
-
if (plan === 'weekly') { for (let i = 0; i < 8; i++) { if (d.getDay() === 1 && d > now) return d; d.setDate(d.getDate() + 1); } return null; }
|
|
129
|
-
if (plan === 'monthly') { d.setDate(1); if (d <= now) d.setMonth(d.getMonth() + 1); return d; }
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
const localDate = (d) => (d ? DAY[d.getDay()] + ' ' + d.getDate() + ' ' + MON[d.getMonth()] + ', 08:00' : '');
|
|
133
|
-
|
|
134
|
-
/* The live page, so the shell can open one of its tabs (a bell notice opens Drafts). A request before the page mounts waits for it. */
|
|
112
|
+
/* The live page, so the shell can open one of its tabs. A request before the page mounts waits for it. Old names (lib, drafts) map to the new tabs. */
|
|
135
113
|
let livePage = null; let wantView = null;
|
|
136
|
-
export function showReportsView(view) { if (livePage) livePage.showTab(
|
|
114
|
+
export function showReportsView(view) { const v = TAB_OF[view] || view; if (livePage) livePage.showTab(v); else wantView = v; }
|
|
137
115
|
|
|
138
116
|
export function mountReports(host, ctx) {
|
|
139
117
|
ensureC3Css(); ensureComponentsCss(); ensureReportsCss();
|
|
140
118
|
const C = ctx || {};
|
|
141
119
|
const get = C.pmGet || httpGet;
|
|
142
|
-
const S = {
|
|
120
|
+
const S = { tab: 'dash', scope: 'portfolio', pick: { board: '', initiative: '' }, meta: null, metaErr: null, layouts: {}, edit: false, alive: true, libOpen: new Set(), setup: null };
|
|
143
121
|
const root = el('div', { class: 'rp3 calwrap', 'data-rp3': '' });
|
|
144
122
|
const stopPhone = host.closest && host.closest('.c3') ? () => {} : c3Phone(root);
|
|
145
123
|
if (!(host.closest && host.closest('.c3'))) root.classList.add('c3');
|
|
146
124
|
const tabBar = el('div', { class: 'seg rp3-tabs', role: 'tablist', 'aria-label': 'Reports' });
|
|
147
125
|
const scopeHost = el('div', { class: 'chips2 rp3-scope', role: 'group', 'aria-label': 'Scope' });
|
|
148
|
-
const panes = { dash: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'dash' }),
|
|
126
|
+
const panes = { dash: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'dash' }), reports: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'reports', hidden: true }), charts: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'charts', hidden: true }) };
|
|
127
|
+
const ruleHost = el('div', { class: 'chips2 rp3-rule', role: 'note', 'aria-label': 'How Reports is organised', 'data-rp3-rule': '' });
|
|
149
128
|
const deckHost = el('div', { 'data-rp3-deck': '' });
|
|
150
|
-
root.append(deckHost, el('div', { class: 'calbar' }, tabBar, scopeHost), panes.dash, panes.
|
|
129
|
+
root.append(deckHost, el('div', { class: 'calbar' }, tabBar, scopeHost), ruleHost, panes.dash, panes.reports, panes.charts);
|
|
151
130
|
host.replaceChildren(root);
|
|
152
131
|
if (typeof C.deck === 'function') C.deck(deckHost); else deckHost.hidden = true;
|
|
153
132
|
|
|
@@ -163,8 +142,8 @@ export function mountReports(host, ctx) {
|
|
|
163
142
|
const defaultOf = (scope) => known(S.meta && S.meta.defaults && S.meta.defaults[scope]);
|
|
164
143
|
const hasOwn = (scope, id) => Object.prototype.hasOwnProperty.call(S.layouts, keyOf(scope, id));
|
|
165
144
|
const layoutOf = (scope, id) => (hasOwn(scope, id) ? known(S.layouts[keyOf(scope, id)]) : defaultOf(scope));
|
|
166
|
-
|
|
167
|
-
|
|
145
|
+
/* the set-ups name a dashboard by its key (portfolio, board:shop); the pages of Reports say its title */
|
|
146
|
+
RS.names = (k) => (({ portfolio: 'Portfolio' })[k] || nameOf(k.split(':')[0], k.split(':').slice(1).join(':')));
|
|
168
147
|
const goInit = (id, title) => {
|
|
169
148
|
if (C.goInit) { C.goInit(id, title); return; }
|
|
170
149
|
if (!listOf('initiative').some((x) => x.id === id)) { toast('No dashboard is kept for ' + (title || id) + '.'); return; }
|
|
@@ -172,17 +151,23 @@ export function mountReports(host, ctx) {
|
|
|
172
151
|
};
|
|
173
152
|
|
|
174
153
|
/* tabs */
|
|
154
|
+
const badgeOf = (id) => (id === 'dash' ? SCOPES.length : id === 'charts' ? (S.meta ? lib().length : null) : (RS.loaded ? draftsWaiting(RS.docs) : null));
|
|
175
155
|
function drawTabs() {
|
|
176
|
-
tabBar.replaceChildren(...TABS.map(([id, label, icon]) => pill({ type: 'button', role: 'tab', 'aria-selected': String(S.tab === id), 'aria-pressed': String(S.tab === id), tabindex: S.tab === id ? '0' : '-1', 'data-rp3-tab': id, onclick: () => showTab(id) }, c3ic(icon, 'sm'), label,
|
|
177
|
-
|
|
156
|
+
tabBar.replaceChildren(...TABS.map(([id, label, icon]) => { const n = badgeOf(id); return pill({ type: 'button', role: 'tab', 'aria-selected': String(S.tab === id), 'aria-pressed': String(S.tab === id), tabindex: S.tab === id ? '0' : '-1', 'data-rp3-tab': id, onclick: () => showTab(id) }, c3ic(icon, 'sm'), label,
|
|
157
|
+
n ? el('span', { class: 'bdg g', 'data-rp3-badge': id, title: id === 'reports' ? 'Drafts waiting for you' : '' }, String(n)) : null); }));
|
|
178
158
|
scopeHost.hidden = S.tab !== 'dash';
|
|
159
|
+
ruleHost.replaceChildren(el('span', { class: 'dim mono rp3-rulelbl' }, 'HOW REPORTS IS ORGANISED'),
|
|
160
|
+
...[['dash', 'report', 'Live \u2192 Dashboards'], ['reports', 'doc', 'Sent \u2192 Reports'], ['charts', 'cols', 'Building blocks \u2192 Charts']].map(([id, ic, t]) => pill({ type: 'button', class: 'chip2 lk', 'data-rp3-rulego': id, onclick: () => showTab(id) }, c3ic(ic, 'sm'), t)));
|
|
179
161
|
}
|
|
180
162
|
function showTab(id) {
|
|
181
|
-
S.tab = id; drawTabs();
|
|
163
|
+
id = TAB_OF[id] || id; S.tab = id; drawTabs();
|
|
182
164
|
for (const k of Object.keys(panes)) panes[k].hidden = k !== id;
|
|
183
|
-
if (id === '
|
|
184
|
-
if (id === '
|
|
165
|
+
if (id === 'charts') drawLib();
|
|
166
|
+
if (id === 'reports') drawReportsTab();
|
|
185
167
|
}
|
|
168
|
+
function drawReportsTab() { if (!S.setup) S.setup = mountReportSetup(panes.reports, { ws: C.ws }); }
|
|
169
|
+
const offReports = onReports(() => { if (S.alive) drawTabs(); });
|
|
170
|
+
loadReports(C.ws);
|
|
186
171
|
|
|
187
172
|
/* ── Dashboards ── */
|
|
188
173
|
/* The scope pills sit on the tab row (the mockup's calbar); the pill names the picked board or initiative. */
|
|
@@ -240,14 +225,14 @@ export function mountReports(host, ctx) {
|
|
|
240
225
|
const own = !noScope && hasOwn(S.scope, id);
|
|
241
226
|
root.classList.toggle('editing', S.edit);
|
|
242
227
|
drawScope(); // again: the pick above may have changed the pill's name
|
|
243
|
-
/* the mockup's .lh: report icon, bold title, one dim line (scope, count
|
|
228
|
+
/* the mockup's .lh: report icon, bold title, one dim line (scope, count), then Send this as a report and Edit dashboard */
|
|
244
229
|
const head = el('div', { class: 'lh rp3-head' }, el('span', { class: 'ki' }, c3ic('report')),
|
|
245
230
|
el('div', { class: 'rp3-min0' }, el('b', { 'data-rp3-title': '' }, noScope ? (S.scope === 'board' ? 'Board' : 'Initiative') : dashTitle()),
|
|
246
231
|
el('div', { class: 'dim' }, noScope ? '' : [el('span', { 'data-rp3-count': String(ids.length) }, plural(ids.length, 'report', 'reports') + (own ? ', your layout' : ', the default layout')),
|
|
247
|
-
el('span',
|
|
232
|
+
el('span', null, ' · Live. Changes as your work changes.')]),
|
|
248
233
|
S.scope !== 'portfolio' ? chooser() : null),
|
|
249
234
|
noScope ? null : el('span', { class: 'chips2' },
|
|
250
|
-
btnV('outline', { 'data-rp3-
|
|
235
|
+
btnV('outline', { 'data-rp3-send': '', onclick: () => openSendSheet({ ws: C.ws, key: keyOf(S.scope, id), title: dashTitle(), charts: ids.length, onDone: () => { loadReports(C.ws, true); showTab('reports'); } }) }, c3ic('mail', 'sm'), 'Send this as a report\u2026'),
|
|
251
236
|
S.edit ? btnV('ghost', { 'data-rp3-reset': '', disabled: !own, title: own ? 'Return this scope to its default layout' : 'This scope shows its default layout', onclick: () => resetLayout() }, c3ic('undo', 'sm'), 'Reset to default') : null,
|
|
252
237
|
btnV(S.edit ? 'primary' : 'outline', { 'data-rp3-edit': '', 'aria-pressed': String(S.edit), onclick: () => { S.edit = !S.edit; drawDash(); } }, c3ic(S.edit ? 'check' : 'edit', 'sm'), S.edit ? 'Done editing' : 'Edit dashboard')));
|
|
253
238
|
const kids = [head];
|
|
@@ -286,7 +271,7 @@ export function mountReports(host, ctx) {
|
|
|
286
271
|
async function save(scope, id, reports) {
|
|
287
272
|
const key = keyOf(scope, id);
|
|
288
273
|
if (reports === null) delete S.layouts[key]; else S.layouts[key] = reports.slice();
|
|
289
|
-
drawDash(); if (S.tab === '
|
|
274
|
+
drawDash(); if (S.tab === 'charts') drawLib();
|
|
290
275
|
const r = await pmPost('/api/report-dashboards/layout', { key, reports });
|
|
291
276
|
if (!r || !r.ok) toast({ text: 'The layout was not saved: ' + ((r && (r.error || r.reason)) || 'no answer') + '. This page keeps it until you reload.', tone: 'error', timeout: 6000 });
|
|
292
277
|
return r;
|
|
@@ -311,70 +296,7 @@ export function mountReports(host, ctx) {
|
|
|
311
296
|
}
|
|
312
297
|
function resetLayout() { save(S.scope, curId(), null); toast('This dashboard is back to its default layout.'); }
|
|
313
298
|
|
|
314
|
-
/* ──
|
|
315
|
-
function share(ids) {
|
|
316
|
-
const scope = S.scope; const id = curId(); const title = dashTitle(); const reps = ids.map(repOf).filter(Boolean);
|
|
317
|
-
const cur = schedOf(scope, id);
|
|
318
|
-
const st = { aud: cur ? cur.audience : 'Leadership', saved: {}, busy: false, msg: '', sched: cur ? cur.plan : 'none', saving: false };
|
|
319
|
-
const body = el('div', { class: 'rp3-share', 'data-rp3-sheet': '' });
|
|
320
|
-
const paint = (raw) => {
|
|
321
|
-
const today = (raw && raw.today) || todayStr(Date.now());
|
|
322
|
-
const F = frameOf({ title, reports: reps, raw: raw && raw.ok ? raw : {}, audience: st.aud, today });
|
|
323
|
-
/* the frame in the mockup's cards: a .lh head, then Headline, Sections, Needs your call and Sources as section cards */
|
|
324
|
-
const sec = (name, icon, count, ...kids) => sect(name, icon, count, kids, null, { 'data-rp3-frame': name });
|
|
325
|
-
const frame = el('div', { class: 'rp3-frame', 'data-rp3-aud': st.aud },
|
|
326
|
-
el('div', { class: 'lh' }, el('span', { class: 'ki' }, c3ic('mail')), el('div', { class: 'rp3-min0' }, el('b', null, title), el('div', { class: 'dim' }, 'For ' + st.aud + ' · ' + today)), el('span', { class: 'chip2' }, 'Draft')),
|
|
327
|
-
sec('Headline', null, null, el('div', { class: 'hero' }, el('b', { class: 'rp3-hl' }, F.headline))),
|
|
328
|
-
sec('Sections', null, F.sections.length, ...(F.sections.length ? [el('ul', { class: 'chgl' }, F.sections.map((s) => el('li', null, c3ic('report', 'sm'),
|
|
329
|
-
el('span', null, el('b', null, s.title), el('br'), s.line, s.nums.length ? el('ul', { class: 'rp3-nums', 'data-rp3-nums': s.id }, s.nums.map((t) => el('li', null, t))) : null), el('span', null))))] : [el('div', { class: 'fold' }, 'Nothing to show.')])),
|
|
330
|
-
sec('Needs your call', null, F.needs.length, ...(F.needs.length ? [el('ul', { class: 'chgl' }, F.needs.map((t) => el('li', { class: 'warn' }, c3ic('flag', 'sm'), el('span', null, t), el('span', null))))] : [el('div', { class: 'fold' }, 'Nothing needs your call in this scope.')])),
|
|
331
|
-
sec('Sources', null, null, el('div', { class: 'chips' }, F.sources.map((x) => el('span', { class: 'chip2' }, x))), el('div', { class: 'dim rp3-sub' }, 'Read from the repository on ' + today + '.')));
|
|
332
|
-
const saved = st.saved[st.aud];
|
|
333
|
-
const saveBtn = btnV('outline', { 'data-rp3-draft': '', disabled: st.busy || !!saved || !raw || !raw.ok,
|
|
334
|
-
onclick: async () => {
|
|
335
|
-
st.busy = true; paint(raw);
|
|
336
|
-
const r = await pmPost('/api/outbox/draft', { channel: 'customer-note', recipient: st.aud, subject: title + ' for ' + st.aud, body: draftBody(F) });
|
|
337
|
-
st.busy = false;
|
|
338
|
-
if (r && r.ok) {
|
|
339
|
-
st.saved[st.aud] = true;
|
|
340
|
-
st.msg = r.entry && r.entry.state === 'blocked' ? 'Saved in the outbox, but the claims check blocks it: ' + ((r.entry.claims && r.entry.claims.refused || []).join(' ') || 'a claim has no label') + '. Nothing was sent.'
|
|
341
|
-
: 'Saved as one draft in the outbox. Nothing was sent: no channel is connected.';
|
|
342
|
-
} else st.msg = 'The draft was not saved: ' + ((r && (r.error || r.reason)) || 'no answer') + '. Nothing was sent.';
|
|
343
|
-
toast(st.msg); paint(raw); loadOutbox();
|
|
344
|
-
} }, c3ic('mail', 'sm'), saved ? 'Saved as draft' : st.busy ? 'Saving' : 'Make a draft now');
|
|
345
|
-
/* Schedule: one per dashboard. A run makes a ready draft in the outbox and a notice on the bell; nothing is sent (lib/report-schedules.js). */
|
|
346
|
-
const next = nextDraftOf(st.sched);
|
|
347
|
-
const saveSched = async () => {
|
|
348
|
-
st.saving = true; paint(raw);
|
|
349
|
-
const r = await pmPost('/api/report-dashboards/schedule', { key: keyOf(scope, id), plan: st.sched, audience: st.aud, ws: C.ws || null });
|
|
350
|
-
st.saving = false;
|
|
351
|
-
if (r && r.ok) {
|
|
352
|
-
S.schedules = r.schedules || {};
|
|
353
|
-
st.msg = st.sched === 'none' ? 'Schedule removed' : 'Scheduled: ' + SCHED_TXT[st.sched] + '. You get a draft and a notification.';
|
|
354
|
-
toast(st.msg); drawDash(); if (S.tab === 'drafts') drawDrafts();
|
|
355
|
-
} else { st.msg = 'The schedule was not saved: ' + ((r && (r.error || r.reason)) || 'no answer') + '.'; toast({ text: st.msg, tone: 'error', timeout: 6000 }); }
|
|
356
|
-
paint(raw);
|
|
357
|
-
};
|
|
358
|
-
const schedule = sect('Schedule', 'cal', null, [
|
|
359
|
-
el('div', { class: 'seg', role: 'group', 'aria-label': 'Send', 'data-rp3-sched': '' }, SCHEDULES.map(([k, label]) => pill({ type: 'button', 'aria-pressed': String(k === st.sched), 'data-sched': k, onclick: () => { st.sched = k; st.msg = ''; paint(raw); } }, label))),
|
|
360
|
-
next ? el('div', { class: 'dim rp3-sub', 'data-rp3-next': '' }, 'Next draft: ' + localDate(next)) : null,
|
|
361
|
-
el('div', { class: 'fold w rp3-why', id: 'rp3-sched-why' }, c3ic('alert', 'sm'), 'No sender channel is connected yet. A scheduled send makes a ready draft and notifies you. Nothing is sent to ' + (st.aud === 'Leadership' ? 'leadership' : 'the team') + ' until you connect a channel.'),
|
|
362
|
-
el('div', { class: 'chips2' }, btnV('primary', { 'data-rp3-sched-save': '', disabled: st.saving, onclick: saveSched }, st.saving ? 'Saving' : 'Save schedule'), saveBtn),
|
|
363
|
-
el('p', { class: 'dim rp3-sub rp3-flat', role: 'status', 'data-rp3-share-msg': '' }, st.msg || 'Make a draft now puts one draft in the outbox. Nothing is sent from this page.')],
|
|
364
|
-
null, { 'data-rp3-schedule': '', class: 'rp3-stack' });
|
|
365
|
-
body.replaceChildren(el('div', { class: 'calbody brief' },
|
|
366
|
-
el('div', { class: 'calbar' }, el('div', { class: 'seg', role: 'group', 'aria-label': 'Audience' }, ['Leadership', 'Team'].map((a) => pill({ type: 'button', 'aria-pressed': String(st.aud === a), 'data-rp3-audbtn': a, onclick: () => { st.aud = a; st.msg = ''; paint(raw); } }, a))),
|
|
367
|
-
el('span', { class: 'dim rp3-sub' }, st.aud === 'Leadership' ? 'Short: one sentence per report, and what needs your call.' : 'Fuller: adds the numbers behind each sentence.')),
|
|
368
|
-
raw ? (raw.ok ? frame : el('div', { class: 'fold', role: 'status' }, c3ic('alert', 'sm'), 'The reports could not be read: ' + raw.error)) : el('p', { class: 'dim mono', role: 'status' }, 'Reading the repository'),
|
|
369
|
-
schedule));
|
|
370
|
-
};
|
|
371
|
-
const cached = cachedData(scope, id);
|
|
372
|
-
openSheet({ title: 'Share: ' + title, icon: 'out', wide: true, body });
|
|
373
|
-
paint(cached);
|
|
374
|
-
if (!cached) readData(get, C.ws, scope, id).then((m) => { if (body.isConnected) paint(m); });
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/* ── Library: the home of every report ── */
|
|
299
|
+
/* ── Charts: the home of every chart ── */
|
|
378
300
|
function usedOn(rid, short) {
|
|
379
301
|
if (!S.meta) return 'nowhere yet';
|
|
380
302
|
if (short) { // the mockup's chip: the scopes it is on, by short name
|
|
@@ -389,8 +311,10 @@ export function mountReports(host, ctx) {
|
|
|
389
311
|
}
|
|
390
312
|
return on.length ? on.join(', ') : 'no dashboard';
|
|
391
313
|
}
|
|
314
|
+
/* a set-up built from a dashboard uses the charts of that dashboard; the contract names the dashboard key, not its charts, so the match is by key */
|
|
315
|
+
const reportsUsing = (r) => { const names = RS.setups.filter((x) => String(x.source || '').startsWith('dashboard:') && layoutOf(String(x.source).slice(10).split(':')[0], String(x.source).slice(10).split(':').slice(1).join(':')).includes(r.id)).map((x) => x.name); return names.length ? names.join(', ') : 'none'; };
|
|
392
316
|
function drawLib() {
|
|
393
|
-
const P = panes.
|
|
317
|
+
const P = panes.charts;
|
|
394
318
|
if (S.metaErr) { P.replaceChildren(el('div', { class: 'fold', role: 'status' }, c3ic('alert', 'sm'), 'The library could not be read: ' + S.metaErr)); return; }
|
|
395
319
|
if (!S.meta) { P.replaceChildren(el('p', { class: 'dim mono rp3-status', role: 'status' }, 'Reading the repository')); return; }
|
|
396
320
|
const rows = lib().map((r) => {
|
|
@@ -399,7 +323,7 @@ export function mountReports(host, ctx) {
|
|
|
399
323
|
const row = el('li', { class: 'nrow rp3-librow', 'data-home': r.id },
|
|
400
324
|
el('span', { class: 'ki hot' }, c3ic(ICON[builderOf(r)] || 'report')),
|
|
401
325
|
el('div', { class: 'nb' }, el('span', { class: 'tt' }, r.title), el('div', { class: 'dim' }, r.question || ''),
|
|
402
|
-
el('div', { class: 'chips' }, el('span', { class: 'chip2' }, r.source || 'no source named'), el('span', { class: 'chip2', 'data-rp3-used': r.id }, '
|
|
326
|
+
el('div', { class: 'chips' }, el('span', { class: 'chip2' }, r.source || 'no source named'), el('span', { class: 'chip2', 'data-rp3-used': r.id }, c3ic('report', 'sm'), 'Dashboards: ' + usedOn(r.id, true).replace('no dashboard', 'none')), el('span', { class: 'chip2', 'data-rp3-used-reports': r.id }, c3ic('doc', 'sm'), 'Reports: ' + reportsUsing(r))), xp));
|
|
403
327
|
const fill = () => {
|
|
404
328
|
const m = cachedData('portfolio', '');
|
|
405
329
|
if (m) { card.replaceChildren(m.ok ? drawReport(r, m, { goInit }) : el('p', { class: 'dim mono' }, 'This report could not be read: ' + m.error)); return; }
|
|
@@ -410,82 +334,24 @@ export function mountReports(host, ctx) {
|
|
|
410
334
|
if (S.libOpen.has(r.id)) api.open();
|
|
411
335
|
return row;
|
|
412
336
|
});
|
|
413
|
-
P.replaceChildren(el('div', { class: 'calbody brief' }, el('div', { class: 'fold rp3-note' }, c3ic('
|
|
414
|
-
sect('
|
|
337
|
+
P.replaceChildren(el('div', { class: 'calbody brief' }, el('div', { class: 'fold rp3-note' }, c3ic('cols', 'sm'), 'Each chart lives here once. Dashboards and reports point at it.'),
|
|
338
|
+
sect('Charts', 'cols', rows.length, el('ul', { class: 'list', role: 'list', 'data-rp3-lib': '' }, rows))));
|
|
415
339
|
}
|
|
416
340
|
function openInLibrary(rid) {
|
|
417
|
-
S.libOpen.add(rid); showTab('
|
|
418
|
-
const row = panes.
|
|
341
|
+
S.libOpen.add(rid); showTab('charts');
|
|
342
|
+
const row = panes.charts.querySelector('[data-home="' + rid + '"]');
|
|
419
343
|
if (row) { try { row.focus({ preventScroll: true }); row.scrollIntoView({ block: 'nearest' }); } catch { /* not focusable */ } }
|
|
420
344
|
}
|
|
421
345
|
|
|
422
|
-
/* ── Drafts: the dashboard drafts, what is scheduled (nothing), what the Briefing used to carry, and the old report families ── */
|
|
423
|
-
const dashDrafts = () => ((S.outbox || []).filter((e) => e.channel === 'customer-note' && DRAFT_RE.test(e.subject || '')));
|
|
424
|
-
function loadOutbox() {
|
|
425
|
-
return Promise.resolve(get('/api/outbox')).then((r) => {
|
|
426
|
-
if (!S.alive) return;
|
|
427
|
-
if (r && r.ok) { S.outbox = r.entries || []; S.outboxErr = null; } else S.outboxErr = (r && (r.error || r.reason)) || 'not available';
|
|
428
|
-
drawTabs(); if (S.tab === 'drafts') drawDrafts();
|
|
429
|
-
}, (e) => { S.outboxErr = String((e && e.message) || e); if (S.tab === 'drafts') drawDrafts(); });
|
|
430
|
-
}
|
|
431
|
-
const draftRows = () => dashDrafts().map((x) => {
|
|
432
|
-
const m = DRAFT_RE.exec(x.subject);
|
|
433
|
-
return el('li', { class: 'nrow', 'data-draft-row': x.id }, el('span', { class: 'ki' }, c3ic('mail')),
|
|
434
|
-
el('div', { class: 'nb' }, el('span', { class: 'tt' }, m[1]),
|
|
435
|
-
el('div', { class: 'chips' }, el('span', { class: 'chip2' }, 'Draft'), el('span', { class: 'chip2' }, c3ic('user', 'sm'), 'For ' + m[2]),
|
|
436
|
-
x.created_at ? el('span', { class: 'chip2' }, c3ic('cal', 'sm'), dateOf(x.created_at)) : null, el('span', { class: 'tag', 'data-rp3-madeby': x.made_by === 'schedule' ? 'schedule' : 'you' }, x.made_by === 'schedule' ? 'Made by schedule' : 'Made by you'), el('span', { class: 'tag' }, x.state === 'blocked' ? 'Blocked by the claims check' : 'Not sent'))),
|
|
437
|
-
el('div', { class: 'acts' }, btnV('outline', { 'data-draft': 'preview', 'data-id': x.id, onclick: () => previewDraft(x, m) }, c3ic('eye', 'sm'), 'Preview'),
|
|
438
|
-
btnV('ghost', { 'data-draft': 'discard', 'data-id': x.id, onclick: () => discardDraft(x) }, c3ic('x', 'sm'), 'Discard')));
|
|
439
|
-
});
|
|
440
|
-
function previewDraft(x, m) {
|
|
441
|
-
openSheet({ title: 'Draft: ' + m[1], icon: 'mail', wide: true, body: el('div', { class: 'calbody brief' },
|
|
442
|
-
el('div', { class: 'lh' }, el('span', { class: 'ki' }, c3ic('mail')), el('div', { class: 'rp3-min0' }, el('b', null, m[1]), el('div', { class: 'dim' }, 'For ' + m[2] + (x.created_at ? ' · ' + dateOf(x.created_at) : ''))), el('span', { class: 'chip2' }, 'Draft')),
|
|
443
|
-
sect('Text', 'doc', null, el('pre', { class: 'rp3-draft-text', 'data-rp3-draft-text': '' }, x.body || ''))) });
|
|
444
|
-
}
|
|
445
|
-
async function discardDraft(x) {
|
|
446
|
-
const r = await pmPost('/api/outbox/discard', { id: x.id });
|
|
447
|
-
if (!r || !r.ok) { toast({ text: 'The draft was not discarded: ' + ((r && (r.error || r.reason)) || 'no answer'), tone: 'error', timeout: 6000 }); return; }
|
|
448
|
-
S.outbox = (S.outbox || []).filter((e) => e.id !== x.id); drawTabs(); drawDrafts();
|
|
449
|
-
toast({ text: 'Draft discarded', actionLabel: 'Undo', timeout: 6000, onAction: async () => { await pmPost('/api/outbox/discard', { id: x.id, undo: true }); loadOutbox(); } });
|
|
450
|
-
}
|
|
451
|
-
function scheduledList() {
|
|
452
|
-
const L = Object.values(S.schedules || {});
|
|
453
|
-
if (!L.length) return el('div', { class: 'fold' }, c3ic('clock', 'sm'), 'No dashboard is scheduled. Share a dashboard to set one.');
|
|
454
|
-
return el('ul', { class: 'list', role: 'list' }, L.map((x) => el('li', { class: 'nrow', 'data-rp3-sched-row': x.key }, el('span', { class: 'ki' }, c3ic('cal')),
|
|
455
|
-
el('div', { class: 'nb' }, el('span', { class: 'tt' }, titleOfKey(x.key)),
|
|
456
|
-
el('div', { class: 'chips' }, el('span', { class: 'chip2' }, SCHED_TXT[x.plan] || x.plan), el('span', { class: 'chip2' }, c3ic('user', 'sm'), 'For ' + x.audience),
|
|
457
|
-
el('span', { class: 'chip2' }, 'Next draft ' + localDate(x.next_run ? new Date(x.next_run) : nextDraftOf(x.plan))),
|
|
458
|
-
x.last_status === 'failed' ? el('span', { class: 'tag', 'data-rp3-sched-failed': '' }, 'The last run made no draft: ' + (x.last_error || 'no reason recorded')) : null)),
|
|
459
|
-
el('span', null))));
|
|
460
|
-
}
|
|
461
|
-
function drawDrafts() {
|
|
462
|
-
const P = panes.drafts;
|
|
463
|
-
if (!S.draftsHost) {
|
|
464
|
-
S.draftsHost = { list: el('div', { 'data-rp3-draftlist': '', class: 'rp3-stack4' }), more: el('div', { 'data-rp3-more': '', class: 'rp3-stack4' }), send: el('div', { class: 'rp3-drafts', 'data-rp3-drafts': '' }) };
|
|
465
|
-
let sendBody;
|
|
466
|
-
if (typeof C.drafts === 'function') { sendBody = S.draftsHost.send; C.drafts(sendBody); } else sendBody = el('div', { class: 'fold' }, c3ic('info', 'sm'), 'The report drafts are not available on this page.');
|
|
467
|
-
if (typeof C.more === 'function') C.more(S.draftsHost.more); else S.draftsHost.more.hidden = true;
|
|
468
|
-
S.draftsHost.sendSect = sect('Reports to send', 'out', null, [el('div', { class: 'dim rp3-sub' }, 'The earlier report families, each with its claims check. Nothing is sent without your press.'), sendBody]);
|
|
469
|
-
P.replaceChildren(el('div', { class: 'calbody brief' }, S.draftsHost.list, S.draftsHost.more, S.draftsHost.sendSect));
|
|
470
|
-
}
|
|
471
|
-
const rows = draftRows();
|
|
472
|
-
S.draftsHost.list.replaceChildren(
|
|
473
|
-
el('div', { class: 'fold w' }, c3ic('alert', 'sm'), 'Nothing here is sent. No sender channel is connected yet, so a schedule only makes a ready draft and notifies you.'),
|
|
474
|
-
sect('Drafts', 'mail', S.outbox ? rows.length : null, S.outboxErr ? el('div', { class: 'fold', role: 'status' }, c3ic('alert', 'sm'), 'The drafts could not be read: ' + S.outboxErr)
|
|
475
|
-
: !S.outbox ? el('p', { class: 'dim mono rp3-status', role: 'status' }, 'Reading the repository')
|
|
476
|
-
: rows.length ? el('ul', { class: 'list', role: 'list', 'data-rp3-drafts-list': '' }, rows) : el('div', { class: 'fold' }, c3ic('check', 'sm'), 'No drafts. Share a dashboard to make one.')),
|
|
477
|
-
sect('Scheduled', 'cal', null, scheduledList(), null, { 'data-rp3-scheduled': '' }));
|
|
478
|
-
}
|
|
479
|
-
|
|
480
346
|
/* boot */
|
|
481
|
-
drawTabs(); drawDash();
|
|
347
|
+
drawTabs(); drawDash();
|
|
482
348
|
Promise.resolve(get('/api/report-dashboards' + (C.ws ? '?ws=' + encodeURIComponent(C.ws) : ''))).then((r) => {
|
|
483
349
|
if (!S.alive) return;
|
|
484
|
-
if (r && r.ok) { S.meta = r; S.layouts = { ...(r.layouts || {}) };
|
|
485
|
-
drawTabs(); drawDash(); if (S.tab === '
|
|
350
|
+
if (r && r.ok) { S.meta = r; S.layouts = { ...(r.layouts || {}) }; } else S.metaErr = (r && (r.error || r.reason)) || 'not available';
|
|
351
|
+
drawTabs(); drawDash(); if (S.tab === 'charts') drawLib();
|
|
486
352
|
}, (e) => { S.metaErr = String((e && e.message) || e); drawDash(); });
|
|
487
353
|
|
|
488
|
-
const api = { dispose() { S.alive = false; if (livePage === api) livePage = null; stopPhone(); root.remove(); }, root, showTab: (v) => { if (panes[v]) showTab(v); } };
|
|
354
|
+
const api = { dispose() { S.alive = false; offReports(); if (livePage === api) livePage = null; stopPhone(); root.remove(); }, root, showTab: (v) => { v = TAB_OF[v] || v; if (panes[v]) showTab(v); } };
|
|
489
355
|
livePage = api;
|
|
490
356
|
if (wantView) { const v = wantView; wantView = null; api.showTab(v); }
|
|
491
357
|
return api;
|
|
@@ -26,6 +26,7 @@ import { c3ic } from './pm-work-view.js';
|
|
|
26
26
|
import { sessionDefaults, setSessionDefaults } from './dock.js';
|
|
27
27
|
import { toast } from './c3-toast.js';
|
|
28
28
|
import { dayPartOf } from './c3-daypart.js';
|
|
29
|
+
import { finderBlock } from './c3-report-store.js';
|
|
29
30
|
|
|
30
31
|
const cx = (...c) => c.filter(Boolean).join(' ');
|
|
31
32
|
|
|
@@ -36,7 +37,7 @@ export const SECTIONS = Object.freeze([
|
|
|
36
37
|
['connectors', 'Connectors', 'org', 'PMOS keeps no passwords or keys for these. Your Claude session reaches each tool through the connectors of its profile.'],
|
|
37
38
|
['recording', 'Recording', 'mic', 'How meetings are transcribed. This is the one place to check when a transcript looks wrong.'],
|
|
38
39
|
['time', 'Day and time', 'sun', 'The Briefing follows your clock. These settings say what your day looks like.'],
|
|
39
|
-
['sharing', 'Sharing', 'mail', 'Who
|
|
40
|
+
['sharing', 'Sharing', 'mail', 'Who reports go to, and how. Each report\'s schedule is set in Reports, under Set up.'],
|
|
40
41
|
['access', 'Access and devices', 'phone', 'Open PMOS from your phone or another computer.'],
|
|
41
42
|
['sessions', 'Claude sessions', 'term', 'The Claude Code sessions PMOS starts, and what they start with.'],
|
|
42
43
|
['privacy', 'Privacy and data', 'shield', 'Nothing leaves this computer unless a line below says so.'],
|
|
@@ -529,7 +530,7 @@ export function mountSettings(host, ctx = {}) {
|
|
|
529
530
|
group('Sending', [storeRow('share.mode', { disabled: sender === 'none', lock: sender === 'none' ? 'Connect a sender in Connectors first. Until then every share is a draft.' : null })], 'mail'),
|
|
530
531
|
group('Detail level', [notYet('share.detail', 'Detail for each audience', 'Short is one sentence per report and what needs a decision. Full adds the numbers behind each sentence.', 'The Share sheet sets the detail for each send. A default per audience comes later.', 'ws')], 'mail'),
|
|
531
532
|
sect('Audiences', [list(rows), M.err['share.audiences'] ? fold('alert', M.err['share.audiences'], 'w') : null], { ic: 'user', count: rows.length }),
|
|
532
|
-
sect('Scheduled
|
|
533
|
+
sect('Scheduled reports', [fold(null, 'Each report has its own schedule. See and change them in Reports, under Set up.', null, btn('Open Reports', { variant: 'ghost', ic: 'next', onclick: () => ctx.goTab && ctx.goTab('rep', 'reports') }))], { ic: 'clock', attrs: { 'data-c3set-sched-link': '' } })];
|
|
533
534
|
}
|
|
534
535
|
|
|
535
536
|
function accessSec() {
|
|
@@ -705,7 +706,7 @@ export function mountSettings(host, ctx = {}) {
|
|
|
705
706
|
return [sect('PMOS app', [list([appRow])], { ic: 'cloud', attrs: { 'data-c3set-app': '' } }), sect('Kit in each workspace', [list(rows)], { ic: 'repo', count: rows.length }), groupOf('updates', 'When', 'cloud')];
|
|
706
707
|
}
|
|
707
708
|
|
|
708
|
-
const BUILD = { about: aboutSec, workspaces: workspacesSec, boards: boardsSec, connectors: connectorsSec, recording: recordingSec, time: timeSec, sharing: sharingSec, access: accessSec, sessions: sessionsSec, privacy: privacySec, appearance: appearanceSec, updates: updatesSec };
|
|
709
|
+
const BUILD = { about: aboutSec, workspaces: workspacesSec, boards: boardsSec, connectors: () => [...connectorsSec(), finderBlock(M.ws)], recording: recordingSec, time: timeSec, sharing: sharingSec, access: accessSec, sessions: sessionsSec, privacy: privacySec, appearance: appearanceSec, updates: updatesSec };
|
|
709
710
|
|
|
710
711
|
function searchView() {
|
|
711
712
|
const hits = rowsMatching(M.q);
|
|
@@ -19,20 +19,18 @@ import { mountCalendarViews } from './calendar-views.js';
|
|
|
19
19
|
import { todaySection } from './c3-brief-today.js';
|
|
20
20
|
import { needsSection, needsCount } from './c3-brief-needs.js';
|
|
21
21
|
import { scanSection } from './c3-scan.js';
|
|
22
|
+
import { reportFoundSection } from './c3-report-store.js';
|
|
22
23
|
import { pulse } from './c3-brief-pulse.js';
|
|
23
24
|
import { loadBriefPage, onBriefPage } from './c3-brief-data.js';
|
|
24
25
|
import { mountLifecycle } from './lifecycle-lens.js';
|
|
25
26
|
import { mountForecast } from './timeline-forecast-view.js';
|
|
26
27
|
import { mountReleases } from './releases-view.js';
|
|
27
|
-
import { mountReportFamilies } from './report-families-view.js';
|
|
28
28
|
import { mountReports, showReportsView } from './c3-reports.js';
|
|
29
|
-
import { mountOutbox } from './outbox-panel.js';
|
|
30
29
|
import { createDock } from './c3-dock.js';
|
|
31
|
-
import { reviewSection,
|
|
32
|
-
import { stopCard } from './c3-misc-panels.js';
|
|
30
|
+
import { reviewSection, footLine, rowApi, BX, toast, setNeedsTitle, hooks as briefHooks } from './c3-brief.js';
|
|
33
31
|
import { openSheet } from './c3-sheet.js';
|
|
34
32
|
import { mountCalPopupTrigger } from './c3-calpop.js';
|
|
35
|
-
import { planNode, dueCard, crossLinks, newInitLine
|
|
33
|
+
import { planNode, dueCard, crossLinks, newInitLine } from './c3-plan.js';
|
|
36
34
|
import { initiativesViewCtx } from './registry.js';
|
|
37
35
|
import { mount as mountRoadmap } from './c3-roadmap.js';
|
|
38
36
|
import { mount as mountBreakdown } from './c3-breakdown.js';
|
|
@@ -303,7 +301,7 @@ function briefTab(pane) {
|
|
|
303
301
|
if (S.why) whyMark();
|
|
304
302
|
});
|
|
305
303
|
S.needsBox = needsSection(S);
|
|
306
|
-
left.append(deckHost, scanSection(S), S.needsBox); // the Horizon scan card sits above Needs you, and shows only on a day a scan ran
|
|
304
|
+
left.append(deckHost, scanSection(S), reportFoundSection(S), S.needsBox); // the Horizon scan card sits above Needs you, and shows only on a day a scan ran
|
|
307
305
|
right.append(todaySection(), pulse(S));
|
|
308
306
|
pane.append(el('div', { class: 'brief' }, el('div', { class: 'bcols' }, left, right), ftHost));
|
|
309
307
|
mountBriefing(deckHost, { noTicker: true, onLens: lensChanged });
|
|
@@ -317,6 +315,13 @@ function lensChanged(l) {
|
|
|
317
315
|
|
|
318
316
|
/* ── global: where a notification goes, the person sheet, the palette and the keys ── */
|
|
319
317
|
/* A bell entry or the banner goes to its target (console3.html nopen, line 2530): the card to turn over, the row to open, or the sheet to show. */
|
|
318
|
+
/* The report pages ask the shell to go somewhere (c3-report-store.js goTo): { tab, view?, section?, finding? }. Settings opens on a section. */
|
|
319
|
+
if (typeof window !== 'undefined') window.addEventListener('pmos:go-tab', (e) => {
|
|
320
|
+
const d = (e && e.detail) || {}; if (!d.tab) return;
|
|
321
|
+
showTab(d.tab, true);
|
|
322
|
+
if (d.tab === 'settings' && d.section && S.settings) S.settings.show(d.section);
|
|
323
|
+
if (d.tab === 'rep' && d.view) showReportsView(d.view);
|
|
324
|
+
});
|
|
320
325
|
function goTarget(t) {
|
|
321
326
|
/* a dashboard schedule's notice (routes-briefing.js notices): Reports, then its Drafts tab */
|
|
322
327
|
if (t && t.tab && BUILD[t.tab]) { showTab(t.tab, true); if (t.tab === 'rep' && t.view) showReportsView(t.view); return; }
|
|
@@ -519,30 +524,9 @@ function showInit(id) {
|
|
|
519
524
|
}
|
|
520
525
|
}
|
|
521
526
|
}
|
|
522
|
-
/* Reports: the headline deck on top (the Plan deck of the Reports scope), the tabs,
|
|
523
|
-
*
|
|
524
|
-
|
|
525
|
-
function repTab(pane) { mountReports(pane, { ws: S.ws, pmGet, deck: (h) => h.append(planNode('rep')), more: draftsMore, drafts: draftsTab }); }
|
|
526
|
-
function draftsMore(host) {
|
|
527
|
-
let sig = '';
|
|
528
|
-
onBriefing((b) => { // the same cards and the same condition as the Briefing had: only what the briefing carries (the demo's); redrawn only when it changed, so an open row stays open
|
|
529
|
-
const g = JSON.stringify([b && b.handled, b && b.stop, b && b.trust]);
|
|
530
|
-
if (g === sig) return; sig = g;
|
|
531
|
-
host.replaceChildren(...[b && b.handled ? handledRow(b.handled) : null, stopCard(b && b.stop), b && b.trust ? trustCard(b.trust) : null].filter(Boolean));
|
|
532
|
-
});
|
|
533
|
-
loadBriefing();
|
|
534
|
-
}
|
|
535
|
-
function draftsTab(pane) {
|
|
536
|
-
const fam = el('div', { 'data-c3pg': 'families-host' }); const out = el('div', { class: 'brief', 'data-c3pg': 'outbox-host', style: 'margin-top:var(--sp-4)' });
|
|
537
|
-
pane.append(fam, out);
|
|
538
|
-
pmGet('/api/report-families/facts?ws=' + encodeURIComponent(S.ws)).then((j) => {
|
|
539
|
-
mountReportFamilies(fam, { reportFacts: j && j.ok ? j.facts : {}, plan: null, portfolio: portfolioCard() });
|
|
540
|
-
mountOutbox(out, {});
|
|
541
|
-
/* The mockup's Reports draw the outbox's drafts under Communications only (the Messages queue); the other families have no outbox. */
|
|
542
|
-
const sync = () => { out.hidden = !fam.querySelector('[data-family="communications"][aria-pressed="true"]'); };
|
|
543
|
-
sync(); new MutationObserver(sync).observe(fam, { subtree: true, attributes: true, attributeFilter: ['aria-pressed'] });
|
|
544
|
-
});
|
|
545
|
-
}
|
|
527
|
+
/* Reports: the headline deck on top (the Plan deck of the Reports scope), then the three tabs (Dashboards, Reports, Charts; c3-reports.js).
|
|
528
|
+
* The Reports deck always names its scope, so real mode gets the engine's report signals and never the Briefing's cards (console3-deck-flow-fixes). */
|
|
529
|
+
function repTab(pane) { mountReports(pane, { ws: S.ws, pmGet, deck: (h) => h.append(planNode('rep')) }); }
|
|
546
530
|
|
|
547
531
|
const BUILD = { brief: briefTab, cal: (p) => mountCalendarViews(p, { view: 'week', page: true, scope: sc('cal'), deck: planNode('cal', { bare: true }) }), work: (p) => { mountPmWork(p, { go: (t) => showTab(t, true), ws: S.ws }); }, init: (p) => mountInitiatives(p, { ws: S.ws, workspaces: S.workspaces, demo: DEMO, showTab }), rep: repTab };
|
|
548
532
|
|
|
@@ -559,7 +543,7 @@ function showTab(id, push) {
|
|
|
559
543
|
if (S.gearBtn) S.gearBtn.setAttribute('aria-pressed', String(id === 'settings'));
|
|
560
544
|
if (id === 'settings') {
|
|
561
545
|
S.ttl.replaceChildren(gearIcon('lg'), 'Settings'); document.title = 'Settings · PMOS console';
|
|
562
|
-
if (!S.settings) { S.settingsCtx = { ws: S.ws, workspaces: S.workspaces, theme: S.theme, themePref: S.themePref, setTheme, goTab: (t) => showTab(t, true), section: q.get('section') || '', root: S.root }; S.settings = mountSettings(S.panes.settings, S.settingsCtx); }
|
|
546
|
+
if (!S.settings) { S.settingsCtx = { ws: S.ws, workspaces: S.workspaces, theme: S.theme, themePref: S.themePref, setTheme, goTab: (t, v) => { showTab(t, true); if (t === 'rep' && v) showReportsView(v); }, section: q.get('section') || '', root: S.root }; S.settings = mountSettings(S.panes.settings, S.settingsCtx); }
|
|
563
547
|
else S.settings.refresh();
|
|
564
548
|
} else if (!S.mounted[id]) { S.mounted[id] = true; BUILD[id](S.panes[id]); }
|
|
565
549
|
S.root.dataset.tab = id;
|