@acmekit/dashboard 2.13.35 → 2.13.36

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 (39) hide show
  1. package/package.json +9 -9
  2. package/src/components/layout/main-layout/main-layout.tsx +1 -28
  3. package/src/dashboard-app/routes/get-route.map.tsx +0 -71
  4. package/src/hooks/api/workflow-executions.tsx +1 -145
  5. package/src/i18n/translations/$schema.json +4 -534
  6. package/src/i18n/translations/en.json +3 -132
  7. package/src/routes/workflow-executions/constants.ts +0 -16
  8. package/src/routes/workflow-executions/utils.ts +14 -170
  9. package/src/routes/workflow-executions/workflow-execution-detail/breadcrumb.tsx +1 -7
  10. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-history-section/workflow-execution-history-section.tsx +6 -157
  11. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-payload-section/workflow-execution-payload-section.tsx +6 -122
  12. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-timeline-section/workflow-execution-timeline-section.tsx +1 -7
  13. package/src/routes/workflow-executions/workflow-execution-detail/workflow-detail.tsx +1 -46
  14. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-columns.tsx +0 -7
  15. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-filters.tsx +1 -7
  16. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-query.tsx +2 -4
  17. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-list-table.tsx +1 -17
  18. package/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx +1 -1
  19. package/src/hooks/api/workflow-definitions.tsx +0 -79
  20. package/src/hooks/api/workflow-metrics.tsx +0 -48
  21. package/src/hooks/use-workflow-sse.tsx +0 -78
  22. package/src/routes/workflow-analytics/workflow-analytics.tsx +0 -167
  23. package/src/routes/workflow-definitions/workflow-definition-detail/workflow-definition-detail.tsx +0 -98
  24. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/use-workflow-definition-table-columns.tsx +0 -78
  25. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/workflow-definition-list-table.tsx +0 -65
  26. package/src/routes/workflow-definitions/workflow-definition-list/workflow-definition-list.tsx +0 -15
  27. package/src/routes/workflow-executions/workflow-execution-complete-step/workflow-execution-complete-step.tsx +0 -270
  28. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/index.ts +0 -1
  29. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/workflow-execution-action-bar.tsx +0 -212
  30. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/index.ts +0 -1
  31. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/workflow-execution-error-card.tsx +0 -59
  32. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/index.ts +0 -1
  33. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/workflow-execution-waiting-banner.tsx +0 -63
  34. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-auto-refresh.tsx +0 -73
  35. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-row-actions.tsx +0 -116
  36. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-saved-views.tsx +0 -84
  37. package/src/routes/workflow-executions/workflow-execution-rerun/workflow-execution-rerun.tsx +0 -159
  38. package/src/routes/workflow-executions/workflow-execution-run/workflow-execution-run.tsx +0 -139
  39. package/src/routes/workflow-scheduled/workflow-scheduled-list.tsx +0 -269
