diff --git a/src/api/catalog-client.ts b/src/api/catalog-client.ts index c81759d..aadc855 100644 --- a/src/api/catalog-client.ts +++ b/src/api/catalog-client.ts @@ -121,6 +121,7 @@ export function prefixForBulletSection(section: string): string { if (section === 'code_quality') return 'code_quality_bullet_rows'; if (section === 'ai_adoption') return 'ai_bullet_rows'; if (section === 'collaboration') return 'collab_bullet_rows'; + if (section === 'support') return 'support_bullet_rows'; return 'task_delivery_bullet_rows'; } diff --git a/src/api/metric-registry.ts b/src/api/metric-registry.ts index bcb483e..f6d7268 100644 --- a/src/api/metric-registry.ts +++ b/src/api/metric-registry.ts @@ -7,11 +7,13 @@ export const METRIC_REGISTRY = { TEAM_BULLET_QUALITY: "00000000-0000-0000-0001-000000000004", TEAM_BULLET_COLLAB: "00000000-0000-0000-0001-000000000005", TEAM_BULLET_AI: "00000000-0000-0000-0001-000000000006", + TEAM_BULLET_SUPPORT: "00000000-0000-0000-0001-000000000007", IC_KPIS: "00000000-0000-0000-0001-000000000010", IC_BULLET_DELIVERY: "00000000-0000-0000-0001-000000000011", IC_BULLET_COLLAB: "00000000-0000-0000-0001-000000000012", IC_BULLET_AI: "00000000-0000-0000-0001-000000000013", + IC_BULLET_SUPPORT: "00000000-0000-0000-0001-000000000008", IC_CHART_LOC: "00000000-0000-0000-0001-000000000014", IC_CHART_DELIVERY: "00000000-0000-0000-0001-000000000015", IC_DRILL: "00000000-0000-0000-0001-000000000016", diff --git a/src/components/widgets/team-bullet-sections.tsx b/src/components/widgets/team-bullet-sections.tsx index 9e0d12b..21f1384 100644 --- a/src/components/widgets/team-bullet-sections.tsx +++ b/src/components/widgets/team-bullet-sections.tsx @@ -13,7 +13,8 @@ type SectionId = | "code_quality" | "estimation" | "ai_adoption" - | "collaboration"; + | "collaboration" + | "support"; export interface TeamBulletSectionsProps { sections: Record; @@ -235,6 +236,49 @@ function CollaborationSection({ ); } +function SupportSection({ + metrics, + onDrillClick, +}: { + metrics: BulletMetric[]; + onDrillClick?: (id: string) => void; +}) { + const left = metrics.filter((_, i) => i % 2 === 0); + const right = metrics.filter((_, i) => i % 2 !== 0); + return ( + +
+ +
+
+ {left.map((m) => ( + + ))} +
+
+ {right.map((m) => ( + + ))} +
+
+
+
+ ); +} + function SectionPlaceholder({ title, kind, @@ -270,6 +314,7 @@ export function TeamBulletSections({ const estimation = sections.estimation; const aiAdoption = sections.ai_adoption; const collab = sections.collaboration; + const support = sections.support; function renderSection( sid: SectionId, @@ -331,6 +376,10 @@ export function TeamBulletSections({ onDrillClick={onDrillClick} /> ))} + + {renderSection("support", "Support", () => ( + + ))} ); } diff --git a/src/components/widgets/team-metrics-table.tsx b/src/components/widgets/team-metrics-table.tsx index 038eace..6164d5a 100644 --- a/src/components/widgets/team-metrics-table.tsx +++ b/src/components/widgets/team-metrics-table.tsx @@ -33,6 +33,7 @@ const META_FALLBACK = (metricKey: string): MetricMeta => ({ const SECTION_ORDER: ReadonlyArray = [ "task_delivery", "collaboration", + "support", "ai_adoption", "git_output", ]; @@ -40,6 +41,7 @@ const SECTION_ORDER: ReadonlyArray = [ const SECTION_LABEL_KEY: Record = { task_delivery: "team_metrics_modal.sections.task_delivery", collaboration: "team_metrics_modal.sections.collaboration", + support: "team_metrics_modal.sections.support", ai_adoption: "team_metrics_modal.sections.ai_adoption", git_output: "team_metrics_modal.sections.git_output", }; diff --git a/src/components/widgets/v2/section-card.tsx b/src/components/widgets/v2/section-card.tsx index 12b641c..010c240 100644 --- a/src/components/widgets/v2/section-card.tsx +++ b/src/components/widgets/v2/section-card.tsx @@ -32,6 +32,12 @@ const COLLAB_PREVIEW_KEYS = [ "m365_emails_sent", ]; +const SUPPORT_PREVIEW_KEYS = [ + "support_updates", + "support_public_comments", + "support_solved", +]; + function pickPreviewRows( sectionId: string | undefined, rows: BulletMetric[], @@ -42,6 +48,12 @@ function pickPreviewRows( (r): r is BulletMetric => r != null && hasBulletValue(r), ); } + if (sectionId === "support") { + const byKey = new Map(rows.map((r) => [r.metric_key, r])); + return SUPPORT_PREVIEW_KEYS.map((k) => byKey.get(k)).filter( + (r): r is BulletMetric => r != null && hasBulletValue(r), + ); + } return rows.filter(hasBulletValue).slice(0, 3); } diff --git a/src/components/widgets/v2/section-drilldown-sheet.tsx b/src/components/widgets/v2/section-drilldown-sheet.tsx index 8127e1f..27c73fb 100644 --- a/src/components/widgets/v2/section-drilldown-sheet.tsx +++ b/src/components/widgets/v2/section-drilldown-sheet.tsx @@ -358,5 +358,24 @@ function DrilldownExtras({ ); } + if (sectionId === "support") { + // CSAT is the section's quality metric (a percent). Surface it as a + // headline percent summary when present; otherwise the CountersBlock + // below renders it (and the always-NULL KB metric) as ComingSoon. + const csat = rows.find((r) => r.metric_key === "support_csat"); + const csatValue = csat ? Number(csat.value) : NaN; + if (!csat || !Number.isFinite(csatValue)) return null; + return ( +
+ +
+ ); + } return null; } diff --git a/src/lib/insight/bullet-layout-groups.ts b/src/lib/insight/bullet-layout-groups.ts index f74b669..2801118 100644 --- a/src/lib/insight/bullet-layout-groups.ts +++ b/src/lib/insight/bullet-layout-groups.ts @@ -161,6 +161,15 @@ const BULLET_DISPLAY_ORDER: readonly string[] = [ 'teams_meetings', 'zoom_meetings', 'meeting_free', + + // support + 'support_active', + 'support_updates', + 'support_public_comments', + 'support_private_comments', + 'support_solved', + 'support_csat', + 'support_kb', ]; const ORDER_INDEX = new Map(BULLET_DISPLAY_ORDER.map((k, i) => [k, i])); diff --git a/src/lib/insight/v2/metric-order.ts b/src/lib/insight/v2/metric-order.ts index 1989880..485dd74 100644 --- a/src/lib/insight/v2/metric-order.ts +++ b/src/lib/insight/v2/metric-order.ts @@ -35,6 +35,15 @@ export const METRIC_ORDER_BY_SECTION: Record = { "m365_emails_received", "m365_emails_read", ], + support: [ + "support_active", + "support_updates", + "support_public_comments", + "support_private_comments", + "support_solved", + "support_csat", + "support_kb", + ], ai_adoption: [ "active_ai_members", "ai_loc_share2", diff --git a/src/lib/insight/v2/sections.ts b/src/lib/insight/v2/sections.ts index ecdc7c7..edc0d9f 100644 --- a/src/lib/insight/v2/sections.ts +++ b/src/lib/insight/v2/sections.ts @@ -3,7 +3,8 @@ export type IcSectionId = | "git_output" | "code_quality" | "collaboration" - | "ai_adoption"; + | "ai_adoption" + | "support"; export type IcHeroSectionId = IcSectionId; @@ -11,7 +12,8 @@ export type TeamSectionId = | "task_delivery" | "code_quality" | "collaboration" - | "ai_adoption"; + | "ai_adoption" + | "support"; export const IC_SECTIONS: ReadonlyArray<{ id: IcSectionId; label: string }> = [ { id: "task_delivery", label: "Task delivery" }, @@ -19,6 +21,7 @@ export const IC_SECTIONS: ReadonlyArray<{ id: IcSectionId; label: string }> = [ { id: "code_quality", label: "Code quality" }, { id: "collaboration", label: "Collaboration" }, { id: "ai_adoption", label: "AI dev tools" }, + { id: "support", label: "Support" }, ] as const; export const IC_HERO_SECTIONS: ReadonlyArray<{ @@ -34,4 +37,5 @@ export const TEAM_SECTIONS: ReadonlyArray<{ { id: "code_quality", label: "Code quality" }, { id: "collaboration", label: "Collaboration" }, { id: "ai_adoption", label: "AI adoption" }, + { id: "support", label: "Support" }, ] as const; diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index f5b03d9..c652444 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -44,6 +44,7 @@ "git_output": "Git output", "code_quality": "Code quality", "collaboration": "Collaboration", + "support": "Support", "ai_adoption": "AI adoption" }, "section_blocks": { @@ -140,6 +141,7 @@ "sections": { "task_delivery": "Delivery", "collaboration": "Collaboration", + "support": "Support", "ai_adoption": "AI Adoption", "git_output": "Git" } @@ -155,7 +157,8 @@ "delivery_trends_subtitle": "Jira + Bitbucket · activity counts · Commits, PRs and Tasks are independent signals", "ai_dev_tools_title": "AI Dev Tools & AI Chat", "ai_adoption": "AI Adoption", - "collaboration": "Collaboration" + "collaboration": "Collaboration", + "support": "Support" } }, "sales_dashboard": { diff --git a/src/mocks/catalog-snapshot.json b/src/mocks/catalog-snapshot.json index 7adfa5d..e5ddab5 100644 --- a/src/mocks/catalog-snapshot.json +++ b/src/mocks/catalog-snapshot.json @@ -1302,6 +1302,140 @@ "bounded_by_lock": false } }, + { + "id": "019e83b1-5001-7000-a001-000000000001", + "metric_key": "support_bullet_rows.support_active", + "label": "Active Support Members", + "sublabel": "Zendesk · people with any support activity · period total", + "unit": "people", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 1, + "warn": 0, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, + { + "id": "019e83b1-5001-7000-a001-000000000002", + "metric_key": "support_bullet_rows.support_updates", + "label": "Ticket Updates", + "sublabel": "Zendesk · ticket updates authored · period total", + "unit": "updates", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 40, + "warn": 15, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, + { + "id": "019e83b1-5001-7000-a001-000000000003", + "metric_key": "support_bullet_rows.support_public_comments", + "label": "Public Comments", + "sublabel": "Zendesk · public replies to requesters · period total", + "unit": "comments", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 30, + "warn": 10, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, + { + "id": "019e83b1-5001-7000-a001-000000000004", + "metric_key": "support_bullet_rows.support_private_comments", + "label": "Private Comments", + "sublabel": "Zendesk · internal notes on tickets · period total", + "unit": "comments", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 20, + "warn": 8, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, + { + "id": "019e83b1-5001-7000-a001-000000000005", + "metric_key": "support_bullet_rows.support_solved", + "label": "Tickets Solved", + "sublabel": "Zendesk · tickets moved to solved · period total", + "unit": "tickets", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 15, + "warn": 5, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, + { + "id": "019e83b1-5001-7000-a001-000000000006", + "metric_key": "support_bullet_rows.support_csat", + "label": "CSAT", + "sublabel": "Zendesk · customer satisfaction · % positive ratings", + "unit": "%", + "format": "percent", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 90, + "warn": 75, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, + { + "id": "019e83b1-5001-7000-a001-000000000007", + "metric_key": "support_bullet_rows.support_kb", + "label": "Knowledge Base Contributions", + "sublabel": "Zendesk Guide · KB articles created or edited · period total", + "unit": "articles", + "higher_is_better": true, + "is_member_scale": false, + "source_tags": [ + "zendesk" + ], + "schema_status": "ok", + "thresholds": { + "good": 3, + "warn": 1, + "resolved_from": "product-default", + "bounded_by_lock": false + } + }, { "id": "019e83b1-43b5-7993-a03d-96d232f5f00c", "metric_key": "ic_kpis.ai_loc_share_pct", diff --git a/src/mocks/factories.ts b/src/mocks/factories.ts index 3a8684b..b819043 100644 --- a/src/mocks/factories.ts +++ b/src/mocks/factories.ts @@ -20,7 +20,7 @@ type SnapshotMetric = { metric_key?: string; unit?: string }; const BULLET_KEYS_BY_SECTION: Map> = (() => { const out = new Map>(); const metrics = (catalogSnapshot as { metrics?: SnapshotMetric[] }).metrics ?? []; - for (const section of ["task_delivery", "git_output", "code_quality", "ai_adoption", "collaboration"]) { + for (const section of ["task_delivery", "git_output", "code_quality", "ai_adoption", "collaboration", "support"]) { const prefixDot = `${prefixForBulletSection(section)}.`; const rows: Array<{ metric_key: string; unit: string }> = []; for (const m of metrics) { @@ -371,6 +371,13 @@ const MOCK_BULLET_DIST: Partial> = { teams_meetings: { median: 28, range_min: 0, range_max: 80 }, zoom_meetings: { median: 9, range_min: 0, range_max: 40 }, meeting_free: { median: 5, range_min: 0, range_max: 15 }, + support_active: { median: 4, range_min: 0, range_max: 12 }, + support_updates: { median: 120, range_min: 0, range_max: 500 }, + support_public_comments: { median: 80, range_min: 0, range_max: 300 }, + support_private_comments: { median: 40, range_min: 0, range_max: 200 }, + support_solved: { median: 32, range_min: 0, range_max: 120 }, + support_csat: { median: 88, range_min: 0, range_max: 100 }, + support_kb: { median: 0, range_min: 0, range_max: 20 }, }; export function mockTeamBulletSection( @@ -512,7 +519,7 @@ export function mockTeamScenario(teamId = 'bob.park@example.com'): { const teamSeed = hashStr(teamId); const members = mockTeamMemberRowsForTeam(teamId); - const bulletSections = ['task_delivery', 'code_quality', 'estimation', 'ai_adoption', 'collaboration']; + const bulletSections = ['task_delivery', 'code_quality', 'estimation', 'ai_adoption', 'collaboration', 'support']; const bullets: Record = {}; for (const section of bulletSections) { bullets[section] = mockTeamBulletSection(section, teamSeed); diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index d1015da..91fbfb5 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -23,6 +23,7 @@ import { mockKpiPeerMedians, mockPeerCohortStats, mockSectionTrend, + mockSupportBulletSection, } from "./v2/factories"; const wrap = (items: T[]) => ({ @@ -149,6 +150,10 @@ const metricHandlers: Record = { mockTeamBulletSection("ai_adoption", seedOf(teamId), periodDays), ); }, + [METRIC_REGISTRY.TEAM_BULLET_SUPPORT]: (body) => { + const { teamId, periodDays } = parseFilter(body); + return wrap(mockSupportBulletSection(teamId ?? "team", periodDays)); + }, [METRIC_REGISTRY.IC_KPIS]: (body) => { const { personId, periodDays } = parseFilter(body); @@ -191,6 +196,10 @@ const metricHandlers: Record = { mockIcBulletSection("git_output", seedOf(personId), periodDays), ); }, + [METRIC_REGISTRY.IC_BULLET_SUPPORT]: (body) => { + const { personId, periodDays } = parseFilter(body); + return wrap(mockSupportBulletSection(personId ?? "person", periodDays)); + }, [METRIC_REGISTRY.IC_CHART_LOC]: (body) => { const { periodDays } = parseFilter(body); const weeks = Math.max(2, Math.round(periodDays / 7)); diff --git a/src/mocks/v2/factories.ts b/src/mocks/v2/factories.ts index 0963a93..89f0174 100644 --- a/src/mocks/v2/factories.ts +++ b/src/mocks/v2/factories.ts @@ -6,6 +6,63 @@ function hashStr(s: string): number { return Math.abs(h); } +// Support (Zendesk) bullet section. The bare metric_key MUST match the +// `support_bullet_rows.*` entries in catalog-snapshot.json so +// transformBulletMetrics resolves labels/units/thresholds from the wire +// catalog (post-#82 — no compile-in metadata). `support_csat` is a percent +// quality metric (non-scaling) and `support_kb` is always NULL (no data +// yet → renders ComingSoon). +export interface SupportBulletRow { + metric_key: string; + value: number | null; + median: number | null; + range_min: number | null; + range_max: number | null; +} + +const SUPPORT_BULLET_SPEC: ReadonlyArray<{ + metric_key: string; + median: number; + range_min: number; + range_max: number; + scaling: boolean; +}> = [ + { metric_key: "support_active", median: 1, range_min: 0, range_max: 1, scaling: false }, + { metric_key: "support_updates", median: 40, range_min: 0, range_max: 200, scaling: true }, + { metric_key: "support_public_comments", median: 30, range_min: 0, range_max: 150, scaling: true }, + { metric_key: "support_private_comments", median: 20, range_min: 0, range_max: 120, scaling: true }, + { metric_key: "support_solved", median: 15, range_min: 0, range_max: 80, scaling: true }, + { metric_key: "support_csat", median: 88, range_min: 0, range_max: 100, scaling: false }, +]; + +export function mockSupportBulletSection( + seed: string, + periodDays = 30, +): SupportBulletRow[] { + const scale = periodDays / 30; + const rows: SupportBulletRow[] = SUPPORT_BULLET_SPEC.map((s) => { + const r = rng(hashStr(`${seed}|${s.metric_key}`)); + const base = s.median * (0.7 + r() * 0.6); + const factor = s.scaling ? scale : 1; + return { + metric_key: s.metric_key, + value: Math.round(base * factor * 10) / 10, + median: s.median, + range_min: s.range_min, + range_max: s.range_max, + }; + }); + // KB has no data yet → NULL value renders as ComingSoon. + rows.push({ + metric_key: "support_kb", + value: null, + median: null, + range_min: null, + range_max: null, + }); + return rows; +} + function rng(seed: number): () => number { let s = seed || 1; return () => { diff --git a/src/queries/ic-dashboard.ts b/src/queries/ic-dashboard.ts index 7399ca0..d7ebf5e 100644 --- a/src/queries/ic-dashboard.ts +++ b/src/queries/ic-dashboard.ts @@ -92,6 +92,7 @@ const BULLET_SECTIONS = { collaboration: METRIC_REGISTRY.IC_BULLET_COLLAB, ai_adoption: METRIC_REGISTRY.IC_BULLET_AI, git_output: METRIC_REGISTRY.IC_BULLET_GIT, + support: METRIC_REGISTRY.IC_BULLET_SUPPORT, } as const; export type IcBulletSectionId = keyof typeof BULLET_SECTIONS; @@ -213,6 +214,7 @@ export type IcDashboardSection = | "git_output" | "ai_adoption" | "collaboration" + | "support" | "loc_trend" | "delivery_trend" | "time_off"; @@ -224,6 +226,7 @@ export interface IcDashboardData { gitOutput: BulletMetric[]; aiAdoption: BulletMetric[]; collaboration: BulletMetric[]; + support: BulletMetric[]; locTrend: LocDataPoint[]; deliveryTrend: DeliveryDataPoint[]; timeOff: TimeOffNotice | null; @@ -294,6 +297,11 @@ export function useIcDashboardData( metric_id: METRIC_REGISTRY.IC_BULLET_COLLAB, $filter: filter, }, + { + id: "support", + metric_id: METRIC_REGISTRY.IC_BULLET_SUPPORT, + $filter: filter, + }, { id: "loc_trend", metric_id: METRIC_REGISTRY.IC_CHART_LOC, @@ -385,6 +393,14 @@ export function useIcDashboardData( "ic", cat, ), + support: transformBulletMetrics( + get("support") ?? [], + "support", + period, + undefined, + "ic", + cat, + ), locTrend: transformLocTrend( get("loc_trend") ?? [], period, @@ -404,6 +420,7 @@ export function useIcDashboardData( git_output: sectionErrored("git_output"), ai_adoption: sectionErrored("ai_adoption"), collaboration: sectionErrored("collaboration"), + support: sectionErrored("support"), loc_trend: sectionErrored("loc_trend"), delivery_trend: sectionErrored("delivery_trend"), time_off: sectionErrored("time_off"), diff --git a/src/queries/team-metrics.ts b/src/queries/team-metrics.ts index 7104ed7..2817bc0 100644 --- a/src/queries/team-metrics.ts +++ b/src/queries/team-metrics.ts @@ -8,6 +8,7 @@ import type { TeamMember } from "@/types/insight"; export type TeamMetricsSectionId = | "task_delivery" | "collaboration" + | "support" | "ai_adoption" | "git_output"; @@ -17,6 +18,7 @@ const SECTIONS: ReadonlyArray<{ }> = [ { id: "task_delivery", metricId: METRIC_REGISTRY.IC_BULLET_DELIVERY }, { id: "collaboration", metricId: METRIC_REGISTRY.IC_BULLET_COLLAB }, + { id: "support", metricId: METRIC_REGISTRY.IC_BULLET_SUPPORT }, { id: "ai_adoption", metricId: METRIC_REGISTRY.IC_BULLET_AI }, { id: "git_output", metricId: METRIC_REGISTRY.IC_BULLET_GIT }, ]; diff --git a/src/queries/team-view.ts b/src/queries/team-view.ts index 2977a57..7ff29a2 100644 --- a/src/queries/team-view.ts +++ b/src/queries/team-view.ts @@ -121,6 +121,7 @@ const TEAM_BULLET_SECTIONS = { estimation: METRIC_REGISTRY.TEAM_BULLET_DELIVERY, collaboration: METRIC_REGISTRY.TEAM_BULLET_COLLAB, ai_adoption: METRIC_REGISTRY.TEAM_BULLET_AI, + support: METRIC_REGISTRY.TEAM_BULLET_SUPPORT, } as const; export type TeamBulletSectionId = keyof typeof TEAM_BULLET_SECTIONS; diff --git a/src/queries/v2/team-extras.ts b/src/queries/v2/team-extras.ts index abca507..12495a8 100644 --- a/src/queries/v2/team-extras.ts +++ b/src/queries/v2/team-extras.ts @@ -20,6 +20,7 @@ const SECTION_METRIC_IDS = [ { sectionId: "task_delivery", metricId: METRIC_REGISTRY.IC_BULLET_DELIVERY }, { sectionId: "git_output", metricId: METRIC_REGISTRY.IC_BULLET_GIT }, { sectionId: "collaboration", metricId: METRIC_REGISTRY.IC_BULLET_COLLAB }, + { sectionId: "support", metricId: METRIC_REGISTRY.IC_BULLET_SUPPORT }, ] as const; async function fetchMemberBullets( diff --git a/src/screens/ic-dashboard/engineering-dashboard-v2.tsx b/src/screens/ic-dashboard/engineering-dashboard-v2.tsx index bbe3e60..fe85cee 100644 --- a/src/screens/ic-dashboard/engineering-dashboard-v2.tsx +++ b/src/screens/ic-dashboard/engineering-dashboard-v2.tsx @@ -70,6 +70,7 @@ export function EngineeringDashboardV2({ code_quality: orderRowsForSection("code_quality", data?.codeQuality ?? []), collaboration: orderRowsForSection("collaboration", data?.collaboration ?? []), ai_adoption: orderRowsForSection("ai_adoption", data?.aiAdoption ?? []), + support: orderRowsForSection("support", data?.support ?? []), }; const heroSections = IC_HERO_SECTIONS.map((s) => ({ diff --git a/src/screens/ic-dashboard/engineering-dashboard.tsx b/src/screens/ic-dashboard/engineering-dashboard.tsx index 73fb660..bc915a7 100644 --- a/src/screens/ic-dashboard/engineering-dashboard.tsx +++ b/src/screens/ic-dashboard/engineering-dashboard.tsx @@ -230,6 +230,24 @@ export function EngineeringDashboard({ + +
+ dashQ.refetch()} + {...sectionState("support")} + /> +
+
+ ({ diff --git a/src/screens/team-view.tsx b/src/screens/team-view.tsx index 551838d..668b571 100644 --- a/src/screens/team-view.tsx +++ b/src/screens/team-view.tsx @@ -150,6 +150,13 @@ export function TeamViewScreen({ teamId, viewerEmail }: TeamViewScreenProps) { period, dateRange, ); + const supportQ = useTeamBulletSection( + "support", + teamId, + teamSize, + period, + dateRange, + ); const aiQ = useTeamBulletSection( "ai_adoption", teamId, @@ -188,6 +195,7 @@ export function TeamViewScreen({ teamId, viewerEmail }: TeamViewScreenProps) { else if (sid === "code_quality") void qualityQ.refetch(); else if (sid === "estimation") void estimationQ.refetch(); else if (sid === "collaboration") void collabQ.refetch(); + else if (sid === "support") void supportQ.refetch(); else void aiQ.refetch(); }; @@ -268,6 +276,7 @@ export function TeamViewScreen({ teamId, viewerEmail }: TeamViewScreenProps) { estimation: estimationQ.data, ai_adoption: aiMetrics, collaboration: collabQ.data, + support: supportQ.data, }} status={{ task_delivery: bulletStatus(taskQ), @@ -275,6 +284,7 @@ export function TeamViewScreen({ teamId, viewerEmail }: TeamViewScreenProps) { estimation: bulletStatus(estimationQ), ai_adoption: bulletStatus(aiQ), collaboration: bulletStatus(collabQ), + support: bulletStatus(supportQ), }} viewMode={viewMode} onDrillClick={handleDrillClick}