@dashadmin/dash-reports 1.3.73 → 1.3.74-srcfree.2

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.
Files changed (75) hide show
  1. package/dist/ReportFilters.d.ts +9 -0
  2. package/dist/ReportFiltersDrawer.d.ts +12 -0
  3. package/dist/ReportPage.d.ts +9 -0
  4. package/dist/ReportsDashboard.d.ts +7 -0
  5. package/dist/ReportsIndex.d.ts +7 -0
  6. package/dist/ReportsModule.d.ts +7 -0
  7. package/dist/TenantDashboard.d.ts +12 -0
  8. package/dist/adapters/mui/BarWidget.d.ts +3 -0
  9. package/dist/adapters/mui/DoughnutWidget.d.ts +3 -0
  10. package/dist/adapters/mui/FunnelWidget.d.ts +3 -0
  11. package/dist/adapters/mui/KpiWidget.d.ts +3 -0
  12. package/dist/adapters/mui/LineWidget.d.ts +3 -0
  13. package/dist/adapters/mui/PieWidget.d.ts +3 -0
  14. package/dist/adapters/mui/PreviewWidget.d.ts +10 -0
  15. package/dist/adapters/mui/TableWidget.d.ts +16 -0
  16. package/dist/adapters/mui/chartSetup.d.ts +2 -0
  17. package/dist/adapters/mui/chartTheme.d.ts +71 -0
  18. package/dist/adapters/mui/index.d.ts +12 -0
  19. package/dist/adapters/mui/palette.d.ts +9 -0
  20. package/dist/adapters/mui/useChartData.d.ts +14 -0
  21. package/dist/core/formatBucket.d.ts +4 -0
  22. package/dist/core/label.d.ts +3 -0
  23. package/dist/core/queryParams.d.ts +3 -0
  24. package/dist/core/types.d.ts +94 -0
  25. package/dist/core/useDashboard.d.ts +50 -0
  26. package/dist/core/useReportExport.d.ts +7 -0
  27. package/dist/core/useReportList.d.ts +7 -0
  28. package/dist/core/useReportPreview.d.ts +7 -0
  29. package/dist/core/useReportSeries.d.ts +8 -0
  30. package/dist/core/useReportSpec.d.ts +9 -0
  31. package/dist/core/useReportTable.d.ts +10 -0
  32. package/dist/core/visualizationLayout.d.ts +13 -0
  33. package/dist/core/visualizationRegistry.d.ts +14 -0
  34. package/dist/createReportsResource.d.ts +40 -0
  35. package/dist/i18n/en.d.ts +83 -0
  36. package/dist/i18n/es.d.ts +83 -0
  37. package/{src/index.tsx → dist/index.d.ts} +3 -31
  38. package/package.json +13 -10
  39. package/src/ReportFilters.tsx +0 -145
  40. package/src/ReportFiltersDrawer.tsx +0 -73
  41. package/src/ReportPage.tsx +0 -280
  42. package/src/ReportsDashboard.tsx +0 -61
  43. package/src/ReportsIndex.tsx +0 -113
  44. package/src/ReportsModule.tsx +0 -42
  45. package/src/TenantDashboard.tsx +0 -290
  46. package/src/adapters/mui/BarWidget.tsx +0 -66
  47. package/src/adapters/mui/DoughnutWidget.tsx +0 -112
  48. package/src/adapters/mui/FunnelWidget.tsx +0 -108
  49. package/src/adapters/mui/KpiWidget.tsx +0 -123
  50. package/src/adapters/mui/LineWidget.tsx +0 -70
  51. package/src/adapters/mui/PieWidget.tsx +0 -16
  52. package/src/adapters/mui/PreviewWidget.tsx +0 -123
  53. package/src/adapters/mui/TableWidget.tsx +0 -141
  54. package/src/adapters/mui/chartSetup.ts +0 -53
  55. package/src/adapters/mui/chartTheme.ts +0 -84
  56. package/src/adapters/mui/index.ts +0 -29
  57. package/src/adapters/mui/palette.ts +0 -85
  58. package/src/adapters/mui/useChartData.ts +0 -62
  59. package/src/core/formatBucket.ts +0 -73
  60. package/src/core/label.ts +0 -28
  61. package/src/core/queryParams.ts +0 -27
  62. package/src/core/types.ts +0 -157
  63. package/src/core/useDashboard.ts +0 -161
  64. package/src/core/useReportExport.ts +0 -68
  65. package/src/core/useReportList.ts +0 -51
  66. package/src/core/useReportPreview.ts +0 -68
  67. package/src/core/useReportSeries.ts +0 -59
  68. package/src/core/useReportSpec.ts +0 -79
  69. package/src/core/useReportTable.ts +0 -71
  70. package/src/core/visualizationLayout.ts +0 -34
  71. package/src/core/visualizationRegistry.ts +0 -39
  72. package/src/createReportsResource.tsx +0 -81
  73. package/src/i18n/en.ts +0 -95
  74. package/src/i18n/es.ts +0 -84
  75. /package/{src/i18n/index.ts → dist/i18n/index.d.ts} +0 -0