@@ -1,212 +0,0 @@
1
- import { ArrowPathMini, EllipsisHorizontal, XMark } from "@acmekit/icons"
2
- import { HttpTypes } from "@acmekit/types"
3
- import {
4
- Badge,
5
- Button,
6
- Container,
7
- Copy,
8
- StatusBadge,
9
- toast,
10
- usePrompt,
11
- } from "@acmekit/ui"
12
- import { useTranslation } from "react-i18next"
13
- import { Link } from "react-router-dom"
14
- import { ActionMenu } from "../../../../../components/common/action-menu"
15
- import {
16
- useCancelWorkflowExecution,
17
- useRunWorkflow,
18
- } from "../../../../../hooks/api/workflow-executions"
19
- import {
20
- TRANSACTION_ERROR_STATES,
21
- TRANSACTION_IN_PROGRESS_STATES,
22
- } from "../../../constants"
23
- import { TransactionState } from "../../../types"
24
- import {
25
- getTransactionState,
26
- getTransactionStateColor,
27
- getWaitingSteps,
28
- } from "../../../utils"
29
-
30
- type WorkflowExecutionActionBarProps = {
31
- execution: HttpTypes.AdminWorkflowExecution
32
- }
33
-
34
- export const WorkflowExecutionActionBar = ({
35
- execution,
36
- }: WorkflowExecutionActionBarProps) => {
37
- const { t } = useTranslation()
38
- const prompt = usePrompt()
39
-
40
- const state = execution.state as TransactionState
41
- const stateColor = getTransactionStateColor(state)
42
- const translatedState = getTransactionState(t, state)
43
-
44
- const workflowId = execution.workflow_id
45
- const transactionId = execution.transaction_id
46
-
47
- const isFailed = TRANSACTION_ERROR_STATES.includes(state)
48
- const isRunning = TRANSACTION_IN_PROGRESS_STATES.includes(state)
49
- const waitingSteps = getWaitingSteps(execution)
50
- const hasWaitingSteps = waitingSteps.length > 0
51
-
52
- const { mutateAsync: runAsync, isPending: isRunning_ } =
53
- useRunWorkflow(workflowId)
54
- const { mutateAsync: cancelAsync, isPending: isCancelling } =
55
- useCancelWorkflowExecution(workflowId, transactionId)
56
-
57
- const handleRetry = async () => {
58
- const payload = (execution as any)?.context?.data?.payload
59
- const input =
60
- payload && typeof payload === "object"
61
- ? (payload as Record<string, unknown>)
62
- : {}
63
- await runAsync(
64
- { input },
65
- {
66
- onSuccess: () => {
67
- toast.success(t("workflowExecutions.actions.retrySuccess"))
68
- },
69
- onError: (err) => {
70
- toast.error(err.message)
71
- },
72
- }
73
- )
74
- }
75
-
76
- const handleCancel = async () => {
77
- const res = await prompt({
78
- title: t("workflowExecutions.actions.cancelConfirmTitle"),
79
- description: t("workflowExecutions.actions.cancelConfirmDescription", {
80
- workflow_id: workflowId,
81
- }),
82
- confirmText: t("workflowExecutions.actions.cancel"),
83
- cancelText: t("actions.cancel"),
84
- })
85
-
86
- if (!res) return
87
-
88
- await cancelAsync(undefined, {
89
- onSuccess: () => {
90
- toast.success(t("workflowExecutions.actions.cancelSuccess"))
91
- },
92
- onError: (err) => {
93
- toast.error(err.message)
94
- },
95
- })
96
- }
97
-
98
- const handleExportJson = () => {
99
- const data = JSON.stringify(execution, null, 2)
100
- const blob = new Blob([data], { type: "application/json" })
101
- const url = URL.createObjectURL(blob)
102
- const a = document.createElement("a")
103
- a.href = url
104
- a.download = `workflow-${workflowId}-${execution.id}.json`
105
- a.click()
106
- URL.revokeObjectURL(url)
107
- }
108
-
109
- const handleExportCsv = () => {
110
- const steps = execution?.execution?.steps || {}
111
- const rows = [["Step", "State", "Duration"]]
112
- for (const [stepId, step] of Object.entries(steps)) {
113
- if (stepId === "_root") continue
114
- const invokeState = (step as any)?.invoke?.state || "unknown"
115
- rows.push([stepId, invokeState, ""])
116
- }
117
- const csv = rows.map((r) => r.join(",")).join("\n")
118
- const blob = new Blob([csv], { type: "text/csv" })
119
- const url = URL.createObjectURL(blob)
120
- const a = document.createElement("a")
121
- a.href = url
122
- a.download = `workflow-${workflowId}-${execution.id}.csv`
123
- a.click()
124
- URL.revokeObjectURL(url)
125
- }
126
-
127
- const cleanId = execution.id.replace("wf_exec_", "")
128
-
129
- return (
130
- <Container className="sticky top-14 z-10 p-0">
131
- <div className="flex items-center justify-between px-6 py-3">
132
- <div className="flex items-center gap-x-3">
133
- <Copy content={workflowId} asChild>
134
- <Badge size="2xsmall" className="cursor-pointer">
135
- {workflowId}
136
- </Badge>
137
- </Copy>
138
- <StatusBadge color={stateColor}>{translatedState}</StatusBadge>
139
- <Copy content={execution.id} asChild>
140
- <Badge
141
- size="2xsmall"
142
- color="grey"
143
- className="cursor-pointer text-ui-fg-muted"
144
- >
145
- {cleanId}
146
- </Badge>
147
- </Copy>
148
- </div>
149
- <div className="flex items-center gap-x-2">
150
- {isFailed && (
151
- <Button
152
- variant="secondary"
153
- size="small"
154
- onClick={handleRetry}
155
- isLoading={isRunning_}
156
- >
157
- <ArrowPathMini className="mr-1" />
158
- {t("workflowExecutions.actions.retry")}
159
- </Button>
160
- )}
161
- {isRunning && (
162
- <Button
163
- variant="danger"
164
- size="small"
165
- onClick={handleCancel}
166
- isLoading={isCancelling}
167
- >
168
- <XMark className="mr-1" />
169
- {t("workflowExecutions.actions.cancel")}
170
- </Button>
171
- )}
172
- {hasWaitingSteps && (
173
- <Button variant="secondary" size="small" asChild>
174
- <Link to="complete-step">
175
- {t("workflowExecutions.actions.completeStep")}
176
- </Link>
177
- </Button>
178
- )}
179
- <ActionMenu
180
- groups={[
181
- {
182
- actions: [
183
- ...(isFailed
184
- ? [
185
- {
186
- label: t(
187
- "workflowExecutions.actions.retryWithNewInput"
188
- ),
189
- icon: <ArrowPathMini />,
190
- to: "rerun",
191
- },
192
- ]
193
- : []),
194
- {
195
- label: t("workflowExecutions.actions.exportJson"),
196
- icon: <EllipsisHorizontal />,
197
- onClick: handleExportJson,
198
- },
199
- {
200
- label: t("workflowExecutions.actions.exportCsv"),
201
- icon: <EllipsisHorizontal />,
202
- onClick: handleExportCsv,
203
- },
204
- ],
205
- },
206
- ]}
207
- />
208
- </div>
209
- </div>
210
- </Container>
211
- )
212
- }
@@ -1 +0,0 @@
1
- export { WorkflowExecutionErrorCard } from "./workflow-execution-error-card"
@@ -1,59 +0,0 @@
1
- import { XCircle } from "@acmekit/icons"
2
- import { HttpTypes } from "@acmekit/types"
3
- import { Container, Heading, Text } from "@acmekit/ui"
4
- import { useTranslation } from "react-i18next"
5
- import { TransactionState } from "../../../types"
6
- import { getFailedSteps } from "../../../utils"
7
-
8
- type WorkflowExecutionErrorCardProps = {
9
- execution: HttpTypes.AdminWorkflowExecution
10
- }
11
-
12
- export const WorkflowExecutionErrorCard = ({
13
- execution,
14
- }: WorkflowExecutionErrorCardProps) => {
15
- const { t } = useTranslation()
16
-
17
- if ((execution.state as TransactionState) !== TransactionState.FAILED) {
18
- return null
19
- }
20
-
21
- const failedSteps = getFailedSteps(execution)
22
- if (failedSteps.length === 0) return null
23
-
24
- const firstFailure = failedSteps[0]
25
- const errorMessage =
26
- firstFailure.error?.error?.message ||
27
- firstFailure.error?.error ||
28
- "Unknown error"
29
- const handlerType = firstFailure.error?.handlerType || "invoke"
30
-
31
- return (
32
- <Container className="border border-ui-tag-red-border bg-ui-tag-red-bg p-0">
33
- <div className="flex items-start gap-x-3 px-6 py-4">
34
- <div className="mt-0.5">
35
- <XCircle className="text-ui-tag-red-icon" />
36
- </div>
37
- <div className="flex flex-1 flex-col gap-y-1">
38
- <Heading level="h3">
39
- {t("workflowExecutions.error.failureAtStep", {
40
- step: firstFailure.stepId,
41
- })}
42
- </Heading>
43
- <Text size="small" className="text-ui-fg-subtle">
44
- {String(errorMessage)}
45
- </Text>
46
- <Text size="xsmall" className="text-ui-fg-muted">
47
- {handlerType === "compensate" ? "Compensation" : "Invoke"} failure
48
- </Text>
49
- </div>
50
- <a
51
- href={`#${firstFailure.stepId}`}
52
- className="text-ui-fg-interactive txt-compact-small-plus shrink-0 hover:underline"
53
- >
54
- {t("workflowExecutions.error.jumpToStep")}
55
- </a>
56
- </div>
57
- </Container>
58
- )
59
- }
@@ -1 +0,0 @@
1
- export { WorkflowExecutionWaitingBanner } from "./workflow-execution-waiting-banner"
@@ -1,63 +0,0 @@
1
- import { ClockSolidMini } from "@acmekit/icons"
2
- import { HttpTypes } from "@acmekit/types"
3
- import { Container, Copy, Heading, Text } from "@acmekit/ui"
4
- import { useTranslation } from "react-i18next"
5
- import { Link } from "react-router-dom"
6
- import { computeIdempotencyKey, getWaitingSteps } from "../../../utils"
7
-
8
- type WorkflowExecutionWaitingBannerProps = {
9
- execution: HttpTypes.AdminWorkflowExecution
10
- }
11
-
12
- export const WorkflowExecutionWaitingBanner = ({
13
- execution,
14
- }: WorkflowExecutionWaitingBannerProps) => {
15
- const { t } = useTranslation()
16
- const waitingSteps = getWaitingSteps(execution)
17
-
18
- if (waitingSteps.length === 0) return null
19
-
20
- const firstStep = waitingSteps[0]
21
- const idempotencyKey = computeIdempotencyKey(
22
- execution.workflow_id,
23
- execution.transaction_id,
24
- firstStep.stepId,
25
- "invoke"
26
- )
27
-
28
- return (
29
- <Container className="border border-ui-tag-purple-border bg-ui-tag-purple-bg p-0">
30
- <div className="flex items-start gap-x-3 px-6 py-4">
31
- <div className="mt-0.5">
32
- <ClockSolidMini className="text-ui-tag-purple-icon" />
33
- </div>
34
- <div className="flex flex-1 flex-col gap-y-1">
35
- <Heading level="h3">
36
- {t("workflowExecutions.asyncStep.waitingBanner")}
37
- </Heading>
38
- <Text size="small" className="text-ui-fg-subtle">
39
- {t("workflowExecutions.asyncStep.waitingDescription", {
40
- step: firstStep.stepId,
41
- })}
42
- </Text>
43
- <div className="mt-1 flex items-center gap-x-2">
44
- <Text size="xsmall" className="text-ui-fg-muted">
45
- {t("workflowExecutions.asyncStep.idempotencyKey")}:
46
- </Text>
47
- <Copy content={idempotencyKey} asChild>
48
- <code className="bg-ui-bg-base txt-compact-xsmall cursor-pointer rounded px-1.5 py-0.5">
49
- {idempotencyKey}
50
- </code>
51
- </Copy>
52
- </div>
53
- </div>
54
- <Link
55
- to="complete-step"
56
- className="text-ui-fg-interactive txt-compact-small-plus shrink-0 hover:underline"
57
- >
58
- {t("workflowExecutions.actions.completeStep")}
59
- </Link>
60
- </div>
61
- </Container>
62
- )
63
- }
@@ -1,73 +0,0 @@
1
- import { ArrowPathMini } from "@acmekit/icons"
2
- import { DropdownMenu, IconButton } from "@acmekit/ui"
3
- import { useQueryClient } from "@tanstack/react-query"
4
- import { useCallback, useEffect, useRef, useState } from "react"
5
- import { workflowExecutionsQueryKeys } from "../../../../../hooks/api/workflow-executions"
6
-
7
- const INTERVALS = [
8
- { label: "Off", value: 0 },
9
- { label: "5s", value: 5000 },
10
- { label: "10s", value: 10000 },
11
- { label: "30s", value: 30000 },
12
- ]
13
-
14
- export const WorkflowExecutionAutoRefresh = () => {
15
- const queryClient = useQueryClient()
16
- const [interval, setInterval_] = useState(0)
17
- const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
18
-
19
- const refresh = useCallback(() => {
20
- queryClient.invalidateQueries({
21
- queryKey: workflowExecutionsQueryKeys.lists(),
22
- })
23
- }, [queryClient])
24
-
25
- useEffect(() => {
26
- if (intervalRef.current) {
27
- clearInterval(intervalRef.current)
28
- intervalRef.current = null
29
- }
30
-
31
- if (interval > 0) {
32
- intervalRef.current = setInterval(refresh, interval)
33
- }
34
-
35
- return () => {
36
- if (intervalRef.current) {
37
- clearInterval(intervalRef.current)
38
- }
39
- }
40
- }, [interval, refresh])
41
-
42
- const activeLabel = INTERVALS.find((i) => i.value === interval)?.label
43
-
44
- return (
45
- <div className="flex items-center gap-x-1">
46
- <IconButton
47
- size="small"
48
- variant="transparent"
49
- onClick={refresh}
50
- >
51
- <ArrowPathMini />
52
- </IconButton>
53
- <DropdownMenu>
54
- <DropdownMenu.Trigger asChild>
55
- <button className="txt-compact-small text-ui-fg-muted hover:text-ui-fg-subtle rounded px-1.5 py-0.5 transition-colors">
56
- {activeLabel}
57
- </button>
58
- </DropdownMenu.Trigger>
59
- <DropdownMenu.Content align="end">
60
- {INTERVALS.map((opt) => (
61
- <DropdownMenu.Item
62
- key={opt.value}
63
- onClick={() => setInterval_(opt.value)}
64
- className={interval === opt.value ? "font-medium" : ""}
65
- >
66
- {opt.label}
67
- </DropdownMenu.Item>
68
- ))}
69
- </DropdownMenu.Content>
70
- </DropdownMenu>
71
- </div>
72
- )
73
- }
@@ -1,116 +0,0 @@
1
- import { ArrowPathMini, Eye, XMark } from "@acmekit/icons"
2
- import { HttpTypes } from "@acmekit/types"
3
- import { toast, usePrompt } from "@acmekit/ui"
4
- import { useTranslation } from "react-i18next"
5
- import {
6
- Action,
7
- ActionMenu,
8
- } from "../../../../../components/common/action-menu"
9
- import {
10
- useCancelWorkflowExecution,
11
- useRunWorkflow,
12
- } from "../../../../../hooks/api/workflow-executions"
13
- import {
14
- TRANSACTION_ERROR_STATES,
15
- TRANSACTION_IN_PROGRESS_STATES,
16
- } from "../../../constants"
17
- import { TransactionState } from "../../../types"
18
-
19
- type WorkflowExecutionRowActionsProps = {
20
- execution: HttpTypes.AdminWorkflowExecutionResponse["workflow_execution"]
21
- }
22
-
23
- export const WorkflowExecutionRowActions = ({
24
- execution,
25
- }: WorkflowExecutionRowActionsProps) => {
26
- const { t } = useTranslation()
27
- const prompt = usePrompt()
28
-
29
- const state = execution.state as TransactionState
30
- const isFailed = TRANSACTION_ERROR_STATES.includes(state)
31
- const isRunning = TRANSACTION_IN_PROGRESS_STATES.includes(state)
32
-
33
- const workflowId = execution.workflow_id
34
- const transactionId = execution.transaction_id
35
-
36
- const { mutateAsync: runAsync } = useRunWorkflow(workflowId)
37
- const { mutateAsync: cancelAsync } = useCancelWorkflowExecution(
38
- workflowId,
39
- transactionId
40
- )
41
-
42
- const handleRetry = async () => {
43
- const payload = (execution as any)?.context?.data?.payload
44
- const input =
45
- payload && typeof payload === "object"
46
- ? (payload as Record<string, unknown>)
47
- : {}
48
- await runAsync(
49
- { input },
50
- {
51
- onSuccess: () => {
52
- toast.success(t("workflowExecutions.actions.retrySuccess"))
53
- },
54
- onError: (err) => {
55
- toast.error(err.message)
56
- },
57
- }
58
- )
59
- }
60
-
61
- const handleCancel = async () => {
62
- const res = await prompt({
63
- title: t("workflowExecutions.actions.cancelConfirmTitle"),
64
- description: t(
65
- "workflowExecutions.actions.cancelConfirmDescription",
66
- { workflow_id: workflowId }
67
- ),
68
- confirmText: t("workflowExecutions.actions.cancel"),
69
- cancelText: t("actions.cancel"),
70
- })
71
-
72
- if (!res) return
73
-
74
- await cancelAsync(undefined, {
75
- onSuccess: () => {
76
- toast.success(t("workflowExecutions.actions.cancelSuccess"))
77
- },
78
- onError: (err) => {
79
- toast.error(err.message)
80
- },
81
- })
82
- }
83
-
84
- const secondaryActions: Action[] = []
85
- if (isFailed) {
86
- secondaryActions.push({
87
- icon: <ArrowPathMini />,
88
- label: t("workflowExecutions.actions.retry"),
89
- onClick: handleRetry,
90
- })
91
- }
92
- if (isRunning) {
93
- secondaryActions.push({
94
- icon: <XMark />,
95
- label: t("workflowExecutions.actions.cancel"),
96
- onClick: handleCancel,
97
- })
98
- }
99
-
100
- const groups = [
101
- {
102
- actions: [
103
- {
104
- icon: <Eye />,
105
- label: t("workflowExecutions.actions.viewDetails"),
106
- to: `${execution.id}`,
107
- } as Action,
108
- ],
109
- },
110
- ...(secondaryActions.length > 0
111
- ? [{ actions: secondaryActions }]
112
- : []),
113
- ]
114
-
115
- return <ActionMenu groups={groups} />
116
- }
@@ -1,84 +0,0 @@
1
- import { Button, clx } from "@acmekit/ui"
2
- import { useCallback, useMemo } from "react"
3
- import { useTranslation } from "react-i18next"
4
- import { useSearchParams } from "react-router-dom"
5
-
6
- type SavedView = {
7
- label: string
8
- params: Record<string, string>
9
- }
10
-
11
- export const WorkflowExecutionSavedViews = () => {
12
- const { t } = useTranslation()
13
- const [searchParams, setSearchParams] = useSearchParams()
14
-
15
- const views: SavedView[] = useMemo(
16
- () => [
17
- {
18
- label: t("workflowExecutions.filters.allExecutions"),
19
- params: {} as Record<string, string>,
20
- },
21
- {
22
- label: t("workflowExecutions.filters.running"),
23
- params: { state: "invoking" } as Record<string, string>,
24
- },
25
- {
26
- label: t("workflowExecutions.filters.failedToday"),
27
- params: {
28
- state: "failed",
29
- created_at: JSON.stringify({
30
- $gte: new Date(
31
- new Date().setHours(0, 0, 0, 0)
32
- ).toISOString(),
33
- }),
34
- } as Record<string, string>,
35
- },
36
- ],
37
- [t]
38
- )
39
-
40
- const isActive = useCallback(
41
- (view: SavedView) => {
42
- const viewKeys = Object.keys(view.params)
43
- if (viewKeys.length === 0) {
44
- return !searchParams.has("state") && !searchParams.has("created_at")
45
- }
46
- return viewKeys.every(
47
- (key) => searchParams.get(key) === view.params[key]
48
- )
49
- },
50
- [searchParams]
51
- )
52
-
53
- const handleClick = useCallback(
54
- (view: SavedView) => {
55
- const newParams = new URLSearchParams()
56
- for (const [key, value] of Object.entries(view.params)) {
57
- newParams.set(key, value)
58
- }
59
- setSearchParams(newParams)
60
- },
61
- [setSearchParams]
62
- )
63
-
64
- return (
65
- <div className="flex items-center gap-x-1">
66
- {views.map((view) => (
67
- <Button
68
- key={view.label}
69
- variant="transparent"
70
- size="small"
71
- onClick={() => handleClick(view)}
72
- className={clx(
73
- "txt-compact-small-plus",
74
- isActive(view)
75
- ? "bg-ui-bg-base shadow-elevation-card-rest text-ui-fg-base"
76
- : "text-ui-fg-muted"
77
- )}
78
- >
79
- {view.label}
80
- </Button>
81
- ))}
82
- </div>
83
- )
84
- }