@archiva/archiva-nextjs 0.3.0 → 0.3.1

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.
@@ -22,8 +22,12 @@ type TimelineItem = {
22
22
  };
23
23
  type TimelineProps = {
24
24
  entityId?: string;
25
+ tenantId?: string;
25
26
  actorId?: string;
26
27
  entityType?: string;
28
+ actionKey?: string;
29
+ actionDescription?: string;
30
+ actorType?: 'user' | 'service' | 'system' | ('user' | 'service' | 'system')[];
27
31
  initialLimit?: number;
28
32
  className?: string;
29
33
  emptyMessage?: string;
@@ -34,7 +38,7 @@ type TimelineProps = {
34
38
  className?: string;
35
39
  }> | undefined;
36
40
  };
37
- declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, showSearch, showFilters, showSystemAndServices, getActorAvatar, }: TimelineProps): react_jsx_runtime.JSX.Element;
41
+ declare function Timeline({ entityId, tenantId, actorId, entityType, actionKey, actionDescription, actorType, initialLimit, className, emptyMessage, showSearch, showFilters, showSystemAndServices, getActorAvatar, }: TimelineProps): react_jsx_runtime.JSX.Element;
38
42
 
39
43
  type ArchivaContextValue = {
40
44
  apiBaseUrl: string;
@@ -22,8 +22,12 @@ type TimelineItem = {
22
22
  };
23
23
  type TimelineProps = {
24
24
  entityId?: string;
25
+ tenantId?: string;
25
26
  actorId?: string;
26
27
  entityType?: string;
28
+ actionKey?: string;
29
+ actionDescription?: string;
30
+ actorType?: 'user' | 'service' | 'system' | ('user' | 'service' | 'system')[];
27
31
  initialLimit?: number;
28
32
  className?: string;
29
33
  emptyMessage?: string;
@@ -34,7 +38,7 @@ type TimelineProps = {
34
38
  className?: string;
35
39
  }> | undefined;
36
40
  };
37
- declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, showSearch, showFilters, showSystemAndServices, getActorAvatar, }: TimelineProps): react_jsx_runtime.JSX.Element;
41
+ declare function Timeline({ entityId, tenantId, actorId, entityType, actionKey, actionDescription, actorType, initialLimit, className, emptyMessage, showSearch, showFilters, showSystemAndServices, getActorAvatar, }: TimelineProps): react_jsx_runtime.JSX.Element;
38
42
 
