@schandlergarcia/sf-web-components 1.7.0 → 1.8.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/dist/components/library/cards/ActionList.d.ts +10 -10
- package/dist/components/library/cards/ActionList.js +2 -3
- package/dist/components/library/cards/ActionList.js.map +1 -1
- package/dist/components/library/cards/ActivityCard.d.ts +18 -5
- package/dist/components/library/cards/ActivityCard.js +3 -4
- package/dist/components/library/cards/ActivityCard.js.map +1 -1
- package/dist/components/library/cards/BaseCard.d.ts +30 -24
- package/dist/components/library/cards/BaseCard.js +2 -3
- package/dist/components/library/cards/BaseCard.js.map +1 -1
- package/dist/components/library/cards/CalloutCard.d.ts +11 -9
- package/dist/components/library/cards/CalloutCard.js +2 -3
- package/dist/components/library/cards/CalloutCard.js.map +1 -1
- package/dist/components/library/cards/ChartCard.d.ts +29 -17
- package/dist/components/library/cards/ChartCard.js +13 -14
- package/dist/components/library/cards/ChartCard.js.map +1 -1
- package/dist/components/library/cards/FeedPanel.d.ts +12 -11
- package/dist/components/library/cards/FeedPanel.js +3 -4
- package/dist/components/library/cards/FeedPanel.js.map +1 -1
- package/dist/components/library/cards/ListCard.d.ts +33 -20
- package/dist/components/library/cards/ListCard.js +35 -35
- package/dist/components/library/cards/ListCard.js.map +1 -1
- package/dist/components/library/cards/MetricCard.d.ts +23 -17
- package/dist/components/library/cards/MetricCard.js +10 -11
- package/dist/components/library/cards/MetricCard.js.map +1 -1
- package/dist/components/library/cards/MetricsStrip.d.ts +11 -11
- package/dist/components/library/cards/MetricsStrip.js +1 -1
- package/dist/components/library/cards/MetricsStrip.js.map +1 -1
- package/dist/components/library/cards/SectionCard.d.ts +17 -12
- package/dist/components/library/cards/SectionCard.js +18 -19
- package/dist/components/library/cards/SectionCard.js.map +1 -1
- package/dist/components/library/cards/SemanticMetricCard.d.ts +15 -20
- package/dist/components/library/cards/SemanticMetricCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/SemanticTableCard.d.ts +13 -18
- package/dist/components/library/cards/SemanticTableCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/StatusCard.d.ts +29 -15
- package/dist/components/library/cards/StatusCard.js +16 -17
- package/dist/components/library/cards/StatusCard.js.map +1 -1
- package/dist/components/library/cards/TableCard.d.ts +40 -23
- package/dist/components/library/cards/TableCard.js +59 -59
- package/dist/components/library/cards/TableCard.js.map +1 -1
- package/dist/components/library/cards/WidgetCard.d.ts +19 -11
- package/dist/components/library/cards/WidgetCard.js.map +1 -1
- package/dist/components/library/charts/D3Chart.d.ts +23 -16
- package/dist/components/library/charts/D3Chart.js.map +1 -1
- package/dist/components/library/charts/D3ChartTemplates.d.ts +33 -3
- package/dist/components/library/charts/D3ChartTemplates.js +7 -7
- package/dist/components/library/charts/D3ChartTemplates.js.map +1 -1
- package/dist/components/library/charts/GeoMap.d.ts +81 -18
- package/dist/components/library/charts/GeoMap.js +28 -26
- package/dist/components/library/charts/GeoMap.js.map +1 -1
- package/dist/components/library/filters/FilterBar.d.ts +18 -8
- package/dist/components/library/filters/FilterBar.js +2 -3
- package/dist/components/library/filters/FilterBar.js.map +1 -1
- package/dist/components/library/filters/SearchFilter.d.ts +7 -6
- package/dist/components/library/filters/SearchFilter.js +2 -3
- package/dist/components/library/filters/SearchFilter.js.map +1 -1
- package/dist/components/library/filters/SelectFilter.d.ts +13 -7
- package/dist/components/library/filters/SelectFilter.js +2 -3
- package/dist/components/library/filters/SelectFilter.js.map +1 -1
- package/dist/components/library/filters/ToggleFilter.d.ts +7 -5
- package/dist/components/library/filters/ToggleFilter.js +2 -3
- package/dist/components/library/filters/ToggleFilter.js.map +1 -1
- package/dist/components/library/forms/FormField.d.ts +10 -8
- package/dist/components/library/forms/FormField.js +3 -4
- package/dist/components/library/forms/FormField.js.map +1 -1
- package/dist/components/library/forms/FormModal.d.ts +23 -14
- package/dist/components/library/forms/FormModal.js.map +1 -1
- package/dist/components/library/forms/FormRenderer.d.ts +29 -9
- package/dist/components/library/forms/FormRenderer.js +6 -7
- package/dist/components/library/forms/FormRenderer.js.map +1 -1
- package/dist/components/library/forms/FormSection.d.ts +10 -8
- package/dist/components/library/forms/FormSection.js +2 -3
- package/dist/components/library/forms/FormSection.js.map +1 -1
- package/dist/components/library/forms/index.d.ts +5 -0
- package/dist/components/library/forms/useFormState.d.ts +23 -15
- package/dist/components/library/forms/useFormState.js +53 -47
- package/dist/components/library/forms/useFormState.js.map +1 -1
- package/dist/components/library/layout/PageContainer.d.ts +6 -4
- package/dist/components/library/layout/PageContainer.js +4 -5
- package/dist/components/library/layout/PageContainer.js.map +1 -1
- package/package.json +4 -1
- package/src/components/library/cards/{ActionList.jsx → ActionList.tsx} +13 -9
- package/src/components/library/cards/{ActivityCard.jsx → ActivityCard.tsx} +33 -4
- package/src/components/library/cards/{BaseCard.jsx → BaseCard.tsx} +33 -6
- package/src/components/library/cards/{CalloutCard.jsx → CalloutCard.tsx} +12 -10
- package/src/components/library/cards/{ChartCard.jsx → ChartCard.tsx} +32 -6
- package/src/components/library/cards/{FeedPanel.jsx → FeedPanel.tsx} +13 -2
- package/src/components/library/cards/{ListCard.jsx → ListCard.tsx} +43 -7
- package/src/components/library/cards/{MetricCard.jsx → MetricCard.tsx} +25 -6
- package/src/components/library/cards/{MetricsStrip.jsx → MetricsStrip.tsx} +22 -12
- package/src/components/library/cards/{SectionCard.jsx → SectionCard.tsx} +27 -8
- package/src/components/library/cards/{SemanticMetricCard.jsx → SemanticMetricCard.tsx} +17 -5
- package/src/components/library/cards/{SemanticMetricCardWithLoading.jsx → SemanticMetricCardWithLoading.tsx} +9 -3
- package/src/components/library/cards/{SemanticTableCard.jsx → SemanticTableCard.tsx} +14 -3
- package/src/components/library/cards/{SemanticTableCardWithLoading.jsx → SemanticTableCardWithLoading.tsx} +9 -5
- package/src/components/library/cards/{StatusCard.jsx → StatusCard.tsx} +61 -12
- package/src/components/library/cards/{TableCard.jsx → TableCard.tsx} +51 -12
- package/src/components/library/cards/{WidgetCard.jsx → WidgetCard.tsx} +28 -5
- package/src/components/library/charts/{D3Chart.jsx → D3Chart.tsx} +27 -7
- package/src/components/library/charts/{D3ChartTemplates.jsx → D3ChartTemplates.tsx} +60 -28
- package/src/components/library/charts/{GeoMap.jsx → GeoMap.tsx} +106 -17
- package/src/components/library/filters/{FilterBar.jsx → FilterBar.tsx} +21 -11
- package/src/components/library/filters/{SearchFilter.jsx → SearchFilter.tsx} +8 -2
- package/src/components/library/filters/{SelectFilter.jsx → SelectFilter.tsx} +15 -8
- package/src/components/library/filters/{ToggleFilter.jsx → ToggleFilter.tsx} +7 -6
- package/src/components/library/forms/{FormField.jsx → FormField.tsx} +91 -45
- package/src/components/library/forms/{FormModal.jsx → FormModal.tsx} +21 -20
- package/src/components/library/forms/{FormRenderer.jsx → FormRenderer.tsx} +32 -10
- package/src/components/library/forms/{FormSection.jsx → FormSection.tsx} +13 -7
- package/src/components/library/forms/index.tsx +11 -0
- package/src/components/library/forms/{useFormState.jsx → useFormState.tsx} +43 -23
- package/src/components/library/layout/{PageContainer.jsx → PageContainer.tsx} +6 -3
- package/src/components/library/forms/index.jsx +0 -5
- /package/src/components/library/filters/{index.jsx → index.ts} +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import SemanticMetricCard from "./SemanticMetricCard";
|
|
2
|
+
import SemanticMetricCard, { SemanticMetricCardProps } from "./SemanticMetricCard";
|
|
3
|
+
|
|
4
|
+
export interface SemanticMetricCardWithLoadingProps extends SemanticMetricCardProps {
|
|
5
|
+
simulateInitialLoad?: boolean;
|
|
6
|
+
minInitialDelayMs?: number;
|
|
7
|
+
maxInitialDelayMs?: number;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
export default function SemanticMetricCardWithLoading({
|
|
5
12
|
simulateInitialLoad = true,
|
|
@@ -7,7 +14,7 @@ export default function SemanticMetricCardWithLoading({
|
|
|
7
14
|
maxInitialDelayMs = 900,
|
|
8
15
|
loading: loadingProp,
|
|
9
16
|
...props
|
|
10
|
-
}) {
|
|
17
|
+
}: SemanticMetricCardWithLoadingProps) {
|
|
11
18
|
const [loading, setLoading] = React.useState(Boolean(simulateInitialLoad));
|
|
12
19
|
|
|
13
20
|
React.useEffect(() => {
|
|
@@ -20,4 +27,3 @@ export default function SemanticMetricCardWithLoading({
|
|
|
20
27
|
|
|
21
28
|
return <SemanticMetricCard loading={loadingProp ?? loading} {...props} />;
|
|
22
29
|
}
|
|
23
|
-
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import TableCard from "./TableCard";
|
|
2
|
+
import TableCard, { TableCardProps, TableColumn } from "./TableCard";
|
|
3
3
|
import { getSemanticDataset } from "../data/chartDataProvider";
|
|
4
4
|
|
|
5
|
+
export interface SemanticTableCardProps extends Omit<TableCardProps, "data" | "columns"> {
|
|
6
|
+
semanticId: string;
|
|
7
|
+
dataOverride?: unknown[];
|
|
8
|
+
columnsOverride?: TableColumn[];
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
striped?: boolean;
|
|
11
|
+
isDark?: boolean;
|
|
12
|
+
simulateInitialLoad?: boolean;
|
|
13
|
+
minInitialDelayMs?: number;
|
|
14
|
+
maxInitialDelayMs?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
export default function SemanticTableCard({
|
|
6
18
|
semanticId,
|
|
7
19
|
dataOverride,
|
|
@@ -19,7 +31,7 @@ export default function SemanticTableCard({
|
|
|
19
31
|
minInitialDelayMs,
|
|
20
32
|
maxInitialDelayMs,
|
|
21
33
|
...rest
|
|
22
|
-
}) {
|
|
34
|
+
}: SemanticTableCardProps) {
|
|
23
35
|
const ds = React.useMemo(() => getSemanticDataset(semanticId), [semanticId]);
|
|
24
36
|
const table = ds?.table;
|
|
25
37
|
|
|
@@ -45,4 +57,3 @@ export default function SemanticTableCard({
|
|
|
45
57
|
/>
|
|
46
58
|
);
|
|
47
59
|
}
|
|
48
|
-
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import SemanticTableCard, { SemanticTableCardProps } from "./SemanticTableCard";
|
|
2
|
+
|
|
3
|
+
export interface SemanticTableCardWithLoadingProps extends SemanticTableCardProps {
|
|
4
|
+
simulateInitialLoad?: boolean;
|
|
5
|
+
minInitialDelayMs?: number;
|
|
6
|
+
maxInitialDelayMs?: number;
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
}
|
|
3
9
|
|
|
4
10
|
export default function SemanticTableCardWithLoading({
|
|
5
11
|
simulateInitialLoad = true,
|
|
@@ -7,8 +13,7 @@ export default function SemanticTableCardWithLoading({
|
|
|
7
13
|
maxInitialDelayMs = 900,
|
|
8
14
|
loading: loadingProp,
|
|
9
15
|
...props
|
|
10
|
-
}) {
|
|
11
|
-
// Leverage TableCard's simulateInitialLoad by default; also allow explicit loading override.
|
|
16
|
+
}: SemanticTableCardWithLoadingProps) {
|
|
12
17
|
return (
|
|
13
18
|
<SemanticTableCard
|
|
14
19
|
{...props}
|
|
@@ -19,4 +24,3 @@ export default function SemanticTableCardWithLoading({
|
|
|
19
24
|
/>
|
|
20
25
|
);
|
|
21
26
|
}
|
|
22
|
-
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
2
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
3
|
import UIText from "../ui/Text";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type Status = "operational" | "degraded" | "outage" | "maintenance";
|
|
6
|
+
type Layout = "list" | "grid" | "timeline";
|
|
7
|
+
|
|
8
|
+
interface StatusMeta {
|
|
9
|
+
label: string;
|
|
10
|
+
dot: string;
|
|
11
|
+
chip: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const STATUS_META: Record<Status, StatusMeta> = {
|
|
6
15
|
operational: {
|
|
7
16
|
label: "Operational",
|
|
8
17
|
dot: "bg-emerald-500",
|
|
@@ -25,15 +34,15 @@ const STATUS_META = {
|
|
|
25
34
|
}
|
|
26
35
|
};
|
|
27
36
|
|
|
28
|
-
function normalizeStatus(status) {
|
|
37
|
+
function normalizeStatus(status: string | undefined): Status {
|
|
29
38
|
const s = String(status ?? "operational").toLowerCase();
|
|
30
39
|
if (s === "ok" || s === "healthy" || s === "up") return "operational";
|
|
31
40
|
if (s === "warn" || s === "warning" || s === "partial") return "degraded";
|
|
32
41
|
if (s === "down" || s === "critical") return "outage";
|
|
33
|
-
return STATUS_META[s] ? s : "operational";
|
|
42
|
+
return STATUS_META[s as Status] ? (s as Status) : "operational";
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
function formatTimestamp(ts) {
|
|
45
|
+
function formatTimestamp(ts: string | Date | undefined): string {
|
|
37
46
|
if (!ts) return "";
|
|
38
47
|
try {
|
|
39
48
|
const d = ts instanceof Date ? ts : new Date(ts);
|
|
@@ -44,7 +53,11 @@ function formatTimestamp(ts) {
|
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
interface StatusChipProps {
|
|
57
|
+
status: string | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function StatusChip({ status }: StatusChipProps) {
|
|
48
61
|
const key = normalizeStatus(status);
|
|
49
62
|
const meta = STATUS_META[key];
|
|
50
63
|
return (
|
|
@@ -55,7 +68,23 @@ function StatusChip({ status }) {
|
|
|
55
68
|
);
|
|
56
69
|
}
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
export interface StatusItem {
|
|
72
|
+
id?: string | number;
|
|
73
|
+
title?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
status?: string;
|
|
77
|
+
timestamp?: string | Date;
|
|
78
|
+
value?: string | number;
|
|
79
|
+
unit?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ItemRowProps {
|
|
83
|
+
item: StatusItem;
|
|
84
|
+
showTimestamp: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ItemRow({ item, showTimestamp }: ItemRowProps) {
|
|
59
88
|
const name = item?.title ?? item?.name ?? "Item";
|
|
60
89
|
const desc = item?.description;
|
|
61
90
|
const value = item?.value;
|
|
@@ -89,7 +118,12 @@ function ItemRow({ item, showTimestamp }) {
|
|
|
89
118
|
);
|
|
90
119
|
}
|
|
91
120
|
|
|
92
|
-
|
|
121
|
+
interface TimelineProps {
|
|
122
|
+
items: StatusItem[];
|
|
123
|
+
showTimestamp: boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Timeline({ items, showTimestamp }: TimelineProps) {
|
|
93
127
|
return (
|
|
94
128
|
<div className="relative mt-4">
|
|
95
129
|
<div className="absolute left-3 top-0 h-full w-px bg-slate-200 dark:bg-slate-800" aria-hidden="true" />
|
|
@@ -110,6 +144,21 @@ function Timeline({ items, showTimestamp }) {
|
|
|
110
144
|
);
|
|
111
145
|
}
|
|
112
146
|
|
|
147
|
+
export interface StatusCardProps extends Omit<BaseCardProps, "variant" | "header" | "body"> {
|
|
148
|
+
title?: string;
|
|
149
|
+
subtitle?: string;
|
|
150
|
+
status?: string;
|
|
151
|
+
items?: StatusItem[];
|
|
152
|
+
layout?: Layout;
|
|
153
|
+
showProgress?: boolean;
|
|
154
|
+
showTimestamp?: boolean;
|
|
155
|
+
actions?: React.ReactNode;
|
|
156
|
+
loading?: boolean;
|
|
157
|
+
error?: string | Error;
|
|
158
|
+
emptyMessage?: string;
|
|
159
|
+
maxBodyHeight?: string | number;
|
|
160
|
+
}
|
|
161
|
+
|
|
113
162
|
export default function StatusCard({
|
|
114
163
|
title,
|
|
115
164
|
subtitle,
|
|
@@ -124,7 +173,7 @@ export default function StatusCard({
|
|
|
124
173
|
emptyMessage = "No status items.",
|
|
125
174
|
maxBodyHeight,
|
|
126
175
|
...cardProps
|
|
127
|
-
}) {
|
|
176
|
+
}: StatusCardProps) {
|
|
128
177
|
const overall = normalizeStatus(status);
|
|
129
178
|
const header = (
|
|
130
179
|
<div className="flex items-start justify-between gap-3">
|
|
@@ -166,7 +215,9 @@ export default function StatusCard({
|
|
|
166
215
|
const okCount = items.filter((it) => normalizeStatus(it?.status) === "operational").length;
|
|
167
216
|
const percentOk = total > 0 ? Math.round((okCount / total) * 100) : 100;
|
|
168
217
|
|
|
169
|
-
const scrollStyle = maxBodyHeight
|
|
218
|
+
const scrollStyle: React.CSSProperties = maxBodyHeight
|
|
219
|
+
? { maxHeight: typeof maxBodyHeight === "number" ? `${maxBodyHeight}px` : maxBodyHeight, overflowY: "auto" }
|
|
220
|
+
: {};
|
|
170
221
|
|
|
171
222
|
const itemsContent = loading ? (
|
|
172
223
|
<div className="space-y-3">
|
|
@@ -216,5 +267,3 @@ export default function StatusCard({
|
|
|
216
267
|
|
|
217
268
|
return <BaseCard variant="status" header={header} body={body} {...cardProps} />;
|
|
218
269
|
}
|
|
219
|
-
|
|
220
|
-
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
2
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
3
|
import UIInput from "../ui/UIInput";
|
|
4
4
|
import UIButton from "../ui/UIButton";
|
|
5
5
|
import UIText from "../ui/Text";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type ColumnType = "currency" | "percentage" | "number" | "text";
|
|
8
|
+
|
|
9
|
+
function defaultTypeFormat(type: ColumnType | undefined, value: unknown): string {
|
|
8
10
|
if (value == null) return "";
|
|
9
11
|
if (!type) return String(value);
|
|
10
12
|
|
|
@@ -23,7 +25,7 @@ function defaultTypeFormat(type, value) {
|
|
|
23
25
|
return String(value);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
function stableSort(data, compare) {
|
|
28
|
+
function stableSort<T>(data: T[], compare: (a: T, b: T) => number): T[] {
|
|
27
29
|
return data
|
|
28
30
|
.map((item, idx) => ({ item, idx }))
|
|
29
31
|
.sort((a, b) => {
|
|
@@ -33,6 +35,44 @@ function stableSort(data, compare) {
|
|
|
33
35
|
.map((x) => x.item);
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
export interface TableColumn {
|
|
39
|
+
key: string;
|
|
40
|
+
label: string;
|
|
41
|
+
type?: ColumnType;
|
|
42
|
+
sortable?: boolean;
|
|
43
|
+
mono?: boolean;
|
|
44
|
+
className?: string;
|
|
45
|
+
render?: (value: unknown, row: Record<string, unknown>) => React.ReactNode;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SortState {
|
|
49
|
+
key: string;
|
|
50
|
+
direction: "asc" | "desc";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TableCardProps extends Omit<BaseCardProps, "variant" | "header" | "body"> {
|
|
54
|
+
data?: Record<string, unknown>[];
|
|
55
|
+
columns?: TableColumn[];
|
|
56
|
+
title?: string;
|
|
57
|
+
subtitle?: string;
|
|
58
|
+
searchable?: boolean;
|
|
59
|
+
sortable?: boolean;
|
|
60
|
+
paginated?: boolean;
|
|
61
|
+
selectable?: boolean;
|
|
62
|
+
pageSize?: number;
|
|
63
|
+
actions?: React.ReactNode;
|
|
64
|
+
rowActions?: (row: Record<string, unknown>) => React.ReactNode;
|
|
65
|
+
onRowSelect?: (row: Record<string, unknown>) => void;
|
|
66
|
+
onSort?: (sort: SortState) => void;
|
|
67
|
+
onSearch?: (query: string) => void;
|
|
68
|
+
loading?: boolean;
|
|
69
|
+
error?: string | Error;
|
|
70
|
+
emptyMessage?: string;
|
|
71
|
+
simulateInitialLoad?: boolean;
|
|
72
|
+
minInitialDelayMs?: number;
|
|
73
|
+
maxInitialDelayMs?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
export default function TableCard({
|
|
37
77
|
data = [],
|
|
38
78
|
columns = [],
|
|
@@ -55,12 +95,12 @@ export default function TableCard({
|
|
|
55
95
|
minInitialDelayMs = 350,
|
|
56
96
|
maxInitialDelayMs = 900,
|
|
57
97
|
...cardProps
|
|
58
|
-
}) {
|
|
98
|
+
}: TableCardProps) {
|
|
59
99
|
const [query, setQuery] = React.useState("");
|
|
60
|
-
const [sortKey, setSortKey] = React.useState(null);
|
|
61
|
-
const [sortDir, setSortDir] = React.useState("asc");
|
|
100
|
+
const [sortKey, setSortKey] = React.useState<string | null>(null);
|
|
101
|
+
const [sortDir, setSortDir] = React.useState<"asc" | "desc">("asc");
|
|
62
102
|
const [page, setPage] = React.useState(1);
|
|
63
|
-
const [selectedId, setSelectedId] = React.useState(
|
|
103
|
+
const [selectedId, setSelectedId] = React.useState<string | number | undefined>(undefined);
|
|
64
104
|
const [simLoading, setSimLoading] = React.useState(simulateInitialLoad);
|
|
65
105
|
|
|
66
106
|
React.useEffect(() => {
|
|
@@ -163,7 +203,7 @@ export default function TableCard({
|
|
|
163
203
|
);
|
|
164
204
|
}
|
|
165
205
|
|
|
166
|
-
const canSort = (col) => sortable && (col.sortable ?? true);
|
|
206
|
+
const canSort = (col: TableColumn) => sortable && (col.sortable ?? true);
|
|
167
207
|
|
|
168
208
|
return (
|
|
169
209
|
<BaseCard
|
|
@@ -248,11 +288,12 @@ export default function TableCard({
|
|
|
248
288
|
</tr>
|
|
249
289
|
) : (
|
|
250
290
|
pageData.map((row, idx) => {
|
|
251
|
-
const
|
|
291
|
+
const rawId = row?.id;
|
|
292
|
+
const rowId: string | number = typeof rawId === "string" || typeof rawId === "number" ? rawId : idx;
|
|
252
293
|
const selected = selectable && selectedId === rowId;
|
|
253
294
|
return (
|
|
254
295
|
<tr
|
|
255
|
-
key={rowId}
|
|
296
|
+
key={String(rowId)}
|
|
256
297
|
className={[
|
|
257
298
|
"group",
|
|
258
299
|
selectable ? "cursor-pointer" : "",
|
|
@@ -333,5 +374,3 @@ export default function TableCard({
|
|
|
333
374
|
/>
|
|
334
375
|
);
|
|
335
376
|
}
|
|
336
|
-
|
|
337
|
-
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import BaseCard from "./BaseCard";
|
|
2
|
+
import BaseCard, { BaseCardProps } from "./BaseCard";
|
|
3
3
|
import UIText from "../ui/Text";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
export interface WidgetSection {
|
|
6
|
+
id?: string | number;
|
|
7
|
+
title?: string;
|
|
8
|
+
actions?: React.ReactNode;
|
|
9
|
+
content: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SectionProps {
|
|
13
|
+
title?: string;
|
|
14
|
+
actions?: React.ReactNode;
|
|
15
|
+
content: React.ReactNode;
|
|
16
|
+
divided: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Section({ title, actions, content, divided }: SectionProps) {
|
|
6
20
|
return (
|
|
7
21
|
<div className={divided ? "border-t border-slate-200 pt-4 dark:border-slate-800" : ""}>
|
|
8
22
|
{(title || actions) ? (
|
|
@@ -22,6 +36,17 @@ function Section({ title, actions, content, divided }) {
|
|
|
22
36
|
);
|
|
23
37
|
}
|
|
24
38
|
|
|
39
|
+
export interface WidgetCardProps extends Omit<BaseCardProps, "variant" | "header" | "body"> {
|
|
40
|
+
header: React.ReactNode;
|
|
41
|
+
sections?: WidgetSection[];
|
|
42
|
+
footer?: React.ReactNode;
|
|
43
|
+
divided?: boolean;
|
|
44
|
+
collapsible?: boolean;
|
|
45
|
+
defaultExpanded?: boolean;
|
|
46
|
+
loading?: boolean;
|
|
47
|
+
emptyMessage?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
export default function WidgetCard({
|
|
26
51
|
header,
|
|
27
52
|
sections = [],
|
|
@@ -32,7 +57,7 @@ export default function WidgetCard({
|
|
|
32
57
|
loading = false,
|
|
33
58
|
emptyMessage = "No sections.",
|
|
34
59
|
...cardProps
|
|
35
|
-
}) {
|
|
60
|
+
}: WidgetCardProps) {
|
|
36
61
|
const [expanded, setExpanded] = React.useState(defaultExpanded);
|
|
37
62
|
|
|
38
63
|
const hdr = (
|
|
@@ -86,5 +111,3 @@ export default function WidgetCard({
|
|
|
86
111
|
|
|
87
112
|
return <BaseCard variant="widget" header={hdr} body={body} {...cardProps} />;
|
|
88
113
|
}
|
|
89
|
-
|
|
90
|
-
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
interface Dimensions {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface D3ChartProps {
|
|
9
|
+
data: unknown;
|
|
10
|
+
renderChart?: (svg: SVGSVGElement, data: unknown, dims: Dimensions, options: Record<string, unknown>) => void;
|
|
11
|
+
options?: Record<string, unknown>;
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
responsive?: boolean;
|
|
15
|
+
aspectRatio?: number;
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
containerStyle?: React.CSSProperties;
|
|
19
|
+
svgStyle?: React.CSSProperties;
|
|
20
|
+
loading?: boolean;
|
|
21
|
+
error?: string | Error;
|
|
22
|
+
ariaLabel?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
/**
|
|
4
26
|
* Minimal D3 chart host:
|
|
5
27
|
* - Owns the <svg> element
|
|
@@ -21,10 +43,10 @@ export default function D3Chart({
|
|
|
21
43
|
loading = false,
|
|
22
44
|
error,
|
|
23
45
|
ariaLabel = "Chart"
|
|
24
|
-
}) {
|
|
25
|
-
const containerRef = React.useRef(null);
|
|
26
|
-
const svgRef = React.useRef(null);
|
|
27
|
-
const [containerWidth, setContainerWidth] = React.useState(null);
|
|
46
|
+
}: D3ChartProps): React.ReactElement {
|
|
47
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
48
|
+
const svgRef = React.useRef<SVGSVGElement>(null);
|
|
49
|
+
const [containerWidth, setContainerWidth] = React.useState<number | null>(null);
|
|
28
50
|
|
|
29
51
|
React.useEffect(() => {
|
|
30
52
|
if (!responsive) return;
|
|
@@ -53,7 +75,7 @@ export default function D3Chart({
|
|
|
53
75
|
const svgEl = svgRef.current;
|
|
54
76
|
if (!svgEl) return;
|
|
55
77
|
|
|
56
|
-
const dims = {
|
|
78
|
+
const dims: Dimensions = {
|
|
57
79
|
width: computedWidth ?? 0,
|
|
58
80
|
height: computedHeight ?? 0
|
|
59
81
|
};
|
|
@@ -105,5 +127,3 @@ export default function D3Chart({
|
|
|
105
127
|
</div>
|
|
106
128
|
);
|
|
107
129
|
}
|
|
108
|
-
|
|
109
|
-
|
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
import * as d3 from "d3";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface Dimensions {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Margin {
|
|
9
|
+
top: number;
|
|
10
|
+
right: number;
|
|
11
|
+
bottom: number;
|
|
12
|
+
left: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LineChartOptions {
|
|
16
|
+
xKey?: string;
|
|
17
|
+
yKey?: string;
|
|
18
|
+
margin?: Margin;
|
|
19
|
+
stroke?: string;
|
|
20
|
+
strokeWidth?: number;
|
|
21
|
+
showAxes?: boolean;
|
|
22
|
+
showGrid?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GroupedBarChartOptions {
|
|
26
|
+
groups?: string[];
|
|
27
|
+
margin?: Margin;
|
|
28
|
+
xKey?: string;
|
|
29
|
+
colors?: string[];
|
|
30
|
+
barRadius?: number;
|
|
31
|
+
yFormat?: string;
|
|
32
|
+
showGrid?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type DataPoint = Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
function clear(svg: SVGSVGElement): void {
|
|
4
38
|
d3.select(svg).selectAll("*").remove();
|
|
5
39
|
}
|
|
6
40
|
|
|
7
41
|
export const D3ChartTemplates = {
|
|
8
|
-
lineChart(svg, data, dims, opts = {}) {
|
|
42
|
+
lineChart(svg: SVGSVGElement, data: DataPoint[], dims: Dimensions, opts: LineChartOptions = {}): void {
|
|
9
43
|
const {
|
|
10
44
|
xKey = "x",
|
|
11
45
|
yKey = "y",
|
|
@@ -25,11 +59,11 @@ export const D3ChartTemplates = {
|
|
|
25
59
|
|
|
26
60
|
const g = d3.select(svg).attr("viewBox", `0 0 ${width} ${height}`).append("g").attr("transform", `translate(${margin.left},${margin.top})`);
|
|
27
61
|
|
|
28
|
-
const xs = data.map((d) => d?.[xKey]).filter((v) => v != null);
|
|
29
|
-
const ys = data.map((d) => d?.[yKey]).filter((v) => v != null);
|
|
62
|
+
const xs = data.map((d) => d?.[xKey]).filter((v) => v != null) as number[];
|
|
63
|
+
const ys = data.map((d) => d?.[yKey]).filter((v) => v != null) as number[];
|
|
30
64
|
|
|
31
|
-
const xDomain = d3.extent(xs);
|
|
32
|
-
const yDomain = d3.extent(ys);
|
|
65
|
+
const xDomain = d3.extent(xs) as [number, number];
|
|
66
|
+
const yDomain = d3.extent(ys) as [number, number];
|
|
33
67
|
|
|
34
68
|
const x = d3.scaleLinear().domain(xDomain).nice().range([0, innerW]);
|
|
35
69
|
const y = d3.scaleLinear().domain(yDomain).nice().range([innerH, 0]);
|
|
@@ -37,15 +71,15 @@ export const D3ChartTemplates = {
|
|
|
37
71
|
if (showGrid) {
|
|
38
72
|
g.append("g")
|
|
39
73
|
.attr("class", "grid")
|
|
40
|
-
.call(d3.axisLeft(y).ticks(5).tickSize(-innerW).tickFormat(""))
|
|
41
|
-
.call((grid) => grid.selectAll("line").attr("stroke", "currentColor").attr("opacity", 0.12))
|
|
42
|
-
.call((grid) => grid.selectAll("path").attr("stroke", "none"));
|
|
74
|
+
.call(d3.axisLeft(y).ticks(5).tickSize(-innerW).tickFormat(() => ""))
|
|
75
|
+
.call((grid: d3.Selection<SVGGElement, unknown, null, undefined>) => grid.selectAll("line").attr("stroke", "currentColor").attr("opacity", 0.12))
|
|
76
|
+
.call((grid: d3.Selection<SVGGElement, unknown, null, undefined>) => grid.selectAll("path").attr("stroke", "none"));
|
|
43
77
|
}
|
|
44
78
|
|
|
45
79
|
const line = d3
|
|
46
|
-
.line()
|
|
47
|
-
.x((d) => x(d[xKey]))
|
|
48
|
-
.y((d) => y(d[yKey]))
|
|
80
|
+
.line<DataPoint>()
|
|
81
|
+
.x((d) => x(d[xKey] as number))
|
|
82
|
+
.y((d) => y(d[yKey] as number))
|
|
49
83
|
.defined((d) => d?.[xKey] != null && d?.[yKey] != null);
|
|
50
84
|
|
|
51
85
|
g.append("path")
|
|
@@ -59,15 +93,15 @@ export const D3ChartTemplates = {
|
|
|
59
93
|
g.append("g")
|
|
60
94
|
.attr("transform", `translate(0,${innerH})`)
|
|
61
95
|
.call(d3.axisBottom(x).ticks(6))
|
|
62
|
-
.call((ax) => ax.selectAll("text").attr("font-size", 10));
|
|
96
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("font-size", 10));
|
|
63
97
|
|
|
64
98
|
g.append("g")
|
|
65
99
|
.call(d3.axisLeft(y).ticks(5))
|
|
66
|
-
.call((ax) => ax.selectAll("text").attr("font-size", 10));
|
|
100
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("font-size", 10));
|
|
67
101
|
}
|
|
68
102
|
},
|
|
69
103
|
|
|
70
|
-
groupedBarChart(svg, data, dims, opts = {}) {
|
|
104
|
+
groupedBarChart(svg: SVGSVGElement, data: DataPoint[], dims: Dimensions, opts: GroupedBarChartOptions = {}): void {
|
|
71
105
|
const {
|
|
72
106
|
groups = [],
|
|
73
107
|
margin = { top: 20, right: 20, bottom: 40, left: 55 },
|
|
@@ -90,37 +124,35 @@ export const D3ChartTemplates = {
|
|
|
90
124
|
|
|
91
125
|
const groupKeys = groups.length ? groups : Object.keys(data[0] || {}).filter((k) => k !== xKey);
|
|
92
126
|
|
|
93
|
-
const x0 = d3.scaleBand().domain(data.map((d) => d[xKey])).range([0, innerW]).padding(0.3);
|
|
127
|
+
const x0 = d3.scaleBand().domain(data.map((d: DataPoint) => String(d[xKey]))).range([0, innerW]).padding(0.3);
|
|
94
128
|
const x1 = d3.scaleBand().domain(groupKeys).range([0, x0.bandwidth()]).padding(0.05);
|
|
95
|
-
const yMax = d3.max(data, (d) => d3.max(groupKeys, (k) => d[k])) * 1.15;
|
|
129
|
+
const yMax = (d3.max(data, (d: DataPoint) => d3.max(groupKeys, (k: string) => d[k] as number)) ?? 0) * 1.15;
|
|
96
130
|
const y = d3.scaleLinear().domain([0, yMax]).range([innerH, 0]);
|
|
97
131
|
|
|
98
132
|
g.append("g")
|
|
99
133
|
.attr("transform", `translate(0,${innerH})`)
|
|
100
134
|
.call(d3.axisBottom(x0).tickSize(0))
|
|
101
|
-
.call((ax) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "11px"))
|
|
102
|
-
.call((ax) => ax.select(".domain").remove());
|
|
135
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "11px"))
|
|
136
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.select(".domain").remove());
|
|
103
137
|
|
|
104
138
|
g.append("g")
|
|
105
139
|
.call(d3.axisLeft(y).ticks(5).tickFormat(d3.format(yFormat)).tickSize(showGrid ? -innerW : 0))
|
|
106
|
-
.call((ax) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "10px"))
|
|
107
|
-
.call((ax) => ax.selectAll(".tick line").attr("stroke", "#e2e8f0").attr("stroke-dasharray", "2,2"))
|
|
108
|
-
.call((ax) => ax.select(".domain").remove());
|
|
140
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "10px"))
|
|
141
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll(".tick line").attr("stroke", "#e2e8f0").attr("stroke-dasharray", "2,2"))
|
|
142
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.select(".domain").remove());
|
|
109
143
|
|
|
110
144
|
const rows = g.selectAll(".bar-group").data(data).join("g")
|
|
111
145
|
.attr("class", "bar-group")
|
|
112
|
-
.attr("transform", (d) => `translate(${x0(d[xKey])},0)`);
|
|
146
|
+
.attr("transform", (d: DataPoint) => `translate(${x0(String(d[xKey]))},0)`);
|
|
113
147
|
|
|
114
148
|
groupKeys.forEach((key, i) => {
|
|
115
149
|
rows.append("rect")
|
|
116
|
-
.attr("x", x1(key))
|
|
117
|
-
.attr("y", (d) => y(d[key]))
|
|
150
|
+
.attr("x", x1(key) ?? 0)
|
|
151
|
+
.attr("y", (d: DataPoint) => y(d[key] as number))
|
|
118
152
|
.attr("width", x1.bandwidth())
|
|
119
|
-
.attr("height", (d) => innerH - y(d[key]))
|
|
153
|
+
.attr("height", (d: DataPoint) => innerH - y(d[key] as number))
|
|
120
154
|
.attr("rx", barRadius)
|
|
121
155
|
.attr("fill", colors[i % colors.length]);
|
|
122
156
|
});
|
|
123
157
|
},
|
|
124
158
|
};
|
|
125
|
-
|
|
126
|
-
|