@lindaui/blocks 0.4.2 → 0.5.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/README.md +56 -0
- package/dist/ai-cost-card-f403ypsg.js +99 -0
- package/dist/ai-performance-card-cggm09nc.js +191 -0
- package/dist/analytics/ai-cost-card.d.ts +3 -0
- package/dist/analytics/ai-cost-card.d.ts.map +1 -0
- package/dist/analytics/ai-performance-card.d.ts +3 -0
- package/dist/analytics/ai-performance-card.d.ts.map +1 -0
- package/dist/analytics/analytics-card.d.ts +3 -0
- package/dist/analytics/analytics-card.d.ts.map +1 -0
- package/dist/analytics/analytics-skeleton.d.ts +5 -0
- package/dist/analytics/analytics-skeleton.d.ts.map +1 -0
- package/dist/analytics/entity-health-card.d.ts +3 -0
- package/dist/analytics/entity-health-card.d.ts.map +1 -0
- package/dist/analytics/status-distribution-chart.d.ts +3 -0
- package/dist/analytics/status-distribution-chart.d.ts.map +1 -0
- package/dist/analytics/time-range-picker.d.ts +3 -0
- package/dist/analytics/time-range-picker.d.ts.map +1 -0
- package/dist/analytics/top-entities-table.d.ts +3 -0
- package/dist/analytics/top-entities-table.d.ts.map +1 -0
- package/dist/analytics/types.d.ts +104 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics-card-bcmijpt6.js +38 -0
- package/dist/analytics-skeleton-imve37du.js +164 -0
- package/dist/analytics.d.ts +11 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +8 -0
- package/dist/app-shell/drawer-focus.d.ts +8 -0
- package/dist/app-shell/drawer-focus.d.ts.map +1 -0
- package/dist/app-shell/footer.d.ts +39 -0
- package/dist/app-shell/footer.d.ts.map +1 -0
- package/dist/app-shell/logout-dialog.d.ts +6 -0
- package/dist/app-shell/logout-dialog.d.ts.map +1 -0
- package/dist/app-shell/navigation.d.ts +15 -0
- package/dist/app-shell/navigation.d.ts.map +1 -0
- package/dist/app-shell/types.d.ts +55 -0
- package/dist/app-shell/types.d.ts.map +1 -0
- package/dist/app-shell.d.ts +7 -0
- package/dist/app-shell.d.ts.map +1 -0
- package/dist/app-shell.js +254 -0
- package/dist/channel-settings.d.ts +24 -0
- package/dist/channel-settings.d.ts.map +1 -0
- package/dist/channel-settings.js +60 -0
- package/dist/chat-avatar-jdmeyf2o.js +30 -0
- package/dist/chat-layout-fwx3x26g.js +25 -0
- package/dist/chat.d.ts +22 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +66 -0
- package/dist/command-palette.d.ts +22 -0
- package/dist/command-palette.d.ts.map +1 -0
- package/dist/command-palette.js +141 -0
- package/dist/compat-chat/chat-avatar.d.ts +16 -0
- package/dist/compat-chat/chat-avatar.d.ts.map +1 -0
- package/dist/compat-chat/chat-header.d.ts +9 -0
- package/dist/compat-chat/chat-header.d.ts.map +1 -0
- package/dist/compat-chat/chat-layout.d.ts +10 -0
- package/dist/compat-chat/chat-layout.d.ts.map +1 -0
- package/dist/compat-chat/conversation-list-item.d.ts +10 -0
- package/dist/compat-chat/conversation-list-item.d.ts.map +1 -0
- package/dist/compat-chat/conversation-list.d.ts +24 -0
- package/dist/compat-chat/conversation-list.d.ts.map +1 -0
- package/dist/compat-chat/conversation-thread.d.ts +18 -0
- package/dist/compat-chat/conversation-thread.d.ts.map +1 -0
- package/dist/compat-chat/date-divider.d.ts +5 -0
- package/dist/compat-chat/date-divider.d.ts.map +1 -0
- package/dist/compat-chat/empty-chat-view.d.ts +6 -0
- package/dist/compat-chat/empty-chat-view.d.ts.map +1 -0
- package/dist/compat-chat/image-lightbox.d.ts +7 -0
- package/dist/compat-chat/image-lightbox.d.ts.map +1 -0
- package/dist/compat-chat/message-bubble.d.ts +6 -0
- package/dist/compat-chat/message-bubble.d.ts.map +1 -0
- package/dist/compat-chat/message-composer.d.ts +11 -0
- package/dist/compat-chat/message-composer.d.ts.map +1 -0
- package/dist/compat-chat/time-divider.d.ts +5 -0
- package/dist/compat-chat/time-divider.d.ts.map +1 -0
- package/dist/compat-chat/time-grouping.d.ts +5 -0
- package/dist/compat-chat/time-grouping.d.ts.map +1 -0
- package/dist/compat-chat/types.d.ts +35 -0
- package/dist/compat-chat/types.d.ts.map +1 -0
- package/dist/compat-chat/whatsapp-audio-player.d.ts +7 -0
- package/dist/compat-chat/whatsapp-audio-player.d.ts.map +1 -0
- package/dist/compat.d.ts +9 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/compat.js +4 -0
- package/dist/connected-panel-lhwy25yk.js +132 -0
- package/dist/connection-panel/connected-panel.d.ts +26 -0
- package/dist/connection-panel/connected-panel.d.ts.map +1 -0
- package/dist/connection-panel/disconnected-panel.d.ts +12 -0
- package/dist/connection-panel/disconnected-panel.d.ts.map +1 -0
- package/dist/connection-panel/instructions.d.ts +5 -0
- package/dist/connection-panel/instructions.d.ts.map +1 -0
- package/dist/connection-panel/qr-panel.d.ts +14 -0
- package/dist/connection-panel/qr-panel.d.ts.map +1 -0
- package/dist/connection-panel/use-qr-refresh.d.ts +18 -0
- package/dist/connection-panel/use-qr-refresh.d.ts.map +1 -0
- package/dist/connection-panel.d.ts +39 -0
- package/dist/connection-panel.d.ts.map +1 -0
- package/dist/connection-panel.js +328 -0
- package/dist/conversation-list-ekq2dzca.js +133 -0
- package/dist/conversation-list-item-fy6t5af2.js +55 -0
- package/dist/conversation-thread-c60431oa.js +205 -0
- package/dist/entity-card.d.ts +17 -0
- package/dist/entity-card.d.ts.map +1 -0
- package/dist/entity-card.js +69 -0
- package/dist/entity-health-card-ggnmrtrq.js +192 -0
- package/dist/entity-switcher.d.ts +33 -0
- package/dist/entity-switcher.d.ts.map +1 -0
- package/dist/entity-switcher.js +137 -0
- package/dist/filter-bar.d.ts +41 -0
- package/dist/filter-bar.d.ts.map +1 -0
- package/dist/filter-bar.js +155 -0
- package/dist/footer-f7r5e42b.js +192 -0
- package/dist/image-lightbox-ci3arb6z.js +62 -0
- package/dist/kanban/drop-utils.d.ts +18 -0
- package/dist/kanban/drop-utils.d.ts.map +1 -0
- package/dist/kanban/navigation-buttons.d.ts +9 -0
- package/dist/kanban/navigation-buttons.d.ts.map +1 -0
- package/dist/kanban/scroll-indicators.d.ts +6 -0
- package/dist/kanban/scroll-indicators.d.ts.map +1 -0
- package/dist/kanban/use-keyboard-navigation.d.ts +4 -0
- package/dist/kanban/use-keyboard-navigation.d.ts.map +1 -0
- package/dist/kanban-board.d.ts +31 -0
- package/dist/kanban-board.d.ts.map +1 -0
- package/dist/kanban-board.js +128 -0
- package/dist/kanban-column.d.ts +13 -0
- package/dist/kanban-column.d.ts.map +1 -0
- package/dist/kanban-column.js +58 -0
- package/dist/legacy-kpi-card.d.ts +16 -0
- package/dist/legacy-kpi-card.d.ts.map +1 -0
- package/dist/legacy-kpi-card.js +4 -0
- package/dist/logout-dialog-bbv59tbi.js +93 -0
- package/dist/message-bubble-odjvhroq.js +110 -0
- package/dist/message-composer-m2n6soo7.js +59 -0
- package/dist/navigation-buttons-e77mshh0.js +65 -0
- package/dist/navigation-hotcrqoq.js +115 -0
- package/dist/save-bar.d.ts +15 -0
- package/dist/save-bar.d.ts.map +1 -0
- package/dist/save-bar.js +38 -0
- package/dist/scroll-indicators-gvxapogd.js +39 -0
- package/dist/settings-layout.d.ts +20 -0
- package/dist/settings-layout.d.ts.map +1 -0
- package/dist/settings-layout.js +56 -0
- package/dist/source-selector.d.ts +18 -0
- package/dist/source-selector.d.ts.map +1 -0
- package/dist/source-selector.js +59 -0
- package/dist/status-distribution-chart-oucedzv7.js +87 -0
- package/dist/status-filters.d.ts +12 -0
- package/dist/status-filters.d.ts.map +1 -0
- package/dist/status-filters.js +31 -0
- package/dist/stepper.d.ts +15 -0
- package/dist/stepper.d.ts.map +1 -0
- package/dist/stepper.js +42 -0
- package/dist/time-range-picker-mu38hul4.js +123 -0
- package/dist/top-entities-table-o7xzpq96.js +78 -0
- package/dist/use-keyboard-navigation-he1xmriz.js +52 -0
- package/dist/whatsapp-audio-player-nooty6jb.js +277 -0
- package/llms.txt +87 -4
- package/package.json +191 -46
- package/src/analytics/ai-cost-card.tsx +31 -0
- package/src/analytics/ai-performance-card.tsx +48 -0
- package/src/analytics/analytics-card.tsx +14 -0
- package/src/analytics/analytics-skeleton.tsx +15 -0
- package/src/analytics/entity-health-card.tsx +50 -0
- package/src/analytics/status-distribution-chart.tsx +20 -0
- package/src/analytics/time-range-picker.tsx +43 -0
- package/src/analytics/top-entities-table.tsx +24 -0
- package/src/analytics/types.ts +43 -0
- package/src/analytics.ts +10 -0
- package/src/app-shell/drawer-focus.ts +42 -0
- package/src/app-shell/footer.tsx +160 -0
- package/src/app-shell/logout-dialog.tsx +68 -0
- package/src/app-shell/navigation.tsx +147 -0
- package/src/app-shell/types.ts +57 -0
- package/src/app-shell.tsx +166 -0
- package/src/area-chart.tsx +85 -0
- package/src/audio-player.tsx +74 -0
- package/src/auth-layout.tsx +132 -0
- package/src/auth-provider.tsx +29 -0
- package/src/bar-chart.tsx +78 -0
- package/src/channel-settings.tsx +76 -0
- package/src/chart-card.tsx +45 -0
- package/src/chat-avatar.tsx +57 -0
- package/src/chat-date-divider.tsx +18 -0
- package/src/chat-header.tsx +24 -0
- package/src/chat-layout.tsx +41 -0
- package/src/chat-time-divider.tsx +20 -0
- package/src/chat-types.ts +32 -0
- package/src/chat.ts +21 -0
- package/src/command-palette.tsx +110 -0
- package/src/compat-chat/chat-avatar.tsx +23 -0
- package/src/compat-chat/chat-header.tsx +12 -0
- package/src/compat-chat/chat-layout.tsx +14 -0
- package/src/compat-chat/conversation-list-item.tsx +27 -0
- package/src/compat-chat/conversation-list.tsx +49 -0
- package/src/compat-chat/conversation-thread.tsx +92 -0
- package/src/compat-chat/date-divider.tsx +7 -0
- package/src/compat-chat/empty-chat-view.tsx +9 -0
- package/src/compat-chat/image-lightbox.tsx +22 -0
- package/src/compat-chat/message-bubble.tsx +28 -0
- package/src/compat-chat/message-composer.tsx +26 -0
- package/src/compat-chat/time-divider.tsx +5 -0
- package/src/compat-chat/time-grouping.ts +19 -0
- package/src/compat-chat/types.ts +31 -0
- package/src/compat-chat/whatsapp-audio-player.tsx +100 -0
- package/src/compat.ts +8 -0
- package/src/confirm-dialog.tsx +85 -0
- package/src/connection-panel/connected-panel.tsx +106 -0
- package/src/connection-panel/disconnected-panel.tsx +48 -0
- package/src/connection-panel/instructions.tsx +9 -0
- package/src/connection-panel/qr-panel.tsx +62 -0
- package/src/connection-panel/use-qr-refresh.ts +52 -0
- package/src/connection-panel.tsx +141 -0
- package/src/conversation-list-item.tsx +53 -0
- package/src/conversation-list.tsx +67 -0
- package/src/conversation-thread.tsx +64 -0
- package/src/detail-panel.tsx +123 -0
- package/src/document-panel.tsx +109 -0
- package/src/document-reader.tsx +29 -0
- package/src/empty-chat-view.tsx +25 -0
- package/src/entity-card.tsx +74 -0
- package/src/entity-health-card.tsx +53 -0
- package/src/entity-switcher.tsx +126 -0
- package/src/fetching-indicator.tsx +16 -0
- package/src/field-array-form.tsx +166 -0
- package/src/filter-bar.tsx +203 -0
- package/src/image-viewer.tsx +129 -0
- package/src/kanban/drop-utils.ts +31 -0
- package/src/kanban/navigation-buttons.tsx +80 -0
- package/src/kanban/scroll-indicators.tsx +47 -0
- package/src/kanban/use-keyboard-navigation.ts +43 -0
- package/src/kanban-board.tsx +160 -0
- package/src/kanban-column.tsx +62 -0
- package/src/legacy-kpi-card.tsx +38 -0
- package/src/line-chart.tsx +58 -0
- package/src/list-item.tsx +63 -0
- package/src/list-pane.tsx +143 -0
- package/src/login-form.tsx +128 -0
- package/src/message-bubble.tsx +70 -0
- package/src/message-composer.tsx +52 -0
- package/src/pie-chart.tsx +70 -0
- package/src/radar-chart.tsx +55 -0
- package/src/radial-chart.tsx +103 -0
- package/src/recording-overlay.tsx +159 -0
- package/src/save-bar.tsx +35 -0
- package/src/settings-layout.tsx +79 -0
- package/src/source-selector.tsx +71 -0
- package/src/split-workspace.tsx +74 -0
- package/src/stat-card.tsx +166 -0
- package/src/stats-grid.tsx +23 -0
- package/src/status-filters.tsx +28 -0
- package/src/stepper.tsx +47 -0
- package/src/table-pane.tsx +194 -0
- package/src/time-range-picker.tsx +89 -0
- package/src/workspace-back-button.tsx +32 -0
- package/dist/chat-types.test.d.ts +0 -2
- package/dist/chat-types.test.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lindaui/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Composed UI sections (auth, lists, master-detail, charts) built on @lindaui/ui — importable npm package, not a copy-paste registry.",
|
|
5
5
|
"author": "Franco Carballar <francocarballar@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,180 +30,325 @@
|
|
|
30
30
|
"exports": {
|
|
31
31
|
"./login-form": {
|
|
32
32
|
"types": "./dist/login-form.d.ts",
|
|
33
|
-
"import": "./dist/login-form.js"
|
|
33
|
+
"import": "./dist/login-form.js",
|
|
34
|
+
"default": "./dist/login-form.js"
|
|
34
35
|
},
|
|
35
36
|
"./list-item": {
|
|
36
37
|
"types": "./dist/list-item.d.ts",
|
|
37
|
-
"import": "./dist/list-item.js"
|
|
38
|
+
"import": "./dist/list-item.js",
|
|
39
|
+
"default": "./dist/list-item.js"
|
|
38
40
|
},
|
|
39
41
|
"./list-pane": {
|
|
40
42
|
"types": "./dist/list-pane.d.ts",
|
|
41
|
-
"import": "./dist/list-pane.js"
|
|
43
|
+
"import": "./dist/list-pane.js",
|
|
44
|
+
"default": "./dist/list-pane.js"
|
|
42
45
|
},
|
|
43
46
|
"./detail-panel": {
|
|
44
47
|
"types": "./dist/detail-panel.d.ts",
|
|
45
|
-
"import": "./dist/detail-panel.js"
|
|
48
|
+
"import": "./dist/detail-panel.js",
|
|
49
|
+
"default": "./dist/detail-panel.js"
|
|
46
50
|
},
|
|
47
51
|
"./confirm-dialog": {
|
|
48
52
|
"types": "./dist/confirm-dialog.d.ts",
|
|
49
|
-
"import": "./dist/confirm-dialog.js"
|
|
53
|
+
"import": "./dist/confirm-dialog.js",
|
|
54
|
+
"default": "./dist/confirm-dialog.js"
|
|
50
55
|
},
|
|
51
56
|
"./image-viewer": {
|
|
52
57
|
"types": "./dist/image-viewer.d.ts",
|
|
53
|
-
"import": "./dist/image-viewer.js"
|
|
58
|
+
"import": "./dist/image-viewer.js",
|
|
59
|
+
"default": "./dist/image-viewer.js"
|
|
54
60
|
},
|
|
55
61
|
"./split-workspace": {
|
|
56
62
|
"types": "./dist/split-workspace.d.ts",
|
|
57
|
-
"import": "./dist/split-workspace.js"
|
|
63
|
+
"import": "./dist/split-workspace.js",
|
|
64
|
+
"default": "./dist/split-workspace.js"
|
|
58
65
|
},
|
|
59
66
|
"./document-panel": {
|
|
60
67
|
"types": "./dist/document-panel.d.ts",
|
|
61
|
-
"import": "./dist/document-panel.js"
|
|
68
|
+
"import": "./dist/document-panel.js",
|
|
69
|
+
"default": "./dist/document-panel.js"
|
|
62
70
|
},
|
|
63
71
|
"./document-reader": {
|
|
64
72
|
"types": "./dist/document-reader.d.ts",
|
|
65
|
-
"import": "./dist/document-reader.js"
|
|
73
|
+
"import": "./dist/document-reader.js",
|
|
74
|
+
"default": "./dist/document-reader.js"
|
|
66
75
|
},
|
|
67
76
|
"./recording-overlay": {
|
|
68
77
|
"types": "./dist/recording-overlay.d.ts",
|
|
69
|
-
"import": "./dist/recording-overlay.js"
|
|
78
|
+
"import": "./dist/recording-overlay.js",
|
|
79
|
+
"default": "./dist/recording-overlay.js"
|
|
70
80
|
},
|
|
71
81
|
"./field-array-form": {
|
|
72
82
|
"types": "./dist/field-array-form.d.ts",
|
|
73
|
-
"import": "./dist/field-array-form.js"
|
|
83
|
+
"import": "./dist/field-array-form.js",
|
|
84
|
+
"default": "./dist/field-array-form.js"
|
|
74
85
|
},
|
|
75
86
|
"./auth-provider": {
|
|
76
87
|
"types": "./dist/auth-provider.d.ts",
|
|
77
|
-
"import": "./dist/auth-provider.js"
|
|
88
|
+
"import": "./dist/auth-provider.js",
|
|
89
|
+
"default": "./dist/auth-provider.js"
|
|
78
90
|
},
|
|
79
91
|
"./auth-layout": {
|
|
80
92
|
"types": "./dist/auth-layout.d.ts",
|
|
81
|
-
"import": "./dist/auth-layout.js"
|
|
93
|
+
"import": "./dist/auth-layout.js",
|
|
94
|
+
"default": "./dist/auth-layout.js"
|
|
82
95
|
},
|
|
83
96
|
"./area-chart": {
|
|
84
97
|
"types": "./dist/area-chart.d.ts",
|
|
85
|
-
"import": "./dist/area-chart.js"
|
|
98
|
+
"import": "./dist/area-chart.js",
|
|
99
|
+
"default": "./dist/area-chart.js"
|
|
86
100
|
},
|
|
87
101
|
"./bar-chart": {
|
|
88
102
|
"types": "./dist/bar-chart.d.ts",
|
|
89
|
-
"import": "./dist/bar-chart.js"
|
|
103
|
+
"import": "./dist/bar-chart.js",
|
|
104
|
+
"default": "./dist/bar-chart.js"
|
|
90
105
|
},
|
|
91
106
|
"./line-chart": {
|
|
92
107
|
"types": "./dist/line-chart.d.ts",
|
|
93
|
-
"import": "./dist/line-chart.js"
|
|
108
|
+
"import": "./dist/line-chart.js",
|
|
109
|
+
"default": "./dist/line-chart.js"
|
|
94
110
|
},
|
|
95
111
|
"./pie-chart": {
|
|
96
112
|
"types": "./dist/pie-chart.d.ts",
|
|
97
|
-
"import": "./dist/pie-chart.js"
|
|
113
|
+
"import": "./dist/pie-chart.js",
|
|
114
|
+
"default": "./dist/pie-chart.js"
|
|
98
115
|
},
|
|
99
116
|
"./radar-chart": {
|
|
100
117
|
"types": "./dist/radar-chart.d.ts",
|
|
101
|
-
"import": "./dist/radar-chart.js"
|
|
118
|
+
"import": "./dist/radar-chart.js",
|
|
119
|
+
"default": "./dist/radar-chart.js"
|
|
102
120
|
},
|
|
103
121
|
"./radial-chart": {
|
|
104
122
|
"types": "./dist/radial-chart.d.ts",
|
|
105
|
-
"import": "./dist/radial-chart.js"
|
|
123
|
+
"import": "./dist/radial-chart.js",
|
|
124
|
+
"default": "./dist/radial-chart.js"
|
|
106
125
|
},
|
|
107
126
|
"./stat-card": {
|
|
108
127
|
"types": "./dist/stat-card.d.ts",
|
|
109
|
-
"import": "./dist/stat-card.js"
|
|
128
|
+
"import": "./dist/stat-card.js",
|
|
129
|
+
"default": "./dist/stat-card.js"
|
|
110
130
|
},
|
|
111
131
|
"./chart-card": {
|
|
112
132
|
"types": "./dist/chart-card.d.ts",
|
|
113
|
-
"import": "./dist/chart-card.js"
|
|
133
|
+
"import": "./dist/chart-card.js",
|
|
134
|
+
"default": "./dist/chart-card.js"
|
|
114
135
|
},
|
|
115
136
|
"./stats-grid": {
|
|
116
137
|
"types": "./dist/stats-grid.d.ts",
|
|
117
|
-
"import": "./dist/stats-grid.js"
|
|
138
|
+
"import": "./dist/stats-grid.js",
|
|
139
|
+
"default": "./dist/stats-grid.js"
|
|
118
140
|
},
|
|
119
141
|
"./workspace-back-button": {
|
|
120
142
|
"types": "./dist/workspace-back-button.d.ts",
|
|
121
|
-
"import": "./dist/workspace-back-button.js"
|
|
143
|
+
"import": "./dist/workspace-back-button.js",
|
|
144
|
+
"default": "./dist/workspace-back-button.js"
|
|
122
145
|
},
|
|
123
146
|
"./table-pane": {
|
|
124
147
|
"types": "./dist/table-pane.d.ts",
|
|
125
|
-
"import": "./dist/table-pane.js"
|
|
148
|
+
"import": "./dist/table-pane.js",
|
|
149
|
+
"default": "./dist/table-pane.js"
|
|
126
150
|
},
|
|
127
151
|
"./audio-player": {
|
|
128
152
|
"types": "./dist/audio-player.d.ts",
|
|
129
|
-
"import": "./dist/audio-player.js"
|
|
153
|
+
"import": "./dist/audio-player.js",
|
|
154
|
+
"default": "./dist/audio-player.js"
|
|
130
155
|
},
|
|
131
156
|
"./chat-avatar": {
|
|
132
157
|
"types": "./dist/chat-avatar.d.ts",
|
|
133
|
-
"import": "./dist/chat-avatar.js"
|
|
158
|
+
"import": "./dist/chat-avatar.js",
|
|
159
|
+
"default": "./dist/chat-avatar.js"
|
|
134
160
|
},
|
|
135
161
|
"./chat-date-divider": {
|
|
136
162
|
"types": "./dist/chat-date-divider.d.ts",
|
|
137
|
-
"import": "./dist/chat-date-divider.js"
|
|
163
|
+
"import": "./dist/chat-date-divider.js",
|
|
164
|
+
"default": "./dist/chat-date-divider.js"
|
|
138
165
|
},
|
|
139
166
|
"./chat-header": {
|
|
140
167
|
"types": "./dist/chat-header.d.ts",
|
|
141
|
-
"import": "./dist/chat-header.js"
|
|
168
|
+
"import": "./dist/chat-header.js",
|
|
169
|
+
"default": "./dist/chat-header.js"
|
|
142
170
|
},
|
|
143
171
|
"./chat-layout": {
|
|
144
172
|
"types": "./dist/chat-layout.d.ts",
|
|
145
|
-
"import": "./dist/chat-layout.js"
|
|
173
|
+
"import": "./dist/chat-layout.js",
|
|
174
|
+
"default": "./dist/chat-layout.js"
|
|
146
175
|
},
|
|
147
176
|
"./chat-time-divider": {
|
|
148
177
|
"types": "./dist/chat-time-divider.d.ts",
|
|
149
|
-
"import": "./dist/chat-time-divider.js"
|
|
178
|
+
"import": "./dist/chat-time-divider.js",
|
|
179
|
+
"default": "./dist/chat-time-divider.js"
|
|
150
180
|
},
|
|
151
181
|
"./chat-types": {
|
|
152
182
|
"types": "./dist/chat-types.d.ts",
|
|
153
|
-
"import": "./dist/chat-types.js"
|
|
183
|
+
"import": "./dist/chat-types.js",
|
|
184
|
+
"default": "./dist/chat-types.js"
|
|
154
185
|
},
|
|
155
186
|
"./conversation-list": {
|
|
156
187
|
"types": "./dist/conversation-list.d.ts",
|
|
157
|
-
"import": "./dist/conversation-list.js"
|
|
188
|
+
"import": "./dist/conversation-list.js",
|
|
189
|
+
"default": "./dist/conversation-list.js"
|
|
158
190
|
},
|
|
159
191
|
"./conversation-list-item": {
|
|
160
192
|
"types": "./dist/conversation-list-item.d.ts",
|
|
161
|
-
"import": "./dist/conversation-list-item.js"
|
|
193
|
+
"import": "./dist/conversation-list-item.js",
|
|
194
|
+
"default": "./dist/conversation-list-item.js"
|
|
162
195
|
},
|
|
163
196
|
"./conversation-thread": {
|
|
164
197
|
"types": "./dist/conversation-thread.d.ts",
|
|
165
|
-
"import": "./dist/conversation-thread.js"
|
|
198
|
+
"import": "./dist/conversation-thread.js",
|
|
199
|
+
"default": "./dist/conversation-thread.js"
|
|
166
200
|
},
|
|
167
201
|
"./empty-chat-view": {
|
|
168
202
|
"types": "./dist/empty-chat-view.d.ts",
|
|
169
|
-
"import": "./dist/empty-chat-view.js"
|
|
203
|
+
"import": "./dist/empty-chat-view.js",
|
|
204
|
+
"default": "./dist/empty-chat-view.js"
|
|
170
205
|
},
|
|
171
206
|
"./message-bubble": {
|
|
172
207
|
"types": "./dist/message-bubble.d.ts",
|
|
173
|
-
"import": "./dist/message-bubble.js"
|
|
208
|
+
"import": "./dist/message-bubble.js",
|
|
209
|
+
"default": "./dist/message-bubble.js"
|
|
174
210
|
},
|
|
175
211
|
"./message-composer": {
|
|
176
212
|
"types": "./dist/message-composer.d.ts",
|
|
177
|
-
"import": "./dist/message-composer.js"
|
|
213
|
+
"import": "./dist/message-composer.js",
|
|
214
|
+
"default": "./dist/message-composer.js"
|
|
178
215
|
},
|
|
179
216
|
"./time-range-picker": {
|
|
180
217
|
"types": "./dist/time-range-picker.d.ts",
|
|
181
|
-
"import": "./dist/time-range-picker.js"
|
|
218
|
+
"import": "./dist/time-range-picker.js",
|
|
219
|
+
"default": "./dist/time-range-picker.js"
|
|
182
220
|
},
|
|
183
221
|
"./entity-health-card": {
|
|
184
222
|
"types": "./dist/entity-health-card.d.ts",
|
|
185
|
-
"import": "./dist/entity-health-card.js"
|
|
223
|
+
"import": "./dist/entity-health-card.js",
|
|
224
|
+
"default": "./dist/entity-health-card.js"
|
|
186
225
|
},
|
|
187
226
|
"./fetching-indicator": {
|
|
188
227
|
"types": "./dist/fetching-indicator.d.ts",
|
|
189
|
-
"import": "./dist/fetching-indicator.js"
|
|
228
|
+
"import": "./dist/fetching-indicator.js",
|
|
229
|
+
"default": "./dist/fetching-indicator.js"
|
|
230
|
+
},
|
|
231
|
+
"./app-shell": {
|
|
232
|
+
"types": "./dist/app-shell.d.ts",
|
|
233
|
+
"import": "./dist/app-shell.js",
|
|
234
|
+
"default": "./dist/app-shell.js"
|
|
235
|
+
},
|
|
236
|
+
"./channel-settings": {
|
|
237
|
+
"types": "./dist/channel-settings.d.ts",
|
|
238
|
+
"import": "./dist/channel-settings.js",
|
|
239
|
+
"default": "./dist/channel-settings.js"
|
|
240
|
+
},
|
|
241
|
+
"./chat": {
|
|
242
|
+
"types": "./dist/chat.d.ts",
|
|
243
|
+
"import": "./dist/chat.js",
|
|
244
|
+
"default": "./dist/chat.js"
|
|
245
|
+
},
|
|
246
|
+
"./command-palette": {
|
|
247
|
+
"types": "./dist/command-palette.d.ts",
|
|
248
|
+
"import": "./dist/command-palette.js",
|
|
249
|
+
"default": "./dist/command-palette.js"
|
|
250
|
+
},
|
|
251
|
+
"./compat": {
|
|
252
|
+
"types": "./dist/compat.d.ts",
|
|
253
|
+
"import": "./dist/compat.js",
|
|
254
|
+
"default": "./dist/compat.js"
|
|
255
|
+
},
|
|
256
|
+
"./connection-panel": {
|
|
257
|
+
"types": "./dist/connection-panel.d.ts",
|
|
258
|
+
"import": "./dist/connection-panel.js",
|
|
259
|
+
"default": "./dist/connection-panel.js"
|
|
260
|
+
},
|
|
261
|
+
"./entity-card": {
|
|
262
|
+
"types": "./dist/entity-card.d.ts",
|
|
263
|
+
"import": "./dist/entity-card.js",
|
|
264
|
+
"default": "./dist/entity-card.js"
|
|
265
|
+
},
|
|
266
|
+
"./entity-switcher": {
|
|
267
|
+
"types": "./dist/entity-switcher.d.ts",
|
|
268
|
+
"import": "./dist/entity-switcher.js",
|
|
269
|
+
"default": "./dist/entity-switcher.js"
|
|
270
|
+
},
|
|
271
|
+
"./filter-bar": {
|
|
272
|
+
"types": "./dist/filter-bar.d.ts",
|
|
273
|
+
"import": "./dist/filter-bar.js",
|
|
274
|
+
"default": "./dist/filter-bar.js"
|
|
275
|
+
},
|
|
276
|
+
"./kanban-board": {
|
|
277
|
+
"types": "./dist/kanban-board.d.ts",
|
|
278
|
+
"import": "./dist/kanban-board.js",
|
|
279
|
+
"default": "./dist/kanban-board.js"
|
|
280
|
+
},
|
|
281
|
+
"./kanban-column": {
|
|
282
|
+
"types": "./dist/kanban-column.d.ts",
|
|
283
|
+
"import": "./dist/kanban-column.js",
|
|
284
|
+
"default": "./dist/kanban-column.js"
|
|
285
|
+
},
|
|
286
|
+
"./legacy-kpi-card": {
|
|
287
|
+
"types": "./dist/legacy-kpi-card.d.ts",
|
|
288
|
+
"import": "./dist/legacy-kpi-card.js",
|
|
289
|
+
"default": "./dist/legacy-kpi-card.js"
|
|
290
|
+
},
|
|
291
|
+
"./save-bar": {
|
|
292
|
+
"types": "./dist/save-bar.d.ts",
|
|
293
|
+
"import": "./dist/save-bar.js",
|
|
294
|
+
"default": "./dist/save-bar.js"
|
|
295
|
+
},
|
|
296
|
+
"./settings-layout": {
|
|
297
|
+
"types": "./dist/settings-layout.d.ts",
|
|
298
|
+
"import": "./dist/settings-layout.js",
|
|
299
|
+
"default": "./dist/settings-layout.js"
|
|
300
|
+
},
|
|
301
|
+
"./source-selector": {
|
|
302
|
+
"types": "./dist/source-selector.d.ts",
|
|
303
|
+
"import": "./dist/source-selector.js",
|
|
304
|
+
"default": "./dist/source-selector.js"
|
|
305
|
+
},
|
|
306
|
+
"./status-filters": {
|
|
307
|
+
"types": "./dist/status-filters.d.ts",
|
|
308
|
+
"import": "./dist/status-filters.js",
|
|
309
|
+
"default": "./dist/status-filters.js"
|
|
310
|
+
},
|
|
311
|
+
"./stepper": {
|
|
312
|
+
"types": "./dist/stepper.d.ts",
|
|
313
|
+
"import": "./dist/stepper.js",
|
|
314
|
+
"default": "./dist/stepper.js"
|
|
315
|
+
},
|
|
316
|
+
"./analytics": {
|
|
317
|
+
"types": "./dist/analytics.d.ts",
|
|
318
|
+
"import": "./dist/analytics.js",
|
|
319
|
+
"default": "./dist/analytics.js"
|
|
190
320
|
}
|
|
191
321
|
},
|
|
192
322
|
"files": [
|
|
193
323
|
"dist",
|
|
324
|
+
"src",
|
|
325
|
+
"!src/**/*.test.*",
|
|
326
|
+
"!src/**/*.stories.*",
|
|
327
|
+
"!dist/**/*.test.*",
|
|
328
|
+
"!dist/**/*.stories.*",
|
|
329
|
+
"!src/test-setup.ts",
|
|
194
330
|
"LICENSE",
|
|
195
331
|
"README.md",
|
|
196
332
|
"llms.txt"
|
|
197
333
|
],
|
|
198
334
|
"peerDependencies": {
|
|
199
|
-
"@lindaui/tokens": ">=0.
|
|
200
|
-
"@lindaui/ui": ">=0.
|
|
335
|
+
"@lindaui/tokens": ">=0.4.0",
|
|
336
|
+
"@lindaui/ui": ">=0.5.0",
|
|
201
337
|
"lucide-react": ">=0.4",
|
|
202
338
|
"react": ">=19",
|
|
203
339
|
"react-dom": ">=19",
|
|
204
340
|
"react-hook-form": ">=7",
|
|
205
341
|
"recharts": ">=3"
|
|
206
342
|
},
|
|
343
|
+
"dependencies": {
|
|
344
|
+
"@dnd-kit/core": "^6.3.1",
|
|
345
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
346
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
347
|
+
"@internationalized/date": "3.12.4",
|
|
348
|
+
"@react-types/shared": "3.36.1",
|
|
349
|
+
"dayjs": "1.11.23",
|
|
350
|
+
"framer-motion": "14.0.0"
|
|
351
|
+
},
|
|
207
352
|
"devDependencies": {
|
|
208
353
|
"@hookform/resolvers": "^5.9.1",
|
|
209
354
|
"@testing-library/jest-dom": "^7.0.1",
|
|
@@ -224,11 +369,11 @@
|
|
|
224
369
|
"vite": "^8.3.4",
|
|
225
370
|
"vitest": "^5.0.3",
|
|
226
371
|
"zod": "^4.6.5",
|
|
227
|
-
"@lindaui/tokens": "0.
|
|
228
|
-
"@lindaui/ui": "0.
|
|
372
|
+
"@lindaui/tokens": "0.4.0",
|
|
373
|
+
"@lindaui/ui": "0.5.0"
|
|
229
374
|
},
|
|
230
375
|
"scripts": {
|
|
231
|
-
"build": "bunchee --no-dts && tsc -p tsconfig.build.json",
|
|
376
|
+
"build": "bunchee --no-dts && tsc -p tsconfig.build.json && node ../../scripts/normalize-declarations.mjs dist",
|
|
232
377
|
"dev": "bunchee --watch",
|
|
233
378
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
234
379
|
"test": "vitest run",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AlertCircle, DollarSign, TrendingUp, Zap } from "lucide-react";
|
|
4
|
+
import { memo, type ElementType } from "react";
|
|
5
|
+
import { AnalyticsCard } from "./analytics-card";
|
|
6
|
+
import type { AiCostCardProps } from "./types";
|
|
7
|
+
|
|
8
|
+
function formatUsd(value: number): string { return value.toLocaleString("en-US", { currency: "USD", maximumFractionDigits: 2, minimumFractionDigits: 2, style: "currency" }); }
|
|
9
|
+
function StatBlock({ icon: Icon, label, value, accent = false }: { icon: ElementType; label: string; value: string; accent?: boolean }) {
|
|
10
|
+
return <div className={`rounded-xl px-4 py-3 ${accent ? "bg-accent/5" : "bg-surface-secondary"}`}>
|
|
11
|
+
<div className="mb-1.5 flex items-center gap-1.5"><Icon className={`h-3.5 w-3.5 ${accent ? "text-accent" : "text-muted"}`} /><p className="text-muted text-xs">{label}</p></div>
|
|
12
|
+
<p className="font-bold text-foreground text-xl tabular-nums">{value}</p>
|
|
13
|
+
</div>;
|
|
14
|
+
}
|
|
15
|
+
function NotConfigured({ t }: { t: (key: string) => string }) {
|
|
16
|
+
return <div className="flex flex-col items-center justify-center gap-2 py-8 text-center">
|
|
17
|
+
<div className="flex h-9 w-9 items-center justify-center rounded-xl bg-surface-secondary"><AlertCircle className="h-5 w-5 text-muted" /></div>
|
|
18
|
+
<p className="font-medium text-muted text-sm">{t("analytics.aiCost.notConfigured")}</p>
|
|
19
|
+
<p className="max-w-[220px] text-muted text-xs">{t("analytics.aiCost.notConfiguredHint")}</p>
|
|
20
|
+
</div>;
|
|
21
|
+
}
|
|
22
|
+
function LoadingState() { return <div className="grid grid-cols-3 gap-3">{["a", "b", "c"].map((k) => <div className="h-[72px] animate-pulse rounded-xl bg-surface-secondary" key={k} />)}</div>; }
|
|
23
|
+
export const AiCostCard = memo(({ aiCost, isLoading, t }: AiCostCardProps) => (
|
|
24
|
+
<AnalyticsCard icon={Zap} title={t("analytics.aiCost.title")}>
|
|
25
|
+
{isLoading ? <LoadingState /> : aiCost?.available === true ? <div className="grid grid-cols-3 gap-3">
|
|
26
|
+
<StatBlock accent icon={DollarSign} label={t("analytics.aiCost.totalCost")} value={formatUsd(aiCost.totalCostUsd)} />
|
|
27
|
+
<StatBlock icon={TrendingUp} label={t("analytics.aiCost.costPerConversion")} value={aiCost.conversionsCount > 0 ? formatUsd(aiCost.costPerConversion) : "—"} />
|
|
28
|
+
<StatBlock icon={Zap} label={t("analytics.aiCost.conversions")} value={aiCost.conversionsCount.toLocaleString()} />
|
|
29
|
+
</div> : <NotConfigured t={t} />}
|
|
30
|
+
</AnalyticsCard>
|
|
31
|
+
));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Bot, Clock, Shield, User } from "lucide-react";
|
|
4
|
+
import { memo } from "react";
|
|
5
|
+
import { Divider } from "@lindaui/ui/compat";
|
|
6
|
+
import { AnalyticsCard } from "./analytics-card";
|
|
7
|
+
import type { AiPerformanceCardProps, AiVsHumanData } from "./types";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_AI_VS_HUMAN: AiVsHumanData = { aiConverted: 0, aiLeads: 0, aiRate: 0, humanConverted: 0, humanLeads: 0, humanRate: 0 };
|
|
10
|
+
function rateColorClass(rate: number): string { return rate >= 10 ? "text-success" : rate >= 5 ? "text-warning" : "text-muted"; }
|
|
11
|
+
function StatRow({ label, value, valueClass = "text-foreground" }: { label: string; value: string | number; valueClass?: string }) {
|
|
12
|
+
return <div className="flex items-center justify-between"><span className="text-muted text-xs">{label}</span><span className={`font-semibold text-xs tabular-nums ${valueClass}`}>{value}</span></div>;
|
|
13
|
+
}
|
|
14
|
+
export const AiPerformanceCard = memo(({ containmentRate = 0, aiVsHuman: aiVsHumanProp, avgResponseTimeMinutes = 0, t }: AiPerformanceCardProps) => {
|
|
15
|
+
const aiVsHuman = aiVsHumanProp ?? DEFAULT_AI_VS_HUMAN;
|
|
16
|
+
return <AnalyticsCard icon={Bot} title={t("analytics.aiPerformance.title")}>
|
|
17
|
+
<div className="grid grid-cols-2 gap-3">
|
|
18
|
+
<div className="rounded-xl bg-accent/5 px-4 py-3">
|
|
19
|
+
<div className="mb-1.5 flex items-center gap-1.5"><Shield className="h-3.5 w-3.5 text-accent" /><p className="text-muted text-xs">{t("analytics.aiPerformance.containment")}</p></div>
|
|
20
|
+
<p className="font-bold text-3xl text-foreground tabular-nums">{containmentRate}<span className="ml-0.5 font-medium text-lg text-muted">%</span></p>
|
|
21
|
+
</div>
|
|
22
|
+
<div className="rounded-xl bg-surface-secondary px-4 py-3">
|
|
23
|
+
<div className="mb-1.5 flex items-center gap-1.5"><Clock className="h-3.5 w-3.5 text-muted" /><p className="text-muted text-xs">{t("analytics.aiPerformance.responseTime")}</p></div>
|
|
24
|
+
<p className="font-bold text-3xl text-foreground tabular-nums">{avgResponseTimeMinutes}<span className="ml-1 font-normal text-muted text-sm">{t("analytics.aiPerformance.minutes")}</span></p>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<Divider className="my-4" />
|
|
28
|
+
<p className="mb-3 font-medium text-muted text-xs uppercase tracking-wider">{t("analytics.aiPerformance.comparison")}</p>
|
|
29
|
+
<div className="grid grid-cols-2 gap-4">
|
|
30
|
+
<div className="space-y-3">
|
|
31
|
+
<div className="flex items-center gap-1.5"><div className="flex h-5 w-5 items-center justify-center rounded-md bg-accent/10"><Bot className="h-3 w-3 text-accent" /></div><p className="font-semibold text-foreground text-xs">{t("analytics.aiPerformance.ai")}</p></div>
|
|
32
|
+
<div className="space-y-2">
|
|
33
|
+
<StatRow label={t("analytics.aiPerformance.leads")} value={aiVsHuman.aiLeads.toLocaleString()} />
|
|
34
|
+
<StatRow label={t("analytics.aiPerformance.converted")} value={aiVsHuman.aiConverted.toLocaleString()} />
|
|
35
|
+
<StatRow label={t("analytics.aiPerformance.rate")} value={`${aiVsHuman.aiRate}%`} valueClass={rateColorClass(aiVsHuman.aiRate)} />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="space-y-3 border-border border-l pl-4">
|
|
39
|
+
<div className="flex items-center gap-1.5"><div className="flex h-5 w-5 items-center justify-center rounded-md bg-surface-secondary"><User className="h-3 w-3 text-muted" /></div><p className="font-semibold text-foreground text-xs">{t("analytics.aiPerformance.human")}</p></div>
|
|
40
|
+
<div className="space-y-2">
|
|
41
|
+
<StatRow label={t("analytics.aiPerformance.leads")} value={aiVsHuman.humanLeads.toLocaleString()} />
|
|
42
|
+
<StatRow label={t("analytics.aiPerformance.converted")} value={aiVsHuman.humanConverted.toLocaleString()} />
|
|
43
|
+
<StatRow label={t("analytics.aiPerformance.rate")} value={`${aiVsHuman.humanRate}%`} valueClass={rateColorClass(aiVsHuman.humanRate)} />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</AnalyticsCard>;
|
|
48
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Card } from "@lindaui/ui/compound";
|
|
4
|
+
import type { AnalyticsCardProps } from "./types";
|
|
5
|
+
|
|
6
|
+
export function AnalyticsCard({ icon: Icon, title, action, children }: AnalyticsCardProps) {
|
|
7
|
+
return <Card className="border border-border bg-surface shadow-none">
|
|
8
|
+
<Card.Header className="flex items-center justify-between px-5 pt-5 pb-0">
|
|
9
|
+
<div className="flex items-center gap-2"><div className="flex h-7 w-7 items-center justify-center rounded-lg bg-accent/8"><Icon className="h-4 w-4 text-accent" /></div><h2 className="font-semibold text-foreground text-sm">{title}</h2></div>
|
|
10
|
+
{action}
|
|
11
|
+
</Card.Header>
|
|
12
|
+
<Card.Content className="px-5 pt-4 pb-5">{children}</Card.Content>
|
|
13
|
+
</Card>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Skeleton } from "@lindaui/ui/compound";
|
|
4
|
+
import { Building2, Loader2, Wifi } from "lucide-react";
|
|
5
|
+
import { KPICard } from "../legacy-kpi-card";
|
|
6
|
+
|
|
7
|
+
export function AnalyticsSkeleton() {
|
|
8
|
+
return <div className="space-y-6">
|
|
9
|
+
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">{["a", "b", "c", "d"].map((k) => <KPICard icon={Building2} isLoading key={k} title="" value="" />)}</div>
|
|
10
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">{["e", "f", "g"].map((k) => <KPICard icon={Wifi} isLoading key={k} title="" value="" />)}</div>
|
|
11
|
+
<div className="grid gap-6 lg:grid-cols-2"><Skeleton className="h-72 w-full rounded-xl" /><Skeleton className="h-72 w-full rounded-xl" /></div>
|
|
12
|
+
<div className="grid gap-6 lg:grid-cols-2"><Skeleton className="h-64 w-full rounded-xl" /><Skeleton className="h-64 w-full rounded-xl" /></div>
|
|
13
|
+
</div>;
|
|
14
|
+
}
|
|
15
|
+
export function FetchingIndicator({ label }: { label: string }) { return <div className="flex items-center gap-1.5 text-muted text-xs"><Loader2 className="h-3 w-3 animate-spin" /><span>{label}</span></div>; }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Activity, CheckCircle2 } from "lucide-react";
|
|
4
|
+
import { Fragment, memo, useMemo, type ReactNode } from "react";
|
|
5
|
+
import { Badge, Divider } from "@lindaui/ui/compat";
|
|
6
|
+
import { AnalyticsCard } from "./analytics-card";
|
|
7
|
+
import type { EntityHealthCardProps, HealthEntity, HealthStatus } from "./types";
|
|
8
|
+
|
|
9
|
+
const STATUS_CONFIG: Record<HealthStatus, { badgeColor: "success" | "warning" | "danger"; dotClass: string; bgClass: string; textClass: string; labelClass: string }> = {
|
|
10
|
+
at_risk: { badgeColor: "warning", bgClass: "bg-warning/8", dotClass: "bg-warning", labelClass: "text-warning/80", textClass: "text-warning" },
|
|
11
|
+
critical: { badgeColor: "danger", bgClass: "bg-danger/8", dotClass: "bg-danger", labelClass: "text-danger/80", textClass: "text-danger" },
|
|
12
|
+
healthy: { badgeColor: "success", bgClass: "bg-success/8", dotClass: "bg-success", labelClass: "text-success/80", textClass: "text-success" },
|
|
13
|
+
};
|
|
14
|
+
function ScoreBar({ count, total, colorClass }: { count: number; total: number; colorClass: string }) {
|
|
15
|
+
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
|
|
16
|
+
return <div className="mt-2 h-1 w-full overflow-hidden rounded-full bg-surface-secondary"><div className={`h-full rounded-full transition-all duration-500 ${colorClass}`} style={{ width: `${pct}%` }} /></div>;
|
|
17
|
+
}
|
|
18
|
+
export const EntityHealthCard = memo(({ entities, labels, renderEntityLink }: EntityHealthCardProps) => {
|
|
19
|
+
const total = entities.length;
|
|
20
|
+
const { healthy, atRisk, critical, alertEntities } = useMemo(() => ({
|
|
21
|
+
healthy: entities.filter((e) => e.status === "healthy").length,
|
|
22
|
+
atRisk: entities.filter((e) => e.status === "at_risk").length,
|
|
23
|
+
critical: entities.filter((e) => e.status === "critical").length,
|
|
24
|
+
alertEntities: entities.filter((e) => e.status !== "healthy"),
|
|
25
|
+
}), [entities]);
|
|
26
|
+
const renderLink = renderEntityLink ?? ((_entity: HealthEntity, children: ReactNode) => <div>{children}</div>);
|
|
27
|
+
const action = critical > 0 ? <Badge color="danger" size="sm" variant="flat">{labels.criticalBadge(critical)}</Badge> : undefined;
|
|
28
|
+
return <AnalyticsCard action={action} icon={Activity} title={labels.title}>
|
|
29
|
+
<div className="grid grid-cols-3 gap-3">
|
|
30
|
+
<div className={`rounded-xl p-3 ${STATUS_CONFIG.healthy.bgClass}`}><p className={`font-bold text-2xl tabular-nums ${STATUS_CONFIG.healthy.textClass}`}>{healthy}</p><p className={`mt-0.5 text-xs ${STATUS_CONFIG.healthy.labelClass}`}>{labels.healthy}</p><ScoreBar colorClass="bg-success" count={healthy} total={total} /></div>
|
|
31
|
+
<div className={`rounded-xl p-3 ${STATUS_CONFIG.at_risk.bgClass}`}><p className={`font-bold text-2xl tabular-nums ${STATUS_CONFIG.at_risk.textClass}`}>{atRisk}</p><p className={`mt-0.5 text-xs ${STATUS_CONFIG.at_risk.labelClass}`}>{labels.atRisk}</p><ScoreBar colorClass="bg-warning" count={atRisk} total={total} /></div>
|
|
32
|
+
<div className={`rounded-xl p-3 ${STATUS_CONFIG.critical.bgClass}`}><p className={`font-bold text-2xl tabular-nums ${STATUS_CONFIG.critical.textClass}`}>{critical}</p><p className={`mt-0.5 text-xs ${STATUS_CONFIG.critical.labelClass}`}>{labels.critical}</p><ScoreBar colorClass="bg-danger" count={critical} total={total} /></div>
|
|
33
|
+
</div>
|
|
34
|
+
{alertEntities.length === 0 ? <div className="mt-4 flex flex-col items-center justify-center gap-2 py-5"><CheckCircle2 className="h-8 w-8 text-success/60" /><p className="text-center text-muted text-xs">{labels.allHealthy}</p></div> : <>
|
|
35
|
+
<Divider className="my-4" />
|
|
36
|
+
<div className="max-h-[200px] space-y-0.5 overflow-y-auto">
|
|
37
|
+
{alertEntities.map((entity) => {
|
|
38
|
+
const cfg = STATUS_CONFIG[entity.status];
|
|
39
|
+
return <Fragment key={entity.id}>{renderLink(entity,
|
|
40
|
+
<div className="flex items-center justify-between rounded-lg px-3 py-2 transition-colors duration-100 hover:bg-surface-secondary">
|
|
41
|
+
<div className="flex min-w-0 items-center gap-2.5"><span className={`h-2 w-2 flex-shrink-0 rounded-full ${cfg.dotClass}`} /><span className="truncate font-medium text-foreground text-sm">{entity.name}</span></div>
|
|
42
|
+
<div className="ml-2 flex flex-shrink-0 items-center gap-2"><span className="font-mono text-muted text-xs tabular-nums">{labels.scoreLabel(entity.score)}</span><Badge color={cfg.badgeColor} size="sm" variant="flat">{labels.statusLabel(entity.status)}</Badge></div>
|
|
43
|
+
</div>
|
|
44
|
+
)}</Fragment>;
|
|
45
|
+
})}
|
|
46
|
+
</div>
|
|
47
|
+
<p className="mt-3 text-muted text-xs">{labels.idleNote}</p>
|
|
48
|
+
</>}
|
|
49
|
+
</AnalyticsCard>;
|
|
50
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { Bar, BarChart, CartesianGrid, Cell, ResponsiveContainer, Tooltip, type TooltipProps, XAxis, YAxis } from "recharts";
|
|
5
|
+
import type { StatusDistributionChartProps } from "./types";
|
|
6
|
+
|
|
7
|
+
export function StatusDistributionChart({ data, emptyLabel, valueLabel, height = 220 }: StatusDistributionChartProps) {
|
|
8
|
+
const chartData = [...data].sort((a, b) => b.count - a.count);
|
|
9
|
+
const formatTooltip = useCallback<NonNullable<TooltipProps["formatter"]>>((value) => [value, valueLabel], [valueLabel]);
|
|
10
|
+
if (!chartData.length) return <p className="py-8 text-center text-muted text-sm">{emptyLabel}</p>;
|
|
11
|
+
return <ResponsiveContainer height={height} width="100%">
|
|
12
|
+
<BarChart data={chartData} layout="vertical" margin={{ bottom: 0, left: 0, right: 16, top: 0 }}>
|
|
13
|
+
<CartesianGrid horizontal={false} stroke="var(--border)" strokeDasharray="3 3" />
|
|
14
|
+
<XAxis axisLine={false} tick={{ fill: "var(--muted)", fontSize: 11 }} tickLine={false} type="number" />
|
|
15
|
+
<YAxis axisLine={false} dataKey="label" tick={{ fill: "var(--muted)", fontSize: 11 }} tickLine={false} type="category" width={110} />
|
|
16
|
+
<Tooltip contentStyle={{ backgroundColor: "var(--surface)", border: "1px solid var(--border)", borderRadius: "8px", fontSize: "12px" }} cursor={{ fill: "var(--surface-secondary)" }} formatter={formatTooltip} />
|
|
17
|
+
<Bar dataKey="count" radius={[0, 4, 4, 0]}>{chartData.map((entry) => <Cell fill={entry.color} key={entry.key} />)}</Bar>
|
|
18
|
+
</BarChart>
|
|
19
|
+
</ResponsiveContainer>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { DateField, DateRangePicker, Label, RangeCalendar } from "@lindaui/ui/compound";
|
|
4
|
+
import { getLocalTimeZone, today } from "@internationalized/date";
|
|
5
|
+
import { type ComponentProps, memo, useState } from "react";
|
|
6
|
+
import type { Preset, TimeRangePickerProps } from "./types";
|
|
7
|
+
|
|
8
|
+
const PRESETS: Preset[] = ["24h", "7d", "30d", "custom"];
|
|
9
|
+
// Preserve the public date types when HeroUI resolves its own date package copy.
|
|
10
|
+
type DateRangePickerValueProps = Pick<ComponentProps<typeof DateRangePicker>, "maxValue" | "onChange" | "value">;
|
|
11
|
+
export const TimeRangePicker = memo(({ preset, customRange, onPresetChange, onCustomRangeChange, t }: TimeRangePickerProps) => {
|
|
12
|
+
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
13
|
+
const maxDate = today(getLocalTimeZone());
|
|
14
|
+
function handlePresetChange(p: Preset) { onPresetChange(p); if (p === "custom") setIsPickerOpen(true); }
|
|
15
|
+
const handlePresetClick = (p: Preset) => () => handlePresetChange(p);
|
|
16
|
+
return <div className="flex items-center gap-3">
|
|
17
|
+
<div className="flex items-center gap-0.5 rounded-xl border border-border/60 bg-surface p-1 shadow-sm">
|
|
18
|
+
{PRESETS.map((p) => <button className={`cursor-pointer rounded-lg px-3.5 py-1.5 font-medium text-sm transition-all duration-150 ${preset === p ? "bg-accent text-accent-foreground shadow-sm" : "text-muted hover:bg-surface-secondary hover:text-foreground"}`} key={p} onClick={handlePresetClick(p)} type="button">{t(`analytics.timeRange.${p}`)}</button>)}
|
|
19
|
+
</div>
|
|
20
|
+
{preset === "custom" && <DateRangePicker className="max-w-xs" isOpen={isPickerOpen} maxValue={maxDate as unknown as DateRangePickerValueProps["maxValue"]} onChange={onCustomRangeChange as unknown as DateRangePickerValueProps["onChange"]} onOpenChange={setIsPickerOpen} value={customRange as unknown as DateRangePickerValueProps["value"]}>
|
|
21
|
+
<Label className="sr-only">{t("analytics.timeRange.label")}</Label>
|
|
22
|
+
<DateField.Group className="shadow-sm">
|
|
23
|
+
<DateField.InputContainer>
|
|
24
|
+
<DateField.Input slot="start">{(segment) => <DateField.Segment segment={segment} />}</DateField.Input>
|
|
25
|
+
<DateRangePicker.RangeSeparator />
|
|
26
|
+
<DateField.Input slot="end">{(segment) => <DateField.Segment segment={segment} />}</DateField.Input>
|
|
27
|
+
</DateField.InputContainer>
|
|
28
|
+
<DateField.Suffix><DateRangePicker.Trigger><DateRangePicker.TriggerIndicator /></DateRangePicker.Trigger></DateField.Suffix>
|
|
29
|
+
</DateField.Group>
|
|
30
|
+
<DateRangePicker.Popover><RangeCalendar aria-label={t("analytics.timeRange.label")} pageBehavior="single">
|
|
31
|
+
<RangeCalendar.Header>
|
|
32
|
+
<RangeCalendar.YearPickerTrigger><RangeCalendar.YearPickerTriggerHeading /><RangeCalendar.YearPickerTriggerIndicator /></RangeCalendar.YearPickerTrigger>
|
|
33
|
+
<RangeCalendar.NavButton slot="previous" /><RangeCalendar.NavButton slot="next" />
|
|
34
|
+
</RangeCalendar.Header>
|
|
35
|
+
<RangeCalendar.Grid>
|
|
36
|
+
<RangeCalendar.GridHeader>{(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}</RangeCalendar.GridHeader>
|
|
37
|
+
<RangeCalendar.GridBody>{(date) => <RangeCalendar.Cell date={date} />}</RangeCalendar.GridBody>
|
|
38
|
+
</RangeCalendar.Grid>
|
|
39
|
+
<RangeCalendar.YearPickerGrid><RangeCalendar.YearPickerGridBody>{({ year }) => <RangeCalendar.YearPickerCell year={year} />}</RangeCalendar.YearPickerGridBody></RangeCalendar.YearPickerGrid>
|
|
40
|
+
</RangeCalendar></DateRangePicker.Popover>
|
|
41
|
+
</DateRangePicker>}
|
|
42
|
+
</div>;
|
|
43
|
+
});
|