Compare commits
1 Commits
main
..
28d817ebe9
| Author | SHA1 | Date | |
|---|---|---|---|
| 28d817ebe9 |
@@ -29,4 +29,3 @@ storage-dump.json
|
|||||||
|
|
||||||
# NAS deploy config (not secret, but host-specific)
|
# NAS deploy config (not secret, but host-specific)
|
||||||
.deploy.env
|
.deploy.env
|
||||||
/.idea/
|
|
||||||
|
|||||||
@@ -4,26 +4,6 @@ All notable changes to **FORGE** are documented here.
|
|||||||
Format based on [Keep a Changelog](https://keepachangelog.com/); this project uses
|
Format based on [Keep a Changelog](https://keepachangelog.com/); this project uses
|
||||||
[Semantic Versioning](https://semver.org/).
|
[Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
## [2.3.2] — 2026-08-29
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- **PM Insights — full-width category items.** Inside **Alert categories** and
|
|
||||||
**Watch list**, each category is now a **full-width accordion** stacked in a single
|
|
||||||
column (was a narrow multi-column grid), with more breathing room: larger inter-card
|
|
||||||
gap, roomier card headers and ticket rows, and a taller expanded list. The click-to-
|
|
||||||
expand behaviour per category is unchanged. (CSS-only: the `.grid` becomes a flex
|
|
||||||
column; `.cardHead`/`.row` padding widened.)
|
|
||||||
|
|
||||||
## [2.3.1] — 2026-08-29
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- **PM Insights — collapsible sections.** The **Alert categories** and **Watch list**
|
|
||||||
sections are now accordions: each heading is a button (chevron + a category-count
|
|
||||||
badge, `aria-expanded`/`aria-controls`) that collapses/expands its card grid. Both
|
|
||||||
default to open; the per-card ticket expand and the **Per-PM roll-up** table are
|
|
||||||
unchanged. Adds a `ChevronIcon` (rotates with the section) and a `.grid[hidden]` rule
|
|
||||||
so a collapsed grid actually hides.
|
|
||||||
|
|
||||||
## [2.3.0] — 2026-08-27
|
## [2.3.0] — 2026-08-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
# Artifact index
|
# Artifact index
|
||||||
- [engineer-20260829-122201](engineer-20260829-122201.md) — PM Insights: category items in "Alert categories" + "Watch list" now full-width single-column accordions with more space (grid→flex column, roomier card/row padding). CSS-only. v2.3.2. verifier PASS.
|
|
||||||
- [engineer-20260829-120746](engineer-20260829-120746.md) — PM Insights: "Alert categories" + "Watch list" now collapsible accordions (new `Section`, `ChevronIcon`, `.grid[hidden]` collapse, `aria-expanded`/`aria-controls`). v2.3.1. verifier PASS.
|
|
||||||
- [engineer-20260827-190753](engineer-20260827-190753.md) — Jira statusDurations+movements + chart #17 (Active tab), /api/analytics/jira-durations, seed-enriched from board_state. v2.3.0.
|
- [engineer-20260827-190753](engineer-20260827-190753.md) — Jira statusDurations+movements + chart #17 (Active tab), /api/analytics/jira-durations, seed-enriched from board_state. v2.3.0.
|
||||||
- [engineer-20260827-185932](engineer-20260827-185932.md) — Extension Jira sync (SNOW→Jira, attach-only /api/sync/jira, custom-domain + PAT). v2.2.0. Live-verified attach-only.
|
- [engineer-20260827-185932](engineer-20260827-185932.md) — Extension Jira sync (SNOW→Jira, attach-only /api/sync/jira, custom-domain + PAT). v2.2.0. Live-verified attach-only.
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# engineer — PM Insights: Alert categories + Watch list as accordions
|
|
||||||
|
|
||||||
**Date:** 2026-08-29 · v2.3.1 · branch `feature/insights-accordions`
|
|
||||||
|
|
||||||
## Built
|
|
||||||
- **Client** (`pages/Insights/index.tsx`): new `Section({title, count, open, onToggle, children})`
|
|
||||||
accordion — a `<button className={sectionHead} aria-expanded aria-controls>` (chevron + title +
|
|
||||||
a category-count badge) over a `<div id={bodyId} className={grid} hidden={!open}>`. The two
|
|
||||||
sections **Alert categories** and **Watch list** are wrapped in it. Added `sections` state
|
|
||||||
(`{alerts, watch}`, both default **open**) + `toggleSection`. The per-card `open` state (which
|
|
||||||
ticket group is expanded) and the **Per-PM roll-up** table are untouched — Per-PM stays a plain
|
|
||||||
`<h2>` (not requested).
|
|
||||||
- **Icons** (`components/icons/index.tsx`): added `ChevronIcon` (right caret, shared `base()`
|
|
||||||
pattern), rotated 90° via CSS when its section is open.
|
|
||||||
- **Styles** (`Insights.module.scss`): `.section` / `.sectionHead` / `.chev` / `.sectionTitle` /
|
|
||||||
`.sectionCount` reusing the old `.h2` typographic treatment; `.sectionHead[aria-expanded='true']
|
|
||||||
.chev { transform: rotate(90deg) }`; and `.grid[hidden] { display: none }` — required, since
|
|
||||||
`.grid { display: grid }` would otherwise beat the native `hidden` attribute (specificity 0,2,0
|
|
||||||
vs 0,1,0).
|
|
||||||
- **Version/docs**: root + client `package.json` 2.3.0 → 2.3.1; CHANGELOG `[2.3.1]`.
|
|
||||||
|
|
||||||
## Verified
|
|
||||||
- client `tsc --noEmit` clean (no errors outside node_modules); `vite build` OK (139 modules).
|
|
||||||
- **verifier PASS** (see `verifier-log.md` 2026-08-29 12:07): independently reproduced tsc + build;
|
|
||||||
grepped the compiled bundle to confirm `._grid…[hidden]{display:none}` co-exists with
|
|
||||||
`._grid…{display:grid}` and wins on specificity **and** source order; `aria-controls` always
|
|
||||||
resolves (body is `hidden`, not unmounted); slugs `insights-alert-categories` /
|
|
||||||
`insights-watch-list` are distinct; per-card expand byte-for-byte unchanged → no regression;
|
|
||||||
no `package.json` dep change.
|
|
||||||
|
|
||||||
## Next: done.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# engineer — PM Insights: full-width category items with more space
|
|
||||||
|
|
||||||
**Date:** 2026-08-29 · v2.3.2 · branch `feature/insights-fullwidth-items`
|
|
||||||
|
|
||||||
## Built
|
|
||||||
Follow-up to the section accordions (v2.3.1, merged as PR #1). The categories inside
|
|
||||||
**Alert categories** and **Watch list** were a narrow multi-column grid of click-to-expand
|
|
||||||
cards; they are now a **full-width, single-column stack** with more breathing room.
|
|
||||||
|
|
||||||
**CSS-only** — `client/src/pages/Insights/Insights.module.scss`:
|
|
||||||
- `.grid`: `display:grid; grid-template-columns: repeat(auto-fill, minmax(300px,1fr))` →
|
|
||||||
`display:flex; flex-direction:column; gap:$s-4`. Each `.card` now spans full width, one
|
|
||||||
per row, with a larger inter-card gap. `.grid[hidden]{display:none}` kept (section-collapse
|
|
||||||
still works).
|
|
||||||
- `.cardHead` padding `12px 14px` → `$s-4 $s-5` (16/20); `.cardTitle` 13px → 14px.
|
|
||||||
- `.row` padding `6px 14px` → `$s-2 $s-5` (8/20); `.emptyRow` `10px 14px` → `$s-3 $s-5`.
|
|
||||||
- `.list` max-height 340 → 440px (fuller expanded list on the wider rows).
|
|
||||||
- `Insights/index.tsx` unchanged — each item was already an accordion.
|
|
||||||
- Version root+client 2.3.1 → 2.3.2; CHANGELOG `[2.3.2]`.
|
|
||||||
|
|
||||||
## Verified
|
|
||||||
- client `tsc --noEmit` clean; `vite build` OK. Full gate `npm test` = client 6/6 + root 3/3
|
|
||||||
(root deps freshly installed — that was the earlier deploy blocker, unrelated to this change).
|
|
||||||
- Compiled CSS confirms `._grid_doeho_78{display:flex;flex-direction:column;gap:16px}` +
|
|
||||||
`[hidden]{display:none}`.
|
|
||||||
- verifier: (see verifier-log entry for 2026-08-29).
|
|
||||||
|
|
||||||
## Next: done.
|
|
||||||
@@ -16,5 +16,3 @@
|
|||||||
- 2026-08-27 19:02 · engineer · Extension Jira-sync (SNOW+Jira attach-only) · VERDICT: PASS · re-ran: server tsc 0, node --check bg/options/popup OK, tests 9 pass (6 client+3 root), live psql TEMP-TABLE smoke of exact attachJira UPDATE (real RITM2653436 untouched) · probed: Jira payload with top-level "status"+SQLi string lands ONLY inside jira jsonb (ticket status stays closed, table survives)→no clobber/no injection; '||' merge preserves omitted keys (extraKept=KEEPME, assignee=Old Person); jira_key WFN-100→605 via COALESCE; non-matching number→UPDATE 0 (no stub); endpoint requireToken not session (401 w/o bearer, index.ts:241); schema additive-only (jira JSONB pre-existing, all ALTER ADD COLUMN IF NOT EXISTS) · PASS
|
- 2026-08-27 19:02 · engineer · Extension Jira-sync (SNOW+Jira attach-only) · VERDICT: PASS · re-ran: server tsc 0, node --check bg/options/popup OK, tests 9 pass (6 client+3 root), live psql TEMP-TABLE smoke of exact attachJira UPDATE (real RITM2653436 untouched) · probed: Jira payload with top-level "status"+SQLi string lands ONLY inside jira jsonb (ticket status stays closed, table survives)→no clobber/no injection; '||' merge preserves omitted keys (extraKept=KEEPME, assignee=Old Person); jira_key WFN-100→605 via COALESCE; non-matching number→UPDATE 0 (no stub); endpoint requireToken not session (401 w/o bearer, index.ts:241); schema additive-only (jira JSONB pre-existing, all ALTER ADD COLUMN IF NOT EXISTS) · PASS
|
||||||
- 2026-08-27 19:11 · engineer · Jira statusDurations+movements+chart #17 · VERDICT: PASS · re-ran: server+client tsc=0, root vitest 3/3, client vitest 6/6, live SQL agg on tickets(jira?statusDurations)=73 rows, booted dist+curl endpoint · probed: 401 unauth (got 401), min-2 filter drops Cancelled n=1, ordering known-workflow-first + extras avgDays-desc, ms/86.4M day math matches SQL exactly (UAT 16.14d/21, Closed 23.51d/16, InProgress 4.90d/37), guards NaN/<=0, client null-safe (jiraDur=[] + length>0 gate), static SQL no user input, additive jsonb no DDL · PASS
|
- 2026-08-27 19:11 · engineer · Jira statusDurations+movements+chart #17 · VERDICT: PASS · re-ran: server+client tsc=0, root vitest 3/3, client vitest 6/6, live SQL agg on tickets(jira?statusDurations)=73 rows, booted dist+curl endpoint · probed: 401 unauth (got 401), min-2 filter drops Cancelled n=1, ordering known-workflow-first + extras avgDays-desc, ms/86.4M day math matches SQL exactly (UAT 16.14d/21, Closed 23.51d/16, InProgress 4.90d/37), guards NaN/<=0, client null-safe (jiraDur=[] + length>0 gate), static SQL no user input, additive jsonb no DDL · PASS
|
||||||
- 2026-08-29 11:20 · devops · SSH NAS host → mycloud.dp.ua:2323 · VERDICT: PASS · re-ran: grep 192.168.50.2 (only comment+artifact), grep NAS_HOST (all mycloud), bash -n push-to-nas.sh OK · probed: all ssh/scp/rsync hops use $NAS_HOST not hardcoded IP; port 2323 preserved; illustrative comment+historical artifact intentional · PASS
|
- 2026-08-29 11:20 · devops · SSH NAS host → mycloud.dp.ua:2323 · VERDICT: PASS · re-ran: grep 192.168.50.2 (only comment+artifact), grep NAS_HOST (all mycloud), bash -n push-to-nas.sh OK · probed: all ssh/scp/rsync hops use $NAS_HOST not hardcoded IP; port 2323 preserved; illustrative comment+historical artifact intentional · PASS
|
||||||
- 2026-08-29 12:07 · engineer · Insights alert/watch sections as accordions · VERDICT: PASS · re-ran: tsc -p (clean), vite build (139 modules, exit 0), grep compiled CSS (.grid[hidden]{display:none} present, specificity/order beats .grid), token defs in globals.scss · probed: collapse hides grid via hidden+override; aria-controls id always-rendered (no dangling ref when collapsed); unique slugs alert-categories/watch-list (no dup id); per-card open state diff-unchanged (no regression); chevron rotate(90deg) bound to aria-expanded=true · PASS
|
|
||||||
- 2026-08-29 12:23 · designer/engineer · Insights full-width accordion items (CSS) · VERDICT: PASS · re-ran: vite build OK, tsc clean, npm test 9/9 (client 6 + root 3), grepped dist CSS · probed: section hidden-collapse specificity (.grid[hidden] 0,2,0 beats .grid 0,1,0 → display:none wins), flex-column full-width via default align-items:stretch, .row internal grid columns intact + tokens compile (sass would error if undefined) · PASS
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "forge-client",
|
"name": "forge-client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.3.2",
|
"version": "2.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -60,14 +60,6 @@ export function CloseIcon({ size = 18 }: IconProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChevronIcon({ size = 14 }: IconProps) {
|
|
||||||
return (
|
|
||||||
<svg {...base(size)}>
|
|
||||||
<path d="m9 18 6-6-6-6" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SpreadsheetIcon({ size = 15 }: IconProps) {
|
export function SpreadsheetIcon({ size = 15 }: IconProps) {
|
||||||
return (
|
return (
|
||||||
<svg {...base(size)}>
|
<svg {...base(size)}>
|
||||||
|
|||||||
@@ -17,48 +17,12 @@
|
|||||||
margin-top: $s-4;
|
margin-top: $s-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section { display: flex; flex-direction: column; gap: $s-3; }
|
|
||||||
.sectionHead {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $s-2;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: $s-4;
|
|
||||||
padding: 4px 0;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: var(--text-muted);
|
|
||||||
&:hover { color: var(--text); }
|
|
||||||
}
|
|
||||||
.chev {
|
|
||||||
display: inline-flex;
|
|
||||||
color: var(--text-dim);
|
|
||||||
transition: transform 0.15s ease;
|
|
||||||
}
|
|
||||||
.sectionHead[aria-expanded='true'] .chev { transform: rotate(90deg); }
|
|
||||||
.sectionHead:hover .chev { color: var(--text-muted); }
|
|
||||||
.sectionTitle { flex: 0 0 auto; }
|
|
||||||
.sectionCount {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0;
|
|
||||||
padding: 1px 7px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--surface-alt);
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
gap: $s-4;
|
gap: $s-3;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
.grid[hidden] { display: none; }
|
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
@@ -74,13 +38,13 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: $s-3;
|
gap: $s-3;
|
||||||
padding: $s-4 $s-5;
|
padding: 12px 14px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover { background: var(--surface-alt); }
|
&:hover { background: var(--surface-alt); }
|
||||||
}
|
}
|
||||||
.cardTitle { font-size: 14px; font-weight: 600; color: var(--text); }
|
.cardTitle { font-size: 13px; font-weight: 600; color: var(--text); }
|
||||||
.counts { display: flex; align-items: center; gap: 6px; }
|
.counts { display: flex; align-items: center; gap: 6px; }
|
||||||
.problem {
|
.problem {
|
||||||
min-width: 22px; text-align: center; font-size: 11px; font-weight: 700;
|
min-width: 22px; text-align: center; font-size: 11px; font-weight: 700;
|
||||||
@@ -91,18 +55,18 @@
|
|||||||
padding: 1px 7px; border-radius: 999px; background: var(--surface-alt); color: var(--text-muted);
|
padding: 1px 7px; border-radius: 999px; background: var(--surface-alt); color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list { list-style: none; border-top: 1px solid var(--border); max-height: 440px; overflow-y: auto; }
|
.list { list-style: none; border-top: 1px solid var(--border); max-height: 340px; overflow-y: auto; }
|
||||||
.row {
|
.row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto auto auto;
|
grid-template-columns: auto 1fr auto auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $s-2;
|
gap: $s-2;
|
||||||
padding: $s-2 $s-5;
|
padding: 6px 14px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
&:last-child { border-bottom: none; }
|
&:last-child { border-bottom: none; }
|
||||||
}
|
}
|
||||||
.emptyRow { padding: $s-3 $s-5; color: var(--text-dim); font-size: 12px; }
|
.emptyRow { padding: 10px 14px; color: var(--text-dim); font-size: 12px; }
|
||||||
.rowNum { display: inline-flex; align-items: center; gap: 3px; font-family: var(--mono); font-weight: 700; color: var(--primary); white-space: nowrap; }
|
.rowNum { display: inline-flex; align-items: center; gap: 3px; font-family: var(--mono); font-weight: 700; color: var(--primary); white-space: nowrap; }
|
||||||
.rowDesc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
|
.rowDesc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
|
||||||
.rowMeta { color: var(--text-muted); white-space: nowrap; max-width: 110px; overflow: hidden; text-overflow: ellipsis; }
|
.rowMeta { color: var(--text-muted); white-space: nowrap; max-width: 110px; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState, type ReactNode } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import PageHeader from '../../components/PageHeader';
|
import PageHeader from '../../components/PageHeader';
|
||||||
import KpiTile from '../../components/charts/KpiTile';
|
import KpiTile from '../../components/charts/KpiTile';
|
||||||
import { ExternalIcon, ChevronIcon } from '../../components/icons';
|
import { ExternalIcon } from '../../components/icons';
|
||||||
import { getInsights } from '../../services/analytics.service';
|
import { getInsights } from '../../services/analytics.service';
|
||||||
import type { InsightsResponse, AlertGroup, InsightTicket } from '../../types/analytics.types';
|
import type { InsightsResponse, AlertGroup, InsightTicket } from '../../types/analytics.types';
|
||||||
import styles from './Insights.module.scss';
|
import styles from './Insights.module.scss';
|
||||||
@@ -15,8 +15,6 @@ export default function Insights({ refreshKey }: { refreshKey: number }) {
|
|||||||
const [data, setData] = useState<InsightsResponse | null>(null);
|
const [data, setData] = useState<InsightsResponse | null>(null);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [open, setOpen] = useState<string | null>(null);
|
const [open, setOpen] = useState<string | null>(null);
|
||||||
const [sections, setSections] = useState({ alerts: true, watch: true });
|
|
||||||
const toggleSection = (key: 'alerts' | 'watch') => setSections(s => ({ ...s, [key]: !s[key] }));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let alive = true;
|
let alive = true;
|
||||||
@@ -42,13 +40,15 @@ export default function Insights({ refreshKey }: { refreshKey: number }) {
|
|||||||
<KpiTile label="PO escalations" value={`${data.waitingPo.levels.l1}·${data.waitingPo.levels.l2}·${data.waitingPo.levels.l3}`} tone="warning" hint="L1 · L2 · L3" />
|
<KpiTile label="PO escalations" value={`${data.waitingPo.levels.l1}·${data.waitingPo.levels.l2}·${data.waitingPo.levels.l3}`} tone="warning" hint="L1 · L2 · L3" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Section title="Alert categories" count={alertGroups.length} open={sections.alerts} onToggle={() => toggleSection('alerts')}>
|
<h2 className={styles.h2}>Alert categories</h2>
|
||||||
|
<div className={styles.grid}>
|
||||||
{alertGroups.map(g => <AlertCard key={g.key} g={g} open={open === g.key} onToggle={() => setOpen(open === g.key ? null : g.key)} showProblem />)}
|
{alertGroups.map(g => <AlertCard key={g.key} g={g} open={open === g.key} onToggle={() => setOpen(open === g.key ? null : g.key)} showProblem />)}
|
||||||
</Section>
|
</div>
|
||||||
|
|
||||||
<Section title="Watch list" count={infoGroups.length} open={sections.watch} onToggle={() => toggleSection('watch')}>
|
<h2 className={styles.h2}>Watch list</h2>
|
||||||
|
<div className={styles.grid}>
|
||||||
{infoGroups.map(g => <AlertCard key={g.key} g={g} open={open === g.key} onToggle={() => setOpen(open === g.key ? null : g.key)} />)}
|
{infoGroups.map(g => <AlertCard key={g.key} g={g} open={open === g.key} onToggle={() => setOpen(open === g.key ? null : g.key)} />)}
|
||||||
</Section>
|
</div>
|
||||||
|
|
||||||
<h2 className={styles.h2}>Per-PM roll-up</h2>
|
<h2 className={styles.h2}>Per-PM roll-up</h2>
|
||||||
<div className={styles.pmCard}>
|
<div className={styles.pmCard}>
|
||||||
@@ -73,20 +73,6 @@ export default function Insights({ refreshKey }: { refreshKey: number }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Section({ title, count, open, onToggle, children }: { title: string; count: number; open: boolean; onToggle: () => void; children: ReactNode }) {
|
|
||||||
const bodyId = `insights-${title.replace(/\s+/g, '-').toLowerCase()}`;
|
|
||||||
return (
|
|
||||||
<section className={styles.section}>
|
|
||||||
<button className={styles.sectionHead} onClick={onToggle} aria-expanded={open} aria-controls={bodyId}>
|
|
||||||
<span className={styles.chev}><ChevronIcon size={14} /></span>
|
|
||||||
<span className={styles.sectionTitle}>{title}</span>
|
|
||||||
<span className={styles.sectionCount}>{count}</span>
|
|
||||||
</button>
|
|
||||||
<div id={bodyId} className={styles.grid} hidden={!open}>{children}</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AlertCard({ g, open, onToggle, showProblem }: { g: AlertGroup; open: boolean; onToggle: () => void; showProblem?: boolean }) {
|
function AlertCard({ g, open, onToggle, showProblem }: { g: AlertGroup; open: boolean; onToggle: () => void; showProblem?: boolean }) {
|
||||||
const shown = showProblem ? g.tickets.filter((_, i) => i < g.problematic || open) : g.tickets;
|
const shown = showProblem ? g.tickets.filter((_, i) => i < g.problematic || open) : g.tickets;
|
||||||
return (
|
return (
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "forge",
|
"name": "forge",
|
||||||
"version": "2.3.2",
|
"version": "2.3.0",
|
||||||
"description": "FORGE 2.0 — ServiceNow ticket analytics portal (RITM/SCTASK) with roles, PM insights, and a Chrome sync extension",
|
"description": "FORGE 2.0 — ServiceNow ticket analytics portal (RITM/SCTASK) with roles, PM insights, and a Chrome sync extension",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user