@autobe/ui 0.19.1 → 0.20.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.
Files changed (92) hide show
  1. package/lib/AutoBeChatUploadSendButton.d.ts +15 -0
  2. package/lib/AutoBeChatUploadSendButton.js +38 -0
  3. package/lib/AutoBeChatUploadSendButton.js.map +1 -0
  4. package/lib/AutoBeFileUploadBox.d.ts +10 -0
  5. package/lib/AutoBeFileUploadBox.js +68 -0
  6. package/lib/AutoBeFileUploadBox.js.map +1 -0
  7. package/lib/AutoBeVoiceRecoderButton.d.ts +11 -0
  8. package/lib/AutoBeVoiceRecoderButton.js +58 -0
  9. package/lib/AutoBeVoiceRecoderButton.js.map +1 -0
  10. package/lib/events/AutoBeCompleteEventMovie.d.ts +7 -0
  11. package/lib/events/AutoBeCompleteEventMovie.js +210 -0
  12. package/lib/events/AutoBeCompleteEventMovie.js.map +1 -0
  13. package/lib/events/AutoBeProgressEventMovie.js +2 -61
  14. package/lib/events/AutoBeProgressEventMovie.js.map +1 -1
  15. package/lib/events/AutoBeScenarioEventMovie.js +2 -44
  16. package/lib/events/AutoBeScenarioEventMovie.js.map +1 -1
  17. package/lib/events/AutoBeValidateEventMovie.d.ts +6 -0
  18. package/lib/events/AutoBeValidateEventMovie.js +115 -0
  19. package/lib/events/AutoBeValidateEventMovie.js.map +1 -0
  20. package/lib/events/common/CollapsibleEventGroup.d.ts +28 -0
  21. package/lib/events/common/CollapsibleEventGroup.js +89 -0
  22. package/lib/events/common/CollapsibleEventGroup.js.map +1 -0
  23. package/lib/events/common/EventCard.d.ts +13 -0
  24. package/lib/events/common/EventCard.js +43 -0
  25. package/lib/events/common/EventCard.js.map +1 -0
  26. package/lib/events/common/EventContent.d.ts +11 -0
  27. package/lib/events/common/EventContent.js +14 -0
  28. package/lib/events/common/EventContent.js.map +1 -0
  29. package/lib/events/common/EventHeader.d.ts +15 -0
  30. package/lib/events/common/EventHeader.js +41 -0
  31. package/lib/events/common/EventHeader.js.map +1 -0
  32. package/lib/events/common/EventIcon.d.ts +11 -0
  33. package/lib/events/common/EventIcon.js +50 -0
  34. package/lib/events/common/EventIcon.js.map +1 -0
  35. package/lib/events/common/ProgressBar.d.ts +14 -0
  36. package/lib/events/common/ProgressBar.js +33 -0
  37. package/lib/events/common/ProgressBar.js.map +1 -0
  38. package/lib/events/common/index.d.ts +6 -0
  39. package/lib/events/common/index.js +16 -0
  40. package/lib/events/common/index.js.map +1 -0
  41. package/lib/events/groups/ValidateEventGroup.d.ts +12 -0
  42. package/lib/events/groups/ValidateEventGroup.js +78 -0
  43. package/lib/events/groups/ValidateEventGroup.js.map +1 -0
  44. package/lib/events/groups/index.d.ts +1 -0
  45. package/lib/events/groups/index.js +6 -0
  46. package/lib/events/groups/index.js.map +1 -0
  47. package/lib/events/index.d.ts +5 -0
  48. package/lib/events/index.js +25 -1
  49. package/lib/events/index.js.map +1 -1
  50. package/lib/events/utils/eventGrouper.d.ts +20 -0
  51. package/lib/events/utils/eventGrouper.js +74 -0
  52. package/lib/events/utils/eventGrouper.js.map +1 -0
  53. package/lib/events/utils/index.d.ts +1 -0
  54. package/lib/events/utils/index.js +6 -0
  55. package/lib/events/utils/index.js.map +1 -0
  56. package/lib/index.d.ts +3 -0
  57. package/lib/index.js +7 -1
  58. package/lib/index.js.map +1 -1
  59. package/lib/utils/AutoBeFileUploader.d.ts +28 -0
  60. package/lib/utils/AutoBeFileUploader.js +237 -0
  61. package/lib/utils/AutoBeFileUploader.js.map +1 -0
  62. package/lib/utils/AutoBeVoiceRecorder.d.ts +7 -0
  63. package/lib/utils/AutoBeVoiceRecorder.js +94 -0
  64. package/lib/utils/AutoBeVoiceRecorder.js.map +1 -0
  65. package/lib/utils/index.d.ts +3 -0
  66. package/lib/utils/index.js +20 -0
  67. package/lib/utils/index.js.map +1 -0
  68. package/package.json +17 -2
  69. package/src/AutoBeChatUploadSendButton.tsx +66 -0
  70. package/src/AutoBeFileUploadBox.tsx +124 -0
  71. package/src/AutoBeVoiceRecoderButton.tsx +100 -0
  72. package/src/events/AutoBeCompleteEventMovie.tsx +402 -0
  73. package/src/events/AutoBeProgressEventMovie.tsx +12 -125
  74. package/src/events/AutoBeScenarioEventMovie.tsx +5 -93
  75. package/src/events/AutoBeValidateEventMovie.tsx +326 -0
  76. package/src/events/README.md +169 -0
  77. package/src/events/common/CollapsibleEventGroup.tsx +220 -0
  78. package/src/events/common/EventCard.tsx +61 -0
  79. package/src/events/common/EventContent.tsx +31 -0
  80. package/src/events/common/EventHeader.tsx +85 -0
  81. package/src/events/common/EventIcon.tsx +82 -0
  82. package/src/events/common/ProgressBar.tsx +63 -0
  83. package/src/events/common/index.ts +13 -0
  84. package/src/events/groups/ValidateEventGroup.tsx +150 -0
  85. package/src/events/groups/index.ts +4 -0
  86. package/src/events/index.ts +14 -0
  87. package/src/events/utils/eventGrouper.tsx +118 -0
  88. package/src/events/utils/index.ts +1 -0
  89. package/src/index.ts +6 -0
  90. package/src/utils/AutoBeFileUploader.ts +279 -0
  91. package/src/utils/AutoBeVoiceRecorder.ts +95 -0
  92. package/src/utils/index.ts +3 -0