39
43
  type ArchivaContextValue = {
40
44
  apiBaseUrl: string;
@@ -205,7 +205,7 @@ var CloudCogIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime2.js
205
205
  function eventToTimelineItem(event, getActorAvatar) {
206
206
  const actionValue = event.actionKey || event.action || "";
207
207
  const action = actionValue.charAt(0).toUpperCase() + actionValue.slice(1);
208
- const description = action;
208
+ const description = event.actionDescription || "";
209
209
  const actorId = event.actorId || "unknown";
210
210
  const userName = actorId.includes(":") ? actorId.split(":")[1] || actorId : actorId;
211
211
  const userHandle = userName;
@@ -252,7 +252,8 @@ function eventToTimelineItem(event, getActorAvatar) {
252
252
  // Matches ActivityLog.tsx line 361
253
253
  statusIndicator: false,
254
254
  // Can be set to true for recent items - matches ActivityLog.tsx line 362
255
- data: event
255
+ data: event,
256
+ badge: action
256
257
  };
257
258
  }
258
259
  async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, params) {
@@ -260,12 +261,18 @@ async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, par
260
261
  if (params.entityId) {
261
262
  url.searchParams.set("entityId", params.entityId);
262
263
  }
264
+ if (params.tenantId) {
265
+ url.searchParams.set("tenantId", params.tenantId);
266
+ }
263
267
  if (params.actorId) {
264
268
  url.searchParams.set("actorId", params.actorId);
265
269
  }
266
270
  if (params.entityType) {
267
271
  url.searchParams.set("entityType", params.entityType);
268
272
  }
273
+ if (params.actionKey) {
274
+ url.searchParams.set("actionKey", params.actionKey);
275
+ }
269
276
  if (params.actorType) {
270
277
  const actorTypeValue = Array.isArray(params.actorType) ? params.actorType.join(",") : params.actorType;
271
278
  url.searchParams.set("actorType", actorTypeValue);
@@ -276,6 +283,9 @@ async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, par
276
283
  if (params.cursor) {
277
284
  url.searchParams.set("cursor", params.cursor);
278
285
  }
286
+ if (params.q) {
287
+ url.searchParams.set("q", params.q);
288
+ }
279
289
  const makeRequest = async (token2) => {
280
290
  return fetch(url.toString(), {
281
291
  headers: {
@@ -408,8 +418,15 @@ function SimpleAvatar({
408
418
  }
409
419
  );
410
420
  }
411
- function applyClientSideFilters(events, searchQuery, showSystemAndServices) {
421
+ function applyClientSideFilters(events, searchQuery, showSystemAndServices, actionDescription) {
412
422
  let filtered = events;
423
+ if (actionDescription) {
424
+ filtered = filtered.filter((event) => {
425
+ const eventActionDescription = (event.actionDescription || "").toLowerCase();
426
+ const filterActionDescription = actionDescription.toLowerCase();
427
+ return eventActionDescription.includes(filterActionDescription);
428
+ });
429
+ }
413
430
  if (!showSystemAndServices) {
414
431
  filtered = filtered.filter((event) => {
415
432
  if (event.actorType) {
@@ -428,13 +445,18 @@ function applyClientSideFilters(events, searchQuery, showSystemAndServices) {
428
445
  const query = searchQuery.toLowerCase();
429
446
  return filtered.filter((event) => {
430
447
  const actionValue = (event.actionKey || event.action || "").toLowerCase();
431
- return actionValue.includes(query) || event.entityType.toLowerCase().includes(query) || event.entityId.toLowerCase().includes(query) || event.actorId && event.actorId.toLowerCase().includes(query) || event.source && event.source.toLowerCase().includes(query) || event.actorDisplay && event.actorDisplay.toLowerCase().includes(query);
448
+ const actionDescValue = (event.actionDescription || "").toLowerCase();
449
+ return actionValue.includes(query) || actionDescValue.includes(query) || event.entityType.toLowerCase().includes(query) || event.entityId.toLowerCase().includes(query) || event.actorId && event.actorId.toLowerCase().includes(query) || event.source && event.source.toLowerCase().includes(query) || event.actorDisplay && event.actorDisplay.toLowerCase().includes(query);
432
450
  });
433
451
  }
434
452
  function Timeline({
435
453
  entityId,
454
+ tenantId,
436
455
  actorId,
437
456
  entityType,
457
+ actionKey,
458
+ actionDescription,
459
+ actorType,
438
460
  initialLimit = 100,
439
461
  className,
440
462
  emptyMessage = "No events yet.",
@@ -451,25 +473,64 @@ function Timeline({
451
473
  const [error, setError] = React2.useState(null);
452
474
  const [hasMore, setHasMore] = React2.useState(false);
453
475
  const [searchQuery, setSearchQuery] = React2.useState("");
476
+ const baseFilters = React2.useMemo(() => {
477
+ const filters = {};
478
+ if (entityId) filters.entityId = entityId;
479
+ if (tenantId) filters.tenantId = tenantId;
480
+ if (actorId) filters.actorId = actorId;
481
+ if (entityType) filters.entityType = entityType;
482
+ if (actionKey) filters.actionKey = actionKey;
483
+ if (actorType) filters.actorType = actorType;
484
+ if (!showSystemAndServices && !actorType) {
485
+ filters.actorType = "user";
486
+ }
487
+ return filters;
488
+ }, [entityId, tenantId, actorId, entityType, actionKey, actorType, showSystemAndServices]);
489
+ const [uiFilters, setUiFilters] = React2.useState({});
490
+ const mergedFilters = React2.useMemo(() => {
491
+ const merged = {
492
+ ...baseFilters,
493
+ limit: initialLimit
494
+ };
495
+ if (!baseFilters.tenantId && uiFilters.tenantId) {
496
+ merged.tenantId = uiFilters.tenantId;
497
+ }
498
+ if (!baseFilters.actionKey && uiFilters.actionKey) {
499
+ merged.actionKey = uiFilters.actionKey;
500
+ }
501
+ if (!baseFilters.entityId && uiFilters.entityId) {
502
+ merged.entityId = uiFilters.entityId;
503
+ }
504
+ if (!baseFilters.entityType && uiFilters.entityType) {
505
+ merged.entityType = uiFilters.entityType;
506
+ }
507
+ if (!baseFilters.actorId && uiFilters.actorId) {
508
+ merged.actorId = uiFilters.actorId;
509
+ }
510
+ if (!baseFilters.actorType && uiFilters.actorType) {
511
+ merged.actorType = uiFilters.actorType;
512
+ }
513
+ return merged;
514
+ }, [baseFilters, uiFilters, initialLimit]);
515
+ const lockedFilters = React2.useMemo(() => ({
516
+ tenantId: !!tenantId,
517
+ actionKey: !!actionKey,
518
+ entityId: !!entityId,
519
+ entityType: !!entityType,
520
+ actorId: !!actorId,
521
+ actorType: !!actorType
522
+ }), [tenantId, actionKey, entityId, entityType, actorId, actorType]);
454
523
  const queryParamsRef = React2.useRef({});
455
524
  const queryParamsKeyRef = React2.useRef("");
456
525
  const queryParams = React2.useMemo(() => {
457
- const params = {
458
- entityId,
459
- actorId,
460
- entityType,
461
- // Filter by actorType on API side
462
- actorType: showSystemAndServices ? void 0 : "user",
463
- limit: initialLimit
464
- };
465
- const key = JSON.stringify(params);
526
+ const key = JSON.stringify(mergedFilters);
466
527
  if (key !== queryParamsKeyRef.current) {
467
528
  queryParamsKeyRef.current = key;
468
- queryParamsRef.current = params;
469
- return params;
529
+ queryParamsRef.current = mergedFilters;
530
+ return mergedFilters;
470
531
  }
471
532
  return queryParamsRef.current;
472
- }, [entityId, actorId, entityType, initialLimit, showSystemAndServices]);
533
+ }, [mergedFilters]);
473
534
  const allEventsRef = React2.useRef([]);
474
535
  React2.useEffect(() => {
475
536
  allEventsRef.current = allEvents;
@@ -516,12 +577,13 @@ function Timeline({
516
577
  },
517
578
  [queryParams, apiBaseUrl, getToken, forceRefreshToken, cursor]
518
579
  );
580
+ const filterKey = React2.useMemo(() => JSON.stringify(mergedFilters), [mergedFilters]);
519
581
  React2.useEffect(() => {
520
582
  setCursor(void 0);
521
583
  setAllEvents([]);
522
584
  setPreviousEvents([]);
523
585
  void load({ reset: true });
524
- }, [entityId, actorId, entityType, showSystemAndServices, initialLimit]);
586
+ }, [filterKey]);
525
587
  const eventsToFilter = React2.useMemo(() => {
526
588
  if (loading && allEvents.length === 0 && previousEvents.length > 0) {
527
589
  return previousEvents;
@@ -529,8 +591,8 @@ function Timeline({
529
591
  return allEvents;
530
592
  }, [allEvents, loading, previousEvents]);
531
593
  const filteredEvents = React2.useMemo(() => {
532
- return applyClientSideFilters(eventsToFilter, searchQuery, showSystemAndServices);
533
- }, [eventsToFilter, searchQuery, showSystemAndServices]);
594
+ return applyClientSideFilters(eventsToFilter, searchQuery, showSystemAndServices, actionDescription);
595
+ }, [eventsToFilter, searchQuery, showSystemAndServices, actionDescription]);
534
596
  const timelineItems = React2.useMemo(() => {
535
597
  return filteredEvents.slice(0, 10).map(
536
598
  (event) => eventToTimelineItem(event, getActorAvatar)
@@ -555,6 +617,144 @@ function Timeline({
555
617
  placeholder: "Search actions, entities, IDs, actors, sources..."
556
618
  }
557
619
  ) }),
620
+ showFilters && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "1rem", padding: "1rem", backgroundColor: "#f9fafb", borderRadius: "0.375rem", display: "flex", flexWrap: "wrap", gap: "0.75rem" }, children: [
621
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: [
623
+ "Tenant ID ",
624
+ lockedFilters.tenantId && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#9ca3af" }, children: "(Locked)" })
625
+ ] }),
626
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
627
+ "input",
628
+ {
629
+ type: "text",
630
+ placeholder: "All tenants",
631
+ value: lockedFilters.tenantId ? tenantId ?? "" : uiFilters.tenantId ?? "",
632
+ onChange: (e) => {
633
+ if (!lockedFilters.tenantId) {
634
+ setUiFilters((prev) => ({ ...prev, tenantId: e.target.value || void 0 }));
635
+ }
636
+ },
637
+ disabled: lockedFilters.tenantId,
638
+ style: {
639
+ padding: "0.375rem 0.5rem",
640
+ border: "1px solid #d1d5db",
641
+ borderRadius: "0.375rem",
642
+ fontSize: "0.875rem",
643
+ backgroundColor: lockedFilters.tenantId ? "#f3f4f6" : "white",
644
+ cursor: lockedFilters.tenantId ? "not-allowed" : "text"
645
+ }
646
+ }
647
+ )
648
+ ] }),
649
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
650
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: [
651
+ "Action Key ",
652
+ lockedFilters.actionKey && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#9ca3af" }, children: "(Locked)" })
653
+ ] }),
654
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
655
+ "input",
656
+ {
657
+ type: "text",
658
+ placeholder: "All actions",
659
+ value: lockedFilters.actionKey ? actionKey ?? "" : uiFilters.actionKey ?? "",
660
+ onChange: (e) => {
661
+ if (!lockedFilters.actionKey) {
662
+ setUiFilters((prev) => ({ ...prev, actionKey: e.target.value || void 0 }));
663
+ }
664
+ },
665
+ disabled: lockedFilters.actionKey,
666
+ style: {
667
+ padding: "0.375rem 0.5rem",
668
+ border: "1px solid #d1d5db",
669
+ borderRadius: "0.375rem",
670
+ fontSize: "0.875rem",
671
+ backgroundColor: lockedFilters.actionKey ? "#f3f4f6" : "white",
672
+ cursor: lockedFilters.actionKey ? "not-allowed" : "text"
673
+ }
674
+ }
675
+ )
676
+ ] }),
677
+ !lockedFilters.entityId && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: "Entity ID" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
680
+ "input",
681
+ {
682
+ type: "text",
683
+ placeholder: "All entities",
684
+ value: uiFilters.entityId ?? "",
685
+ onChange: (e) => {
686
+ setUiFilters((prev) => ({ ...prev, entityId: e.target.value || void 0 }));
687
+ },
688
+ style: {
689
+ padding: "0.375rem 0.5rem",
690
+ border: "1px solid #d1d5db",
691
+ borderRadius: "0.375rem",
692
+ fontSize: "0.875rem"
693
+ }
694
+ }
695
+ )
696
+ ] }),
697
+ !lockedFilters.entityType && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: "Entity Type" }),
699
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
700
+ "input",
701
+ {
702
+ type: "text",
703
+ placeholder: "All types",
704
+ value: uiFilters.entityType ?? "",
705
+ onChange: (e) => {
706
+ setUiFilters((prev) => ({ ...prev, entityType: e.target.value || void 0 }));
707
+ },
708
+ style: {
709
+ padding: "0.375rem 0.5rem",
710
+ border: "1px solid #d1d5db",
711
+ borderRadius: "0.375rem",
712
+ fontSize: "0.875rem"
713
+ }
714
+ }
715
+ )
716
+ ] }),
717
+ !lockedFilters.actorId && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
718
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: "Actor ID" }),
719
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
720
+ "input",
721
+ {
722
+ type: "text",
723
+ placeholder: "All actors",
724
+ value: uiFilters.actorId ?? "",
725
+ onChange: (e) => {
726
+ setUiFilters((prev) => ({ ...prev, actorId: e.target.value || void 0 }));
727
+ },
728
+ style: {
729
+ padding: "0.375rem 0.5rem",
730
+ border: "1px solid #d1d5db",
731
+ borderRadius: "0.375rem",
732
+ fontSize: "0.875rem"
733
+ }
734
+ }
735
+ )
736
+ ] }),
737
+ (Object.keys(uiFilters).length > 0 || searchQuery) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "flex-end" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
738
+ "button",
739
+ {
740
+ type: "button",
741
+ onClick: () => {
742
+ setUiFilters({});
743
+ setSearchQuery("");
744
+ },
745
+ style: {
746
+ padding: "0.375rem 0.75rem",
747
+ backgroundColor: "#ef4444",
748
+ color: "white",
749
+ border: "none",
750
+ borderRadius: "0.375rem",
751
+ fontSize: "0.875rem",
752
+ cursor: "pointer"
753
+ },
754
+ children: "Clear"
755
+ }
756
+ ) })
757
+ ] }),
558
758
  (searchQuery || filteredEvents.length !== eventsToFilter.length) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", color: "#6b7280" }, children: [