@@ -1,161 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import type { ReportMetric, ReportResult, VisualizationType } from './types';
4
-
5
- export interface DashboardPeriod {
6
- id: string;
7
- label: string;
8
- start: string;
9
- end: string;
10
- isOpen: boolean;
11
- }
12
-
13
- export interface DashboardWidget {
14
- key: string;
15
- reportKey: string;
16
- title: string;
17
- titleText?: string;
18
- pivot?: string;
19
- metrics?: string[];
20
- visualization?: VisualizationType;
21
- size?: 'sm' | 'md' | 'lg' | 'full';
22
- result?: ReportResult & { metrics?: ReportMetric[] };
23
- error?: string;
24
- }
25
-
26
- export interface DashboardTenant {
27
- tenant: { id: string; name: string };
28
- period: DashboardPeriod | null;
29
- }
30
-
31
- /**
32
- * The tenants whose dashboards this user should see, each with its period.
33
- *
34
- * One cheap call so the page knows how many tenant panels to mount before any
35
- * of them fetches data — a TenancyAdmin sees one panel per restaurant, a
36
- * tenant user sees exactly one.
37
- */
38
- export function useDashboardTenants() {
39
- const axios = useAxios();
40
- // See useReportList: useAxios() is unmemoized and must stay out of deps.
41
- const axiosRef = useRef(axios);
42
- axiosRef.current = axios;
43
-
44
- const [tenants, setTenants] = useState<DashboardTenant[]>([]);
45
- const [widgets, setWidgets] = useState<DashboardWidget[]>([]);
46
- const [loading, setLoading] = useState(true);
47
- const [error, setError] = useState<Error | null>(null);
48
-
49
- useEffect(() => {
50
- let alive = true;
51
-
52
- axiosRef.current
53
- .get('report/dashboard')
54
- .then((response: { data: { data: DashboardTenant[]; widgets: DashboardWidget[] } }) => {
55
- if (!alive) return;
56
- setTenants(response.data?.data ?? []);
57
- setWidgets(response.data?.widgets ?? []);
58
- })
59
- .catch((e: Error) => {
60
- if (alive) setError(e);
61
- })
62
- .finally(() => {
63
- if (alive) setLoading(false);
64
- });
65
-
66
- return () => {
67
- alive = false;
68
- };
69
- // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, []);
71
-
72
- return { tenants, widgets, loading, error };
73
- }
74
-
75
- export interface DashboardData {
76
- tenantId: string;
77
- period: DashboardPeriod;
78
- /** 'live' | 'live-no-snapshot' | 'snapshot' — see the backend controller. */
79
- source: string;
80
- widgets: DashboardWidget[];
81
- }
82
-
83
- /**
84
- * One tenant's widgets for one period.
85
- *
86
- * Fetched per tenant rather than in one batch so panels load in parallel and
87
- * fail independently — a tenant whose data errors shows one broken panel, not
88
- * an empty dashboard.
89
- */
90
- export function useTenantDashboard(tenantId: string | null, period = 'current') {
91
- const axios = useAxios();
92
- const axiosRef = useRef(axios);
93
- axiosRef.current = axios;
94
-
95
- const [data, setData] = useState<DashboardData | null>(null);
96
- const [loading, setLoading] = useState(true);
97
- const [error, setError] = useState<Error | null>(null);
98
-
99
- const sequence = useRef(0);
100
-
101
- useEffect(() => {
102
- if (!tenantId) return;
103
-
104
- const ticket = ++sequence.current;
105
- setLoading(true);
106
- setError(null);
107
-
108
- axiosRef.current
109
- .get(`report/dashboard/${encodeURIComponent(tenantId)}`, { params: { period } })
110
- .then((response: { data: DashboardData }) => {
111
- if (ticket === sequence.current) setData(response.data);
112
- })
113
- .catch((e: Error) => {
114
- if (ticket === sequence.current) setError(e);
115
- })
116
- .finally(() => {
117
- if (ticket === sequence.current) setLoading(false);
118
- });
119
- // eslint-disable-next-line react-hooks/exhaustive-deps
120
- }, [tenantId, period]);
121
-
122
- return { data, loading, error };
123
- }
124
-
125
- /** The period picker's options for one tenant. */
126
- export function useTenantPeriods(tenantId: string | null) {
127
- const axios = useAxios();
128
- const axiosRef = useRef(axios);
129
- axiosRef.current = axios;
130
-
131
- const [periods, setPeriods] = useState<DashboardPeriod[]>([]);
132
- const [loading, setLoading] = useState(false);
133
-
134
- useEffect(() => {
135
- if (!tenantId) return;
136
-
137
- let alive = true;
138
- setLoading(true);
139
-
140
- axiosRef.current
141
- .get(`report/dashboard/${encodeURIComponent(tenantId)}/periods`)
142
- .then((response: { data: { data: DashboardPeriod[] } }) => {
143
- if (alive) setPeriods(response.data?.data ?? []);
144
- })
145
- .catch(() => {
146
- // A missing period list is not worth surfacing — the dashboard still
147
- // works on the current period, which is the default.
148
- if (alive) setPeriods([]);
149
- })
150
- .finally(() => {
151
- if (alive) setLoading(false);
152
- });
153
-
154
- return () => {
155
- alive = false;
156
- };
157
- // eslint-disable-next-line react-hooks/exhaustive-deps
158
- }, [tenantId]);
159
-
160
- return { periods, loading };
161
- }
@@ -1,68 +0,0 @@
1
- import { useCallback, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import { toQueryParams } from './queryParams';
4
- import type { ReportFilterState } from './types';
5
-
6
- /**
7
- * Download a report in one of the formats its spec advertises.
8
- *
9
- * `responseType: 'blob'` is an AXIOS REQUEST OPTION, not a query parameter.
10
- * The legacy Stats page put it inside `params`, so it was serialised into the
11
- * URL as `?responseType=blob` and axios still parsed the response as text —
12
- * which corrupted the binary and is one reason that export never worked. The
13
- * distinction is easy to miss because nothing errors; you just get a broken
14
- * file.
15
- */
16
- export function useReportExport(reportKey: string) {
17
- const axios = useAxios();
18
- // See useReportList: useAxios() is unmemoized and must stay out of deps.
19
- const axiosRef = useRef(axios);
20
- axiosRef.current = axios;
21
-
22
- const [exporting, setExporting] = useState(false);
23
- const [error, setError] = useState<Error | null>(null);
24
-
25
- const exportReport = useCallback(
26
- async (filters: ReportFilterState, format = 'xlsx') => {
27
- setExporting(true);
28
- setError(null);
29
-
30
- try {
31
- const response = await axiosRef.current.get(`report/${encodeURIComponent(reportKey)}/export`, {
32
- params: { ...toQueryParams(filters), format },
33
- responseType: 'blob',
34
- });
35
-
36
- const blob = new Blob([response.data], {
37
- type: String(response.headers?.['content-type'] ?? 'application/octet-stream'),
38
- });
39
-
40
- // Prefer the filename the server chose — it carries the report key and
41
- // the actual date range, which a client-side guess would drift from.
42
- const disposition = String(response.headers?.['content-disposition'] ?? '');
43
- const match = disposition.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i);
44
- const filename = match ? decodeURIComponent(match[1]) : `${reportKey}.${format}`;
45
-
46
- const url = URL.createObjectURL(blob);
47
- const link = document.createElement('a');
48
- link.href = url;
49
- link.download = filename;
50
- document.body.appendChild(link);
51
- link.click();
52
- link.remove();
53
- // Revoked on the next tick: revoking synchronously can cancel the
54
- // download in some browsers before it has read the blob.
55
- setTimeout(() => URL.revokeObjectURL(url), 0);
56
- } catch (e) {
57
- setError(e as Error);
58
- } finally {
59
- setExporting(false);
60
- }
61
- },
62
- [reportKey],
63
- );
64
-
65
- return { exporting, error, exportReport };
66
- }
67
-
68
- export default useReportExport;
@@ -1,51 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import type { ReportListEntry } from './types';
4
-
5
- /**
6
- * Every report this caller may see.
7
- *
8
- * The backend has already filtered by role, so whatever comes back is safe to
9
- * render as a menu.
10
- */
11
- /**
12
- * useAxios() builds a NEW axios instance on every render (dash-axios-hook calls
13
- * axios.create() unmemoized), so it must never appear in a dependency array:
14
- * a fresh reference each render re-fires the effect, which sets state, which
15
- * re-renders — an unbroken request loop against the API. The instance is held
16
- * in a ref instead, and the effect depends only on values that are stable by
17
- * value.
18
- */
19
- export function useReportList() {
20
- const axios = useAxios();
21
- const axiosRef = useRef(axios);
22
- axiosRef.current = axios;
23
- const [reports, setReports] = useState<ReportListEntry[]>([]);
24
- const [loading, setLoading] = useState(true);
25
- const [error, setError] = useState<Error | null>(null);
26
-
27
- useEffect(() => {
28
- let alive = true;
29
-
30
- axiosRef.current
31
- .get('report')
32
- .then((response: { data: { data: ReportListEntry[] } }) => {
33
- if (alive) setReports(response.data?.data ?? []);
34
- })
35
- .catch((e: Error) => {
36
- if (alive) setError(e);
37
- })
38
- .finally(() => {
39
- if (alive) setLoading(false);
40
- });
41
-
42
- return () => {
43
- alive = false;
44
- };
45
- // eslint-disable-next-line react-hooks/exhaustive-deps
46
- }, []);
47
-
48
- return { reports, loading, error };
49
- }
50
-
51
- export default useReportList;
@@ -1,68 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import type { Granularity, ReportResult } from './types';
4
-
5
- /**
6
- * A small series for one report's list-page card.
7
- *
8
- * One request per card, fired in parallel and failing independently — a report
9
- * whose preview errors shows a card without a chart rather than blanking the
10
- * list. The engine this replaces fetched every report's full dataset plus one
11
- * request per chart type before the index rendered at all (2N requests to draw
12
- * a page nobody had chosen anything on yet); this is N cheap ones, at a coarse
13
- * grain, and only for what is on screen.
14
- */
15
- export function useReportPreview(
16
- reportKey: string,
17
- granularity: Granularity,
18
- rangeDays: number,
19
- enabled = true,
20
- ) {
21
- const axios = useAxios();
22
- // See useReportList: useAxios() is unmemoized and must stay out of deps.
23
- const axiosRef = useRef(axios);
24
- axiosRef.current = axios;
25
-
26
- const [result, setResult] = useState<ReportResult | null>(null);
27
- const [loading, setLoading] = useState(enabled);
28
- const [error, setError] = useState<Error | null>(null);
29
-
30
- useEffect(() => {
31
- if (!enabled || !reportKey) return;
32
-
33
- let alive = true;
34
-
35
- const end = new Date();
36
- const start = new Date();
37
- start.setDate(start.getDate() - (rangeDays || 30));
38
-
39
- setLoading(true);
40
-
41
- axiosRef.current
42
- .get(`report/${encodeURIComponent(reportKey)}/series`, {
43
- params: {
44
- start: start.toISOString().slice(0, 10),
45
- end: end.toISOString().slice(0, 10),
46
- granularity,
47
- },
48
- })
49
- .then((response: { data: ReportResult }) => {
50
- if (alive) setResult(response.data);
51
- })
52
- .catch((e: Error) => {
53
- if (alive) setError(e);
54
- })
55
- .finally(() => {
56
- if (alive) setLoading(false);
57
- });
58
-
59
- return () => {
60
- alive = false;
61
- };
62
- // eslint-disable-next-line react-hooks/exhaustive-deps
63
- }, [reportKey, granularity, rangeDays, enabled]);
64
-
65
- return { result, loading, error };
66
- }
67
-
68
- export default useReportPreview;
@@ -1,59 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import { toQueryParams } from './queryParams';
4
- import type { ReportFilterState, ReportResult } from './types';
5
-
6
- export interface UseReportSeriesResult {
7
- result: ReportResult | null;
8
- loading: boolean;
9
- error: Error | null;
10
- }
11
-
12
- /**
13
- * Fetch the bucketed series for a report under the current filters.
14
- *
15
- * Refetches whenever the filters change, and discards responses that arrive
16
- * out of order. Without the sequence guard, dragging a date range quickly
17
- * leaves whichever request happened to finish last on screen, which is not
18
- * necessarily the one the user is currently looking at.
19
- */
20
- export function useReportSeries(
21
- reportKey: string,
22
- filters: ReportFilterState | null,
23
- ): UseReportSeriesResult {
24
- const axios = useAxios();
25
- // See useReportList: useAxios() is unmemoized and must stay out of deps.
26
- const axiosRef = useRef(axios);
27
- axiosRef.current = axios;
28
-
29
- const [result, setResult] = useState<ReportResult | null>(null);
30
- const [loading, setLoading] = useState(false);
31
- const [error, setError] = useState<Error | null>(null);
32
-
33
- const sequence = useRef(0);
34
-
35
- useEffect(() => {
36
- if (!reportKey || !filters) return;
37
-
38
- const ticket = ++sequence.current;
39
- setLoading(true);
40
- setError(null);
41
-
42
- axiosRef.current
43
- .get(`report/${encodeURIComponent(reportKey)}/series`, { params: toQueryParams(filters) })
44
- .then((response: { data: ReportResult }) => {
45
- if (ticket === sequence.current) setResult(response.data);
46
- })
47
- .catch((e: Error) => {
48
- if (ticket === sequence.current) setError(e);
49
- })
50
- .finally(() => {
51
- if (ticket === sequence.current) setLoading(false);
52
- });
53
- // eslint-disable-next-line react-hooks/exhaustive-deps
54
- }, [reportKey, JSON.stringify(filters)]);
55
-
56
- return { result, loading, error };
57
- }
58
-
59
- export default useReportSeries;
@@ -1,79 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import type { ReportSpec } from './types';
4
-
5
- /**
6
- * Fetch a report's spec.
7
- *
8
- * Single-flight per key: React 18 StrictMode double-invokes effects in
9
- * development, and without the guard every report page would fire its spec
10
- * request twice on mount. The in-flight promise is shared rather than the
11
- * second call being dropped, so both callers still resolve.
12
- */
13
- const inFlight = new Map<string, Promise<ReportSpec>>();
14
-
15
- export interface UseReportSpecResult {
16
- spec: ReportSpec | null;
17
- loading: boolean;
18
- error: Error | null;
19
- reload: () => void;
20
- }
21
-
22
- export function useReportSpec(reportKey: string): UseReportSpecResult {
23
- const axios = useAxios();
24
- // See useReportList: useAxios() is unmemoized and must stay out of deps.
25
- const axiosRef = useRef(axios);
26
- axiosRef.current = axios;
27
-
28
- const [spec, setSpec] = useState<ReportSpec | null>(null);
29
- const [loading, setLoading] = useState(true);
30
- const [error, setError] = useState<Error | null>(null);
31
- const [nonce, setNonce] = useState(0);
32
-
33
- // Survives unmount-during-fetch without setting state on a dead component.
34
- const alive = useRef(true);
35
- useEffect(() => {
36
- alive.current = true;
37
- return () => {
38
- alive.current = false;
39
- };
40
- }, []);
41
-
42
- useEffect(() => {
43
- if (!reportKey) return;
44
-
45
- setLoading(true);
46
- setError(null);
47
-
48
- const cacheKey = `${reportKey}:${nonce}`;
49
-
50
- let promise = inFlight.get(cacheKey);
51
- if (!promise) {
52
- promise = axiosRef.current
53
- .get(`report/${encodeURIComponent(reportKey)}/spec`)
54
- .then((response: { data: ReportSpec }) => response.data)
55
- .finally(() => {
56
- inFlight.delete(cacheKey);
57
- });
58
- inFlight.set(cacheKey, promise);
59
- }
60
-
61
- promise
62
- .then((data) => {
63
- if (alive.current) setSpec(data);
64
- })
65
- .catch((e: Error) => {
66
- if (alive.current) setError(e);
67
- })
68
- .finally(() => {
69
- if (alive.current) setLoading(false);
70
- });
71
- // eslint-disable-next-line react-hooks/exhaustive-deps
72
- }, [reportKey, nonce]);
73
-
74
- const reload = useCallback(() => setNonce((n) => n + 1), []);
75
-
76
- return { spec, loading, error, reload };
77
- }
78
-
79
- export default useReportSpec;
@@ -1,71 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { useAxios } from 'dash-axios-hook';
3
- import { toQueryParams } from './queryParams';
4
- import type { ReportFilterState, ReportTablePage } from './types';
5
-
6
- export interface UseReportTableResult {
7
- page: ReportTablePage | null;
8
- loading: boolean;
9
- error: Error | null;
10
- setPage: (page: number) => void;
11
- currentPage: number;
12
- }
13
-
14
- /**
15
- * The paginated detail rows behind a report.
16
- *
17
- * `enabled` exists because the table is usually behind a toggle: fetching
18
- * detail rows for a report nobody has expanded is a wasted query on every
19
- * filter change, and these are the widest rows the endpoint serves.
20
- */
21
- export function useReportTable(
22
- reportKey: string,
23
- filters: ReportFilterState | null,
24
- enabled = true,
25
- perPage = 50,
26
- ): UseReportTableResult {
27
- const axios = useAxios();
28
- // See useReportList: useAxios() is unmemoized and must stay out of deps.
29
- const axiosRef = useRef(axios);
30
- axiosRef.current = axios;
31
-
32
- const [page, setPageData] = useState<ReportTablePage | null>(null);
33
- const [loading, setLoading] = useState(false);
34
- const [error, setError] = useState<Error | null>(null);
35
- const [currentPage, setPage] = useState(1);
36
-
37
- const sequence = useRef(0);
38
-
39
- // A filter change invalidates the page number: page 7 of the old result set
40
- // is meaningless against the new one, and often past its end.
41
- useEffect(() => {
42
- setPage(1);
43
- }, [JSON.stringify(filters)]);
44
-
45
- useEffect(() => {
46
- if (!enabled || !reportKey || !filters) return;
47
-
48
- const ticket = ++sequence.current;
49
- setLoading(true);
50
- setError(null);
51
-
52
- axiosRef.current
53
- .get(`report/${encodeURIComponent(reportKey)}/table`, {
54
- params: { ...toQueryParams(filters), page: currentPage, perPage },
55
- })
56
- .then((response: { data: ReportTablePage }) => {
57
- if (ticket === sequence.current) setPageData(response.data);
58
- })
59
- .catch((e: Error) => {
60
- if (ticket === sequence.current) setError(e);
61
- })
62
- .finally(() => {
63
- if (ticket === sequence.current) setLoading(false);
64
- });
65
- // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, [reportKey, JSON.stringify(filters), enabled, currentPage, perPage]);
67
-
68
- return { page, loading, error, setPage, currentPage };
69
- }
70
-
71
- export default useReportTable;
@@ -1,34 +0,0 @@
1
- import type { VisualizationType } from './types';
2
-
3
- /**
4
- * Which visualizations draw to a <canvas>, and therefore need a parent with a
5
- * DEFINITE height.
6
- *
7
- * Chart.js with `responsive: true` + `maintainAspectRatio: false` sizes its
8
- * canvas from the parent box. If that parent's height is content-derived —
9
- * `minHeight`, `auto`, or nothing at all — the canvas grows to fill it, which
10
- * grows the parent, which fires another resize, which grows the canvas again.
11
- * The card expands without bound and the page never settles. `minHeight` does
12
- * NOT prevent this: it sets a floor, not a bound.
13
- *
14
- * Kpi, Funnel and Table are ordinary DOM and must size to their content
15
- * instead — pinning them to a fixed height would clip a long funnel or a wide
16
- * table.
17
- */
18
- const CANVAS: ReadonlySet<string> = new Set<VisualizationType>(['Bar', 'Line', 'Pie', 'Doughnut']);
19
-
20
- export function isCanvasVisualization(type: string | null | undefined): boolean {
21
- return !!type && CANVAS.has(type);
22
- }
23
-
24
- /**
25
- * The sx for a visualization's container: a definite height for canvases, and
26
- * content height for everything else.
27
- */
28
- export function visualizationBoxSx(type: string | null | undefined, height: number) {
29
- return isCanvasVisualization(type)
30
- ? { height, minHeight: height, maxHeight: height, overflow: 'hidden' }
31
- : { minHeight: 0 };
32
- }
33
-
34
- export default isCanvasVisualization;
@@ -1,39 +0,0 @@
1
- import type { ComponentType } from 'react';
2
- import type { ReportResult, ReportSpec, TranslateFn, VisualizationType } from './types';
3
-
4
- /**
5
- * Which component renders which visualization type.
6
- *
7
- * A registry rather than a switch so the chart library is a swappable detail:
8
- * the MUI/Chart.js adapter registers one set, and a different adapter (or a
9
- * single overridden widget) can replace any of them without this package
10
- * changing. It is also what keeps `core/` free of any chart import, so the
11
- * hooks and types stay usable somewhere Chart.js is not wanted.
12
- */
13
-
14
- export interface VisualizationProps {
15
- result: ReportResult;
16
- spec: ReportSpec;
17
- translate: TranslateFn;
18
- options?: Record<string, unknown>;
19
- height?: number;
20
- }
21
-
22
- const registry = new Map<VisualizationType, ComponentType<VisualizationProps>>();
23
-
24
- export function registerVisualization(
25
- type: VisualizationType,
26
- component: ComponentType<VisualizationProps>,
27
- ): void {
28
- registry.set(type, component);
29
- }
30
-
31
- export function getVisualization(type: VisualizationType): ComponentType<VisualizationProps> | null {
32
- return registry.get(type) ?? null;
33
- }
34
-
35
- export function registeredVisualizations(): VisualizationType[] {
36
- return Array.from(registry.keys());
37
- }
38
-
39
- export default registry;