@@ -0,0 +1,326 @@
1
+ import {
2
+ AutoBeInterfaceComplementEvent,
3
+ AutoBeInterfaceOperationsReviewEvent,
4
+ AutoBePrismaInsufficientEvent,
5
+ AutoBePrismaValidateEvent,
6
+ AutoBeRealizeAuthorizationValidateEvent,
7
+ AutoBeRealizeValidateEvent,
8
+ AutoBeTestValidateEvent,
9
+ } from "@autobe/interface";
10
+ import { JSX } from "react";
11
+
12
+ import { EventCard, EventContent, EventHeader } from "./common";
13
+
14
+ export interface IAutoBeValidateEventMovieProps {
15
+ event:
16
+ | AutoBePrismaInsufficientEvent
17
+ | AutoBePrismaValidateEvent
18
+ | AutoBeInterfaceOperationsReviewEvent
19
+ | AutoBeInterfaceComplementEvent
20
+ | AutoBeTestValidateEvent
21
+ | AutoBeRealizeValidateEvent
22
+ | AutoBeRealizeAuthorizationValidateEvent;
23
+ }
24
+
25
+ export const AutoBeValidateEventMovie = (
26
+ props: IAutoBeValidateEventMovieProps,
27
+ ) => {
28
+ const { event } = props;
29
+ const { title, description, isError, step, isSuccess } = getState(event);
30
+
31
+ const getCardVariant = () => {
32
+ if (isSuccess) return "success";
33
+ if (isError) return "error";
34
+ return "warning";
35
+ };
36
+
37
+ const getIconType = () => {
38
+ if (isSuccess) return "success";
39
+ if (isError) return "error";
40
+ return "warning";
41
+ };
42
+
43
+ return (
44
+ <EventCard variant={getCardVariant()}>
45
+ <EventHeader
46
+ title={title}
47
+ timestamp={event.created_at}
48
+ iconType={getIconType()}
49
+ step={step}
50
+ />
51
+ <EventContent>{description}</EventContent>
52
+ </EventCard>
53
+ );
54
+ };
55
+
56
+ export default AutoBeValidateEventMovie;
57
+
58
+ interface IState {
59
+ title: string;
60
+ description: string | JSX.Element;
61
+ isError: boolean;
62
+ isSuccess: boolean;
63
+ step?: number;
64
+ }
65
+
66
+ function getState(event: IAutoBeValidateEventMovieProps["event"]): IState {
67
+ switch (event.type) {
68
+ case "prismaValidate":
69
+ return {
70
+ title: "Prisma Validation Failed",
71
+ description: (
72
+ <>
73
+ Database schema validation encountered errors that require
74
+ correction.
75
+ <br />
76
+ <br />
77
+ <strong>Error Details:</strong>
78
+ <br />
79
+ {event.result.errors.length > 0 && (
80
+ <>
81
+ {event.result.errors
82
+ .slice(0, 3)
83
+ .map((error: any, idx: number) => (
84
+ <div key={idx} style={{ marginTop: "0.25rem" }}>
85
+ • {error.message}
86
+ </div>
87
+ ))}
88
+ </>
89
+ )}
90
+ <br />
91
+ <strong>Failed Schemas:</strong> {Object.keys(event.schemas).length}{" "}
92
+ file(s)
93
+ </>
94
+ ),
95
+ isError: true,
96
+ isSuccess: false,
97
+ step: event.step,
98
+ };
99
+ case "testValidate":
100
+ const isTestSuccess = event.result.type === "success";
101
+ return {
102
+ title: isTestSuccess
103
+ ? "Test Validation Successful"
104
+ : "Test Validation Failed",
105
+ description: (
106
+ <>
107
+ Test file validation completed.
108
+ <br />
109
+ <br />
110
+ <strong>File:</strong> {event.file.location}
111
+ <br />
112
+ <strong>Status:</strong> {isTestSuccess ? "Success" : "Failed"}
113
+ {!isTestSuccess && (
114
+ <>
115
+ <br />
116
+ <br />
117
+ <strong>Compilation Issues:</strong>
118
+ <br />
119
+ {event.result.type === "failure" &&
120
+ event.result.diagnostics
121
+ .slice(0, 3)
122
+ .map((diagnostic: any, idx: number) => (
123
+ <div key={idx} style={{ marginTop: "0.25rem" }}>
124
+ • {diagnostic.messageText}
125
+ </div>
126
+ ))}
127
+ {event.result.type === "exception" && (
128
+ <div style={{ marginTop: "0.25rem" }}>
129
+ • Exception occurred during compilation
130
+ </div>
131
+ )}
132
+ </>
133
+ )}
134
+ </>
135
+ ),
136
+ isError: !isTestSuccess,
137
+ isSuccess: isTestSuccess,
138
+ step: event.step,
139
+ };
140
+ case "realizeValidate":
141
+ return {
142
+ title: "Implementation Validation Failed",
143
+ description: (
144
+ <>
145
+ Implementation code compilation encountered errors that need
146
+ correction.
147
+ <br />
148
+ <br />
149
+ <strong>Failed Files:</strong> {Object.keys(event.files).length}{" "}
150
+ file(s)
151
+ <br />
152
+ <br />
153
+ <strong>Error Type:</strong>{" "}
154
+ {event.result.type === "failure"
155
+ ? "Compilation Error"
156
+ : "Runtime Exception"}
157
+ <br />
158
+ {event.result.type === "failure" &&
159
+ event.result.diagnostics.length > 0 && (
160
+ <>
161
+ <br />
162
+ <strong>First Error:</strong>
163
+ <br />
164
+ {event.result.diagnostics[0].messageText}
165
+ </>
166
+ )}
167
+ </>
168
+ ),
169
+ isError: true,
170
+ isSuccess: false,
171
+ step: event.step,
172
+ };
173
+ case "realizeAuthorizationValidate":
174
+ const isAuthSuccess = event.result.type === "success";
175
+ return {
176
+ title: isAuthSuccess
177
+ ? "Authorization Validation Successful"
178
+ : "Authorization Validation Failed",
179
+ description: (
180
+ <>
181
+ Authorization implementation validation completed.
182
+ <br />
183
+ <br />
184
+ <strong>Role:</strong> {event.authorization.role.name}(
185
+ {event.authorization.role.description})
186
+ <br />
187
+ <strong>Status:</strong> {isAuthSuccess ? "Success" : "Failed"}
188
+ {!isAuthSuccess && (
189
+ <>
190
+ <br />
191
+ <br />
192
+ <strong>Validation Issues:</strong>
193
+ <br />
194
+ {event.result.type === "failure" &&
195
+ event.result.diagnostics
196
+ .slice(0, 2)
197
+ .map((diagnostic: any, idx: number) => (
198
+ <div key={idx} style={{ marginTop: "0.25rem" }}>
199
+ • {diagnostic.messageText}
200
+ </div>
201
+ ))}
202
+ {event.result.type === "exception" && (
203
+ <div style={{ marginTop: "0.25rem" }}>
204
+ • Exception occurred during validation
205
+ </div>
206
+ )}
207
+ </>
208
+ )}
209
+ </>
210
+ ),
211
+ isError: !isAuthSuccess,
212
+ isSuccess: isAuthSuccess,
213
+ step: event.step,
214
+ };
215
+ case "prismaInsufficient":
216
+ return {
217
+ title: "Prisma Model Generation Insufficient",
218
+ description: (
219
+ <>
220
+ Prisma model generation was incomplete for the assigned component.
221
+ <br />
222
+ <br />
223
+ <strong>Component:</strong> {event.component.namespace}
224
+ <br />
225
+ <strong>Generated Models:</strong> {event.actual.length}
226
+ <br />
227
+ <strong>Missing Models:</strong> {event.missed.length}
228
+ <br />
229
+ <br />
230
+ {event.missed.length > 0 && (
231
+ <>
232
+ <strong>Missed Tables:</strong>
233
+ <br />
234
+ {event.missed.slice(0, 5).map((table: string, idx: number) => (
235
+ <div key={idx} style={{ marginTop: "0.25rem" }}>
236
+ • {table}
237
+ </div>
238
+ ))}
239
+ {event.missed.length > 5 && (
240
+ <div style={{ marginTop: "0.25rem" }}>
241
+ ... and {event.missed.length - 5} more
242
+ </div>
243
+ )}
244
+ </>
245
+ )}
246
+ </>
247
+ ),
248
+ isError: true,
249
+ isSuccess: false,
250
+ step: undefined, // prismaInsufficient doesn't have step
251
+ };
252
+ case "interfaceOperationsReview":
253
+ return {
254
+ title: "Interface Operations Review",
255
+ description: (
256
+ <>
257
+ API operations are being reviewed for quality and consistency.
258
+ <br />
259
+ <br />
260
+ <strong>Operations Count:</strong> {event.operations.length}
261
+ <br />
262
+ <strong>Status:</strong> Review in progress
263
+ <br />
264
+ <br />
265
+ <strong>Review Summary:</strong>
266
+ <br />
267
+ <div
268
+ style={{
269
+ maxHeight: "100px",
270
+ overflow: "hidden",
271
+ fontSize: "0.75rem",
272
+ color: "#64748b",
273
+ marginTop: "0.5rem",
274
+ }}
275
+ >
276
+ {event.review.length > 200
277
+ ? `${event.review.substring(0, 200)}...`
278
+ : event.review}
279
+ </div>
280
+ </>
281
+ ),
282
+ isError: false,
283
+ isSuccess: true,
284
+ step: event.step,
285
+ };
286
+ case "interfaceComplement":
287
+ return {
288
+ title: "Interface Schema Complement",
289
+ description: (
290
+ <>
291
+ Missing schema definitions are being added to complete the API
292
+ specification.
293
+ <br />
294
+ <br />
295
+ <strong>Missing Schemas:</strong> {event.missed.length}
296
+ <br />
297
+ <strong>Added Schemas:</strong> {Object.keys(event.schemas).length}
298
+ <br />
299
+ <br />
300
+ {event.missed.length > 0 && (
301
+ <>
302
+ <strong>Complemented Types:</strong>
303
+ <br />
304
+ {event.missed.slice(0, 5).map((schema: string, idx: number) => (
305
+ <div key={idx} style={{ marginTop: "0.25rem" }}>
306
+ • {schema}
307
+ </div>
308
+ ))}
309
+ {event.missed.length > 5 && (
310
+ <div style={{ marginTop: "0.25rem" }}>
311
+ ... and {event.missed.length - 5} more
312
+ </div>
313
+ )}
314
+ </>
315
+ )}
316
+ </>
317
+ ),
318
+ isError: false,
319
+ isSuccess: true,
320
+ step: event.step,
321
+ };
322
+ default:
323
+ event satisfies never;
324
+ throw new Error("Unknown validation event type");
325
+ }
326
+ }
@@ -0,0 +1,169 @@
1
+ # AutoBe Event Components
2
+
3
+ Event UI components for the AutoBe platform with grouping and collapsible functionality.
4
+
5
+ ## Individual Event Components
6
+
7
+ ### AutoBeProgressEventMovie
8
+ Shows progress tracking events with progress bars.
9
+
10
+ ```tsx
11
+ import { AutoBeProgressEventMovie } from "@autobe/ui/events";
12
+
13
+ <AutoBeProgressEventMovie event={progressEvent} />
14
+ ```
15
+
16
+ ### AutoBeValidateEventMovie
17
+ Shows validation events with error/success states.
18
+
19
+ ```tsx
20
+ import { AutoBeValidateEventMovie } from "@autobe/ui/events";
21
+
22
+ <AutoBeValidateEventMovie event={validateEvent} />
23
+ ```
24
+
25
+ ### AutoBeScenarioEventMovie
26
+ Shows scenario analysis and component generation events.
27
+
28
+ ```tsx
29
+ import { AutoBeScenarioEventMovie } from "@autobe/ui/events";
30
+
31
+ <AutoBeScenarioEventMovie event={scenarioEvent} />
32
+ ```
33
+
34
+ ### AutoBeStartEventMovie
35
+ Shows pipeline start events with simple status display.
36
+
37
+ ```tsx
38
+ import { AutoBeStartEventMovie } from "@autobe/ui/events";
39
+
40
+ <AutoBeStartEventMovie event={startEvent} />
41
+ ```
42
+
43
+ ## Grouped Event Components
44
+
45
+ ### ValidateEventGroup
46
+ Groups validation events with success rate statistics.
47
+
48
+ ```tsx
49
+ import { ValidateEventGroup } from "@autobe/ui/events";
50
+
51
+ <ValidateEventGroup
52
+ events={validateEvents}
53
+ defaultCollapsed={true}
54
+ />
55
+ ```
56
+
57
+ ## Automatic Event Grouping
58
+
59
+ Use the `groupEvents` utility to automatically group events by type:
60
+
61
+ ```tsx
62
+ import { groupEvents } from "@autobe/ui/events/utils/eventGrouper";
63
+
64
+ const EventList = ({ events }: { events: AutoBeEvent[] }) => {
65
+ const groupedComponents = groupEvents(events, {
66
+ minGroupSize: 3, // Minimum events to form a group
67
+ defaultCollapsed: true, // Start collapsed
68
+ enableGrouping: true, // Enable grouping
69
+ });
70
+
71
+ return (
72
+ <div>
73
+ {groupedComponents}
74
+ </div>
75
+ );
76
+ };
77
+ ```
78
+
79
+ ## Custom Collapsible Groups
80
+
81
+ Create custom event groups using the `CollapsibleEventGroup` component:
82
+
83
+ ```tsx
84
+ import { CollapsibleEventGroup } from "@autobe/ui/events";
85
+
86
+ <CollapsibleEventGroup
87
+ events={customEvents}
88
+ title="Custom Event Group"
89
+ iconType="info"
90
+ getTimestamp={(event) => event.created_at}
91
+ renderEvent={(event, index) => <CustomEventComponent event={event} />}
92
+ renderSummary={(events) => <CustomSummary events={events} />}
93
+ defaultCollapsed={true}
94
+ description="Custom grouped events"
95
+ />
96
+ ```
97
+
98
+ ## Common Components
99
+
100
+ ### EventCard
101
+ Basic card container for consistent styling.
102
+
103
+ ```tsx
104
+ import { EventCard } from "@autobe/ui/events";
105
+
106
+ <EventCard>
107
+ <div>Your content here</div>
108
+ </EventCard>
109
+ ```
110
+
111
+ ### EventHeader
112
+ Standardized header with icon, title, and timestamp.
113
+
114
+ ```tsx
115
+ import { EventHeader } from "@autobe/ui/events";
116
+
117
+ <EventHeader
118
+ title="Event Title"
119
+ timestamp="2024-01-01T12:00:00Z"
120
+ iconType="success"
121
+ step={1}
122
+ />
123
+ ```
124
+
125
+ ### EventContent
126
+ Consistent content area styling.
127
+
128
+ ```tsx
129
+ import { EventContent } from "@autobe/ui/events";
130
+
131
+ <EventContent>
132
+ <div>Your content here</div>
133
+ </EventContent>
134
+ ```
135
+
136
+ ### EventIcon
137
+ Consistent icons for different event states.
138
+
139
+ ```tsx
140
+ import { EventIcon } from "@autobe/ui/events";
141
+
142
+ <EventIcon type="success" size={16} />
143
+ ```
144
+
145
+ Available icon types: `success`, `progress`, `warning`, `error`, `info`, `start`
146
+
147
+ ### ProgressBar
148
+ Reusable progress bar component.
149
+
150
+ ```tsx
151
+ import { ProgressBar } from "@autobe/ui/events";
152
+
153
+ <ProgressBar
154
+ current={7}
155
+ total={10}
156
+ color="#4caf50"
157
+ showLabel={true}
158
+ />
159
+ ```
160
+
161
+ ## Features
162
+
163
+ - 🎯 **Consistent Design**: All components follow the same design system
164
+ - 📱 **Responsive**: Works on all screen sizes
165
+ - 🔄 **Collapsible**: Group events to reduce visual clutter
166
+ - 📊 **Statistics**: Automatic progress and error rate calculations
167
+ - 🎨 **Customizable**: Easy to extend and customize
168
+ - 💎 **TypeScript**: Full type safety and IntelliSense support
169
+ - ⚡ **Performance**: Efficient rendering with minimal re-renders
@@ -0,0 +1,220 @@
1
+ import { ReactNode, useState } from "react";
2
+
3
+ import { EventCard } from "./EventCard";
4
+ import { EventContent } from "./EventContent";
5
+ import { EventIcon, EventIconType } from "./EventIcon";
6
+
7
+ export interface ICollapsibleEventGroupProps<T = any> {
8
+ /** Array of events of the same type */
9
+ events: T[];
10
+ /** Title for the group */
11
+ title: string;
12
+ /** Icon type for the group header */
13
+ iconType: EventIconType;
14
+ /** Function to render individual events */
15
+ renderEvent: (event: T, index: number) => ReactNode;
16
+ /** Function to get timestamp from event (for header) */
17
+ getTimestamp: (event: T) => string;
18
+ /** Optional custom summary content */
19
+ renderSummary?: (events: T[]) => ReactNode;
20
+ /** Initial collapsed state */
21
+ defaultCollapsed?: boolean;
22
+ /** Custom group description */
23
+ description?: string;
24
+ /** Card variant based on event status */
25
+ variant?: "default" | "success" | "error" | "warning";
26
+ }
27
+
28
+ /**
29
+ * Collapsible event group component Groups multiple events of the same type
30
+ * with expand/collapse functionality
31
+ */
32
+ export const CollapsibleEventGroup = <T,>(
33
+ props: ICollapsibleEventGroupProps<T>,
34
+ ) => {
35
+ const {
36
+ events,
37
+ title,
38
+ iconType,
39
+ renderEvent,
40
+ getTimestamp,
41
+ renderSummary,
42
+ defaultCollapsed = true,
43
+ description,
44
+ variant = "default",
45
+ } = props;
46
+
47
+ const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
48
+
49
+ if (events.length === 0) {
50
+ return null;
51
+ }
52
+
53
+ // Use the latest event's timestamp for the group header
54
+ const latestTimestamp = getTimestamp(events[events.length - 1]);
55
+
56
+ const toggleCollapse = () => {
57
+ setIsCollapsed(!isCollapsed);
58
+ };
59
+
60
+ const defaultSummary = (
61
+ <>
62
+ {description && (
63
+ <>
64
+ {description}
65
+ <br />
66
+ <br />
67
+ </>
68
+ )}
69
+ <strong>Total Events:</strong> {events.length}
70
+ <br />
71
+ <strong>Status:</strong> {isCollapsed ? "Collapsed" : "Expanded"}
72
+ </>
73
+ );
74
+
75
+ return (
76
+ <EventCard variant={variant}>
77
+ {/* Header */}
78
+ <div
79
+ style={{
80
+ display: "flex",
81
+ alignItems: "center",
82
+ justifyContent: "space-between",
83
+ marginBottom: "1rem",
84
+ }}
85
+ >
86
+ <div
87
+ style={{
88
+ display: "flex",
89
+ alignItems: "center",
90
+ gap: "0.75rem",
91
+ }}
92
+ >
93
+ <EventIcon type={iconType} />
94
+
95
+ <div>
96
+ <h3
97
+ style={{
98
+ fontSize: "1.125rem",
99
+ fontWeight: "600",
100
+ color: "#1e293b",
101
+ margin: 0,
102
+ marginBottom: "0.25rem",
103
+ }}
104
+ >
105
+ {title}
106
+ </h3>
107
+ <div
108
+ style={{
109
+ fontSize: "0.75rem",
110
+ color: "#64748b",
111
+ }}
112
+ >
113
+ {events.length} event{events.length !== 1 ? "s" : ""}
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <div
119
+ style={{
120
+ fontSize: "0.75rem",
121
+ color: "#64748b",
122
+ textAlign: "right",
123
+ }}
124
+ >
125
+ {latestTimestamp}
126
+ </div>
127
+ </div>
128
+
129
+ {/* Summary content when collapsed */}
130
+ {isCollapsed && (
131
+ <EventContent>
132
+ {renderSummary ? renderSummary(events) : defaultSummary}
133
+ </EventContent>
134
+ )}
135
+
136
+ {/* Individual events when expanded */}
137
+ {!isCollapsed && (
138
+ <div
139
+ style={{
140
+ display: "flex",
141
+ flexDirection: "column",
142
+ gap: "0.5rem",
143
+ }}
144
+ >
145
+ {events.map((event, index) => (
146
+ <div
147
+ key={index}
148
+ style={{
149
+ border: "1px solid #e2e8f0",
150
+ borderRadius: "0.5rem",
151
+ overflow: "hidden",
152
+ }}
153
+ >
154
+ {renderEvent(event, index)}
155
+ </div>
156
+ ))}
157
+ </div>
158
+ )}
159
+
160
+ {/* Expand/Collapse Button */}
161
+ <div
162
+ style={{
163
+ display: "flex",
164
+ justifyContent: "center",
165
+ marginTop: "1rem",
166
+ }}
167
+ >
168
+ <button
169
+ onClick={toggleCollapse}
170
+ style={{
171
+ display: "flex",
172
+ alignItems: "center",
173
+ gap: "0.5rem",
174
+ padding: "0.5rem 1rem",
175
+ backgroundColor: "#f8fafc",
176
+ border: "1px solid #e2e8f0",
177
+ borderRadius: "0.5rem",
178
+ cursor: "pointer",
179
+ fontSize: "0.875rem",
180
+ color: "#64748b",
181
+ transition: "all 0.2s ease",
182
+ }}
183
+ onMouseEnter={(e) => {
184
+ e.currentTarget.style.backgroundColor = "#f1f5f9";
185
+ e.currentTarget.style.borderColor = "#cbd5e1";
186
+ }}
187
+ onMouseLeave={(e) => {
188
+ e.currentTarget.style.backgroundColor = "#f8fafc";
189
+ e.currentTarget.style.borderColor = "#e2e8f0";
190
+ }}
191
+ >
192
+ <span>{isCollapsed ? "Expand" : "Collapse"}</span>
193
+ <div
194
+ style={{
195
+ width: "1rem",
196
+ height: "1rem",
197
+ display: "flex",
198
+ alignItems: "center",
199
+ justifyContent: "center",
200
+ transition: "transform 0.2s ease",
201
+ transform: isCollapsed ? "rotate(0deg)" : "rotate(180deg)",
202
+ }}
203
+ >
204
+ <svg
205
+ width="12"
206
+ height="12"
207
+ viewBox="0 0 24 24"
208
+ fill="currentColor"
209
+ style={{ color: "#64748b" }}
210
+ >
211
+ <path d="M7 10l5 5 5-5z" />
212
+ </svg>
213
+ </div>
214
+ </button>
215
+ </div>
216
+ </EventCard>
217
+ );
218
+ };
219
+
220
+ export default CollapsibleEventGroup;