Skip to content

Commit 63ebda2

Browse files
authored
Merge pull request #2084 from pnp/KPIControl
New KPIControl
2 parents eb22253 + 58a95df commit 63ebda2

53 files changed

Lines changed: 2280 additions & 14 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
1.02 MB
Loading
707 KB
Loading
Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
# KPIControl
2+
3+
A React component for displaying Key Performance Indicator (KPI) cards in a responsive grid layout. Each card visualizes progress toward a goal with visual indicators, progress bars, and status badges. Two card variants are available: a full-featured card and a compact version.
4+
5+
## Table of Contents
6+
7+
- [Installation](#installation)
8+
- [Basic Usage](#basic-usage)
9+
- [Card Variants](#card-variants)
10+
- [Properties](#properties)
11+
- [Data Structure](#data-structure)
12+
- [Goal Metrics](#goal-metrics)
13+
- [Features](#features)
14+
- [Examples](#examples)
15+
16+
## Installation
17+
18+
```bash
19+
npm install @pnp/spfx-controls-react
20+
```
21+
22+
## Example of KPIControl
23+
24+
![KPIControl Example](../assets/KPIControl.png)
25+
26+
## Basic Usage
27+
28+
```tsx
29+
import * as React from 'react';
30+
import { Kpis } from '@pnp/spfx-controls-react/lib/KPIControl';
31+
32+
const MyKPIComponent: React.FC = () => {
33+
return (
34+
<Kpis skeletonCount={3} />
35+
);
36+
};
37+
```
38+
39+
## Card Variants
40+
41+
The KPIControl offers two card variants to suit different use cases:
42+
43+
### KPICard (Full Version)
44+
45+
The full KPICard displays comprehensive information including footer metrics (Goal, Total Items, % of Total) and a status badge.
46+
47+
```tsx
48+
import * as React from 'react';
49+
import { KPICard } from '@pnp/spfx-controls-react/lib/KPIControl';
50+
import { IKpiCardData, EGoalMetric } from '@pnp/spfx-controls-react/lib/KPIControl';
51+
52+
const MyKPICard: React.FC = () => {
53+
const kpiData: IKpiCardData = {
54+
identifier: 'sales-kpi',
55+
title: 'Sales Revenue',
56+
currentValue: 125000,
57+
goal: 150000,
58+
totalItems: 200000,
59+
description: 'Total sales revenue for Q1',
60+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
61+
};
62+
63+
return (
64+
<KPICard dataCard={kpiData} />
65+
);
66+
};
67+
```
68+
69+
### KPICardCompact (Compact Version)
70+
71+
The compact version displays only essential information: title, goal metric indicator, current value, goal, and progress bar. Ideal for dashboards with limited space or when displaying many KPIs.
72+
73+
```tsx
74+
import * as React from 'react';
75+
import { KPICardCompact } from '@pnp/spfx-controls-react/lib/KPIControl';
76+
import { IKpiCardData, EGoalMetric } from '@pnp/spfx-controls-react/lib/KPIControl';
77+
78+
const MyCompactKPICard: React.FC = () => {
79+
const kpiData: IKpiCardData = {
80+
identifier: 'sales-kpi',
81+
title: 'Sales Revenue',
82+
currentValue: 125000,
83+
goal: 150000,
84+
totalItems: 200000,
85+
description: 'Total sales revenue for Q1',
86+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
87+
};
88+
89+
return (
90+
<KPICardCompact dataCard={kpiData} />
91+
);
92+
};
93+
```
94+
95+
### Comparison
96+
97+
| Feature | KPICard | KPICardCompact |
98+
|---------|---------|----------------|
99+
| Title |||
100+
| Goal Metric Indicator |||
101+
| Current Value / Goal |||
102+
| Progress Bar |||
103+
| Progress Percentage |||
104+
| Footer Metrics (Goal, Total Items, % of Total) |||
105+
| Status Badge (On Track / Off Track) |||
106+
| Glow Effect |||
107+
| Info Tooltip |||
108+
109+
## Properties
110+
111+
### Kpis Component Properties
112+
113+
| Property | Type | Required | Default | Description |
114+
|----------|------|----------|---------|-------------|
115+
| `skeletonCount` | `number` | No | `3` | Number of skeleton cards to display while loading |
116+
| `compact` | `boolean` | No | `false` | When `true`, renders compact KPI cards instead of full cards |
117+
118+
#### Using the compact prop
119+
120+
```tsx
121+
import * as React from 'react';
122+
import { Kpis } from '@pnp/spfx-controls-react/lib/KPIControl';
123+
124+
// Full cards (default)
125+
const FullKpis: React.FC = () => <Kpis />;
126+
127+
// Compact cards
128+
const CompactKpis: React.FC = () => <Kpis compact />;
129+
```
130+
131+
### KPICard & KPICardCompact Component Properties
132+
133+
Both card variants share the same props interface:
134+
135+
| Property | Type | Required | Default | Description |
136+
|----------|------|----------|---------|-------------|
137+
| `dataCard` | `IKpiCardData` | Yes | - | The KPI data to display |
138+
139+
## Data Structure
140+
141+
### IKpiCardData Interface
142+
143+
```typescript
144+
interface IKpiCardData {
145+
/** Unique identifier for the KPI */
146+
identifier: string;
147+
/** Current numeric value of the KPI */
148+
currentValue: number;
149+
/** Goal / target threshold */
150+
goal: number;
151+
/** Total number of items used to compute the KPI */
152+
totalItems: number;
153+
/** Description of the KPI */
154+
description: string;
155+
/** Card title */
156+
title?: string;
157+
/** Goal metric type - determines if lower or higher is better */
158+
goalMetric?: EGoalMetric;
159+
}
160+
```
161+
162+
## Goal Metrics
163+
164+
The `EGoalMetric` enum defines how the KPI progress is evaluated:
165+
166+
```typescript
167+
enum EGoalMetric {
168+
/** Lower values are better (e.g., response time, error rate) */
169+
LOWER_IS_BETTER = 1,
170+
/** Higher values are better (e.g., sales, customer satisfaction) */
171+
HIGHER_IS_BETTER = 2
172+
}
173+
```
174+
175+
### Goal Metric Examples
176+
177+
| Metric Type | Use Case | On Track When |
178+
|-------------|----------|---------------|
179+
| `LOWER_IS_BETTER` | Response Time | Current ≤ Goal |
180+
| `LOWER_IS_BETTER` | Error Rate | Current ≤ Goal |
181+
| `HIGHER_IS_BETTER` | Sales Revenue | Current ≥ Goal |
182+
| `HIGHER_IS_BETTER` | Customer Satisfaction | Current ≥ Goal |
183+
184+
## Features
185+
186+
### Visual Indicators
187+
188+
- **Progress Bar**: Shows progress toward the goal with color coding (green for on track, red for off track)
189+
- **Status Badge**: Checkmark icon when on track, alert icon when off track
190+
- **Glow Effect**: Subtle background glow that changes color based on status
191+
- **Hover Animation**: Cards lift slightly on hover for better interactivity
192+
193+
### Card Information
194+
195+
Each KPI card displays:
196+
197+
- **Title**: The KPI name (uppercase)
198+
- **Current Value**: The current metric value
199+
- **Goal**: The target value
200+
- **Progress Percentage**: How close to the goal
201+
- **Footer Metrics**: Goal, Total Items, and Percentage of Total
202+
- **Description**: Available via info tooltip on the title
203+
204+
### Responsive Layout
205+
206+
The KPI container uses a responsive CSS grid:
207+
208+
- Auto-fills columns with minimum 280px width
209+
- Adapts to tablet screens (≤768px) with 240px minimum
210+
- Single column layout on mobile (≤480px)
211+
212+
### Loading State
213+
214+
When loading, skeleton cards are displayed to provide visual feedback. The number of skeleton cards can be configured via the `skeletonCount` prop.
215+
216+
### Empty State
217+
218+
When no KPIs are configured, a `NoKpisCard` component is displayed with a call-to-action to configure KPIs.
219+
220+
### Error Handling
221+
222+
Errors are displayed using a `ShowError` component with the error message.
223+
224+
## Examples
225+
226+
### Multiple KPIs
227+
228+
```tsx
229+
import * as React from 'react';
230+
import { KPICard } from '@pnp/spfx-controls-react/lib/KPIControl';
231+
import { IKpiCardData, EGoalMetric } from '@pnp/spfx-controls-react/lib/KPIControl';
232+
233+
const kpis: IKpiCardData[] = [
234+
{
235+
identifier: 'kpi-1',
236+
title: 'Sales Revenue',
237+
currentValue: 125000,
238+
goal: 150000,
239+
totalItems: 200000,
240+
description: 'Total sales revenue for Q1',
241+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
242+
},
243+
{
244+
identifier: 'kpi-2',
245+
title: 'Customer Satisfaction',
246+
currentValue: 87,
247+
goal: 90,
248+
totalItems: 100,
249+
description: 'Customer satisfaction score',
250+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
251+
},
252+
{
253+
identifier: 'kpi-3',
254+
title: 'Response Time',
255+
currentValue: 2.5,
256+
goal: 2,
257+
totalItems: 5,
258+
description: 'Average response time in hours',
259+
goalMetric: EGoalMetric.LOWER_IS_BETTER,
260+
},
261+
];
262+
263+
const MyKPIs: React.FC = () => {
264+
return (
265+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '16px' }}>
266+
{kpis.map((kpi) => (
267+
<KPICard key={kpi.identifier} dataCard={kpi} />
268+
))}
269+
</div>
270+
);
271+
};
272+
```
273+
274+
### KPI with Lower is Better Metric
275+
276+
```tsx
277+
const responseTimeKpi: IKpiCardData = {
278+
identifier: 'response-time',
279+
title: 'Response Time',
280+
currentValue: 1.5, // 1.5 hours - below goal, so ON TRACK
281+
goal: 2, // 2 hours target
282+
totalItems: 5,
283+
description: 'Average response time in hours - lower is better',
284+
goalMetric: EGoalMetric.LOWER_IS_BETTER,
285+
};
286+
```
287+
288+
### KPI with Higher is Better Metric
289+
290+
```tsx
291+
const salesKpi: IKpiCardData = {
292+
identifier: 'sales',
293+
title: 'Monthly Sales',
294+
currentValue: 85000, // $85k - below goal, so OFF TRACK
295+
goal: 100000, // $100k target
296+
totalItems: 150000,
297+
description: 'Monthly sales target - higher is better',
298+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
299+
};
300+
```
301+
302+
### Using Compact Cards in a Dashboard
303+
304+
```tsx
305+
import * as React from 'react';
306+
import { KPICardCompact } from '@pnp/spfx-controls-react/lib/KPIControl';
307+
import { IKpiCardData, EGoalMetric } from '@pnp/spfx-controls-react/lib/KPIControl';
308+
309+
const kpis: IKpiCardData[] = [
310+
{
311+
identifier: 'kpi-1',
312+
title: 'Sales Revenue',
313+
currentValue: 125000,
314+
goal: 150000,
315+
totalItems: 200000,
316+
description: 'Total sales revenue for Q1',
317+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
318+
},
319+
{
320+
identifier: 'kpi-2',
321+
title: 'Customer Satisfaction',
322+
currentValue: 87,
323+
goal: 90,
324+
totalItems: 100,
325+
description: 'Customer satisfaction score',
326+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
327+
},
328+
{
329+
identifier: 'kpi-3',
330+
title: 'Response Time',
331+
currentValue: 1.5,
332+
goal: 2,
333+
totalItems: 5,
334+
description: 'Average response time in hours',
335+
goalMetric: EGoalMetric.LOWER_IS_BETTER,
336+
},
337+
];
338+
339+
const MyCompactKPIs: React.FC = () => {
340+
return (
341+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '16px' }}>
342+
{kpis.map((kpi) => (
343+
<KPICardCompact key={kpi.identifier} dataCard={kpi} />
344+
))}
345+
</div>
346+
);
347+
};
348+
```
349+
350+
### Mixing Full and Compact Cards
351+
352+
You can use both card types in the same dashboard for different purposes:
353+
354+
```tsx
355+
import * as React from 'react';
356+
import { KPICard, KPICardCompact } from '@pnp/spfx-controls-react/lib/KPIControl';
357+
import { IKpiCardData, EGoalMetric } from '@pnp/spfx-controls-react/lib/KPIControl';
358+
359+
const primaryKpi: IKpiCardData = {
360+
identifier: 'primary-kpi',
361+
title: 'Revenue',
362+
currentValue: 125000,
363+
goal: 150000,
364+
totalItems: 200000,
365+
description: 'Primary KPI with full details',
366+
goalMetric: EGoalMetric.HIGHER_IS_BETTER,
367+
};
368+
369+
const secondaryKpis: IKpiCardData[] = [
370+
// ... secondary KPIs
371+
];
372+
373+
const MixedDashboard: React.FC = () => {
374+
return (
375+
<div>
376+
{/* Primary KPI with full details */}
377+
<KPICard dataCard={primaryKpi} />
378+
379+
{/* Secondary KPIs in compact format */}
380+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px', marginTop: '16px' }}>
381+
{secondaryKpis.map((kpi) => (
382+
<KPICardCompact key={kpi.identifier} dataCard={kpi} />
383+
))}
384+
</div>
385+
</div>
386+
);
387+
};
388+
```

src/KPIControl.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './controls/KPIControl';
2+

0 commit comments

Comments
 (0)