559
759
  "Showing ",
560
760
  filteredEvents.length,
@@ -78,7 +78,7 @@ var CloudCogIcon = ({ className }) => /* @__PURE__ */ jsxs(
78
78
  function eventToTimelineItem(event, getActorAvatar) {
79
79
  const actionValue = event.actionKey || event.action || "";
80
80
  const action = actionValue.charAt(0).toUpperCase() + actionValue.slice(1);
81
- const description = action;
81
+ const description = event.actionDescription || "";
82
82
  const actorId = event.actorId || "unknown";
83
83
  const userName = actorId.includes(":") ? actorId.split(":")[1] || actorId : actorId;
84
84
  const userHandle = userName;
@@ -125,7 +125,8 @@ function eventToTimelineItem(event, getActorAvatar) {
125
125
  // Matches ActivityLog.tsx line 361
126
126
  statusIndicator: false,
127
127
  // Can be set to true for recent items - matches ActivityLog.tsx line 362
128
- data: event
128
+ data: event,
129
+ badge: action
129
130
  };
130
131
  }
131
132
  async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, params) {
@@ -133,12 +134,18 @@ async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, par
133
134
  if (params.entityId) {
134
135
  url.searchParams.set("entityId", params.entityId);
135
136
  }
137
+ if (params.tenantId) {
138
+ url.searchParams.set("tenantId", params.tenantId);
139
+ }
136
140
  if (params.actorId) {
137
141
  url.searchParams.set("actorId", params.actorId);
138
142
  }
139
143
  if (params.entityType) {
140
144
  url.searchParams.set("entityType", params.entityType);
141
145
  }
146
+ if (params.actionKey) {
147
+ url.searchParams.set("actionKey", params.actionKey);
148
+ }
142
149
  if (params.actorType) {
143
150
  const actorTypeValue = Array.isArray(params.actorType) ? params.actorType.join(",") : params.actorType;
144
151
  url.searchParams.set("actorType", actorTypeValue);
@@ -149,6 +156,9 @@ async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, par
149
156
  if (params.cursor) {
150
157
  url.searchParams.set("cursor", params.cursor);
151
158
  }
159
+ if (params.q) {
160
+ url.searchParams.set("q", params.q);
161
+ }
152
162
  const makeRequest = async (token2) => {
153
163
  return fetch(url.toString(), {
154
164
  headers: {
@@ -281,8 +291,15 @@ function SimpleAvatar({
281
291
  }
282
292
  );
283
293
  }
284
- function applyClientSideFilters(events, searchQuery, showSystemAndServices) {
294
+ function applyClientSideFilters(events, searchQuery, showSystemAndServices, actionDescription) {
285
295
  let filtered = events;
296
+ if (actionDescription) {
297
+ filtered = filtered.filter((event) => {
298
+ const eventActionDescription = (event.actionDescription || "").toLowerCase();
299
+ const filterActionDescription = actionDescription.toLowerCase();
300
+ return eventActionDescription.includes(filterActionDescription);
301
+ });
302
+ }
286
303
  if (!showSystemAndServices) {
287
304
  filtered = filtered.filter((event) => {
288
305
  if (event.actorType) {
@@ -301,13 +318,18 @@ function applyClientSideFilters(events, searchQuery, showSystemAndServices) {
301
318
  const query = searchQuery.toLowerCase();
302
319
  return filtered.filter((event) => {
303
320
  const actionValue = (event.actionKey || event.action || "").toLowerCase();
304
- return actionValue.includes(query) || event.entityType.toLowerCase().includes(query) || event.entityId.toLowerCase().includes(query) || event.actorId && event.actorId.toLowerCase().includes(query) || event.source && event.source.toLowerCase().includes(query) || event.actorDisplay && event.actorDisplay.toLowerCase().includes(query);
321
+ const actionDescValue = (event.actionDescription || "").toLowerCase();
322
+ return actionValue.includes(query) || actionDescValue.includes(query) || event.entityType.toLowerCase().includes(query) || event.entityId.toLowerCase().includes(query) || event.actorId && event.actorId.toLowerCase().includes(query) || event.source && event.source.toLowerCase().includes(query) || event.actorDisplay && event.actorDisplay.toLowerCase().includes(query);
305
323
  });
306
324
  }
307
325
  function Timeline({
308
326
  entityId,
327
+ tenantId,
309
328
  actorId,
310
329
  entityType,
330
+ actionKey,
331
+ actionDescription,
332
+ actorType,
311
333
  initialLimit = 100,
312
334
  className,
313
335
  emptyMessage = "No events yet.",
@@ -324,25 +346,64 @@ function Timeline({
324
346
  const [error, setError] = React.useState(null);
325
347
  const [hasMore, setHasMore] = React.useState(false);
326
348
  const [searchQuery, setSearchQuery] = React.useState("");
349
+ const baseFilters = React.useMemo(() => {
350
+ const filters = {};
351
+ if (entityId) filters.entityId = entityId;
352
+ if (tenantId) filters.tenantId = tenantId;
353
+ if (actorId) filters.actorId = actorId;
354
+ if (entityType) filters.entityType = entityType;
355
+ if (actionKey) filters.actionKey = actionKey;
356
+ if (actorType) filters.actorType = actorType;
357
+ if (!showSystemAndServices && !actorType) {
358
+ filters.actorType = "user";
359
+ }
360
+ return filters;
361
+ }, [entityId, tenantId, actorId, entityType, actionKey, actorType, showSystemAndServices]);
362
+ const [uiFilters, setUiFilters] = React.useState({});
363
+ const mergedFilters = React.useMemo(() => {
364
+ const merged = {
365
+ ...baseFilters,
366
+ limit: initialLimit
367
+ };
368
+ if (!baseFilters.tenantId && uiFilters.tenantId) {
369
+ merged.tenantId = uiFilters.tenantId;
370
+ }
371
+ if (!baseFilters.actionKey && uiFilters.actionKey) {
372
+ merged.actionKey = uiFilters.actionKey;
373
+ }
374
+ if (!baseFilters.entityId && uiFilters.entityId) {
375
+ merged.entityId = uiFilters.entityId;
376
+ }
377
+ if (!baseFilters.entityType && uiFilters.entityType) {
378
+ merged.entityType = uiFilters.entityType;
379
+ }
380
+ if (!baseFilters.actorId && uiFilters.actorId) {
381
+ merged.actorId = uiFilters.actorId;
382
+ }
383
+ if (!baseFilters.actorType && uiFilters.actorType) {
384
+ merged.actorType = uiFilters.actorType;
385
+ }
386
+ return merged;
387
+ }, [baseFilters, uiFilters, initialLimit]);
388
+ const lockedFilters = React.useMemo(() => ({
389
+ tenantId: !!tenantId,
390
+ actionKey: !!actionKey,
391
+ entityId: !!entityId,
392
+ entityType: !!entityType,
393
+ actorId: !!actorId,
394
+ actorType: !!actorType
395
+ }), [tenantId, actionKey, entityId, entityType, actorId, actorType]);
327
396
  const queryParamsRef = React.useRef({});
328
397
  const queryParamsKeyRef = React.useRef("");
329
398
  const queryParams = React.useMemo(() => {
330
- const params = {
331
- entityId,
332
- actorId,
333
- entityType,
334
- // Filter by actorType on API side
335
- actorType: showSystemAndServices ? void 0 : "user",
336
- limit: initialLimit
337
- };
338
- const key = JSON.stringify(params);
399
+ const key = JSON.stringify(mergedFilters);
339
400
  if (key !== queryParamsKeyRef.current) {
340
401
  queryParamsKeyRef.current = key;
341
- queryParamsRef.current = params;
342
- return params;
402
+ queryParamsRef.current = mergedFilters;
403
+ return mergedFilters;
343
404
  }
344
405
  return queryParamsRef.current;
345
- }, [entityId, actorId, entityType, initialLimit, showSystemAndServices]);
406
+ }, [mergedFilters]);
346
407
  const allEventsRef = React.useRef([]);
347
408
  React.useEffect(() => {
348
409
  allEventsRef.current = allEvents;
@@ -389,12 +450,13 @@ function Timeline({
389
450
  },
390
451
  [queryParams, apiBaseUrl, getToken, forceRefreshToken, cursor]
391
452
  );
453
+ const filterKey = React.useMemo(() => JSON.stringify(mergedFilters), [mergedFilters]);
392
454
  React.useEffect(() => {
393
455
  setCursor(void 0);
394
456
  setAllEvents([]);
395
457
  setPreviousEvents([]);
396
458
  void load({ reset: true });
397
- }, [entityId, actorId, entityType, showSystemAndServices, initialLimit]);
459
+ }, [filterKey]);
398
460
  const eventsToFilter = React.useMemo(() => {
399
461
  if (loading && allEvents.length === 0 && previousEvents.length > 0) {
400
462
  return previousEvents;
@@ -402,8 +464,8 @@ function Timeline({
402
464
  return allEvents;
403
465
  }, [allEvents, loading, previousEvents]);
404
466
  const filteredEvents = React.useMemo(() => {
405
- return applyClientSideFilters(eventsToFilter, searchQuery, showSystemAndServices);
406
- }, [eventsToFilter, searchQuery, showSystemAndServices]);
467
+ return applyClientSideFilters(eventsToFilter, searchQuery, showSystemAndServices, actionDescription);
468
+ }, [eventsToFilter, searchQuery, showSystemAndServices, actionDescription]);
407
469
  const timelineItems = React.useMemo(() => {
408
470
  return filteredEvents.slice(0, 10).map(
409
471
  (event) => eventToTimelineItem(event, getActorAvatar)
@@ -428,6 +490,144 @@ function Timeline({
428
490
  placeholder: "Search actions, entities, IDs, actors, sources..."
429
491
  }
430
492
  ) }),
493
+ showFilters && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "1rem", padding: "1rem", backgroundColor: "#f9fafb", borderRadius: "0.375rem", display: "flex", flexWrap: "wrap", gap: "0.75rem" }, children: [
494
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
495
+ /* @__PURE__ */ jsxs("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: [
496
+ "Tenant ID ",
497
+ lockedFilters.tenantId && /* @__PURE__ */ jsx("span", { style: { color: "#9ca3af" }, children: "(Locked)" })
498
+ ] }),
499
+ /* @__PURE__ */ jsx(
500
+ "input",
501
+ {
502
+ type: "text",
503
+ placeholder: "All tenants",
504
+ value: lockedFilters.tenantId ? tenantId ?? "" : uiFilters.tenantId ?? "",
505
+ onChange: (e) => {
506
+ if (!lockedFilters.tenantId) {
507
+ setUiFilters((prev) => ({ ...prev, tenantId: e.target.value || void 0 }));
508
+ }
509
+ },
510
+ disabled: lockedFilters.tenantId,
511
+ style: {
512
+ padding: "0.375rem 0.5rem",
513
+ border: "1px solid #d1d5db",
514
+ borderRadius: "0.375rem",
515
+ fontSize: "0.875rem",
516
+ backgroundColor: lockedFilters.tenantId ? "#f3f4f6" : "white",
517
+ cursor: lockedFilters.tenantId ? "not-allowed" : "text"
518
+ }
519
+ }
520
+ )
521
+ ] }),
522
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
523
+ /* @__PURE__ */ jsxs("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: [
524
+ "Action Key ",
525
+ lockedFilters.actionKey && /* @__PURE__ */ jsx("span", { style: { color: "#9ca3af" }, children: "(Locked)" })
526
+ ] }),
527
+ /* @__PURE__ */ jsx(
528
+ "input",
529
+ {
530
+ type: "text",
531
+ placeholder: "All actions",
532
+ value: lockedFilters.actionKey ? actionKey ?? "" : uiFilters.actionKey ?? "",
533
+ onChange: (e) => {
534
+ if (!lockedFilters.actionKey) {
535
+ setUiFilters((prev) => ({ ...prev, actionKey: e.target.value || void 0 }));
536
+ }
537
+ },
538
+ disabled: lockedFilters.actionKey,
539
+ style: {
540
+ padding: "0.375rem 0.5rem",
541
+ border: "1px solid #d1d5db",
542
+ borderRadius: "0.375rem",
543
+ fontSize: "0.875rem",
544
+ backgroundColor: lockedFilters.actionKey ? "#f3f4f6" : "white",
545
+ cursor: lockedFilters.actionKey ? "not-allowed" : "text"
546
+ }
547
+ }
548
+ )
549
+ ] }),
550
+ !lockedFilters.entityId && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
551
+ /* @__PURE__ */ jsx("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: "Entity ID" }),
552
+ /* @__PURE__ */ jsx(
553
+ "input",
554
+ {
555
+ type: "text",
556
+ placeholder: "All entities",
557
+ value: uiFilters.entityId ?? "",
558
+ onChange: (e) => {
559
+ setUiFilters((prev) => ({ ...prev, entityId: e.target.value || void 0 }));
560
+ },
561
+ style: {
562
+ padding: "0.375rem 0.5rem",
563
+ border: "1px solid #d1d5db",
564
+ borderRadius: "0.375rem",
565
+ fontSize: "0.875rem"
566
+ }
567
+ }
568
+ )
569
+ ] }),
570
+ !lockedFilters.entityType && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
571
+ /* @__PURE__ */ jsx("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: "Entity Type" }),
572
+ /* @__PURE__ */ jsx(
573
+ "input",
574
+ {
575
+ type: "text",
576
+ placeholder: "All types",
577
+ value: uiFilters.entityType ?? "",
578
+ onChange: (e) => {
579
+ setUiFilters((prev) => ({ ...prev, entityType: e.target.value || void 0 }));
580
+ },
581
+ style: {
582
+ padding: "0.375rem 0.5rem",
583
+ border: "1px solid #d1d5db",
584
+ borderRadius: "0.375rem",
585
+ fontSize: "0.875rem"
586
+ }
587
+ }
588
+ )
589
+ ] }),
590
+ !lockedFilters.actorId && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
591
+ /* @__PURE__ */ jsx("label", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#6b7280" }, children: "Actor ID" }),
592
+ /* @__PURE__ */ jsx(
593
+ "input",
594
+ {
595
+ type: "text",
596
+ placeholder: "All actors",
597
+ value: uiFilters.actorId ?? "",
598
+ onChange: (e) => {
599
+ setUiFilters((prev) => ({ ...prev, actorId: e.target.value || void 0 }));
600
+ },
601
+ style: {
602
+ padding: "0.375rem 0.5rem",
603
+ border: "1px solid #d1d5db",
604
+ borderRadius: "0.375rem",
605
+ fontSize: "0.875rem"
606
+ }
607
+ }
608
+ )
609
+ ] }),
610
+ (Object.keys(uiFilters).length > 0 || searchQuery) && /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "flex-end" }, children: /* @__PURE__ */ jsx(
611
+ "button",
612
+ {
613
+ type: "button",
614
+ onClick: () => {
615
+ setUiFilters({});
616
+ setSearchQuery("");
617
+ },
618
+ style: {
619
+ padding: "0.375rem 0.75rem",
620
+ backgroundColor: "#ef4444",
621
+ color: "white",
622
+ border: "none",
623
+ borderRadius: "0.375rem",
624
+ fontSize: "0.875rem",
625
+ cursor: "pointer"
626
+ },
627
+ children: "Clear"
628
+ }
629
+ ) })
630
+ ] }),
431
631
  (searchQuery || filteredEvents.length !== eventsToFilter.length) && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", color: "#6b7280" }, children: [
432
632
  "Showing ",
433
633
  filteredEvents.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archiva/archiva-nextjs",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Archiva Next.js SDK - Server Actions and Timeline Component",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",