@vendure/dashboard 3.2.4 → 3.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.
- package/dist/plugin/utils/ast-utils.spec.js +2 -2
- package/dist/plugin/utils/schema-generator.js +1 -1
- package/dist/plugin/utils/ui-config.js +2 -3
- package/dist/plugin/vite-plugin-config.js +4 -6
- package/package.json +13 -9
- package/src/app/app-providers.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +0 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +8 -2
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -0
- package/src/app/routes/_authenticated/_system/job-queue.tsx +7 -8
- package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +241 -0
- package/src/app/styles.css +15 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +1 -1
- package/src/lib/components/data-table/data-table.tsx +32 -26
- package/src/lib/components/data-table/refresh-button.tsx +25 -0
- package/src/lib/components/layout/nav-user.tsx +16 -11
- package/src/lib/components/layout/prerelease-popup.tsx +1 -5
- package/src/lib/components/shared/alerts.tsx +19 -1
- package/src/lib/components/shared/error-page.tsx +2 -2
- package/src/lib/components/shared/navigation-confirmation.tsx +20 -10
- package/src/lib/components/shared/paginated-list-data-table.tsx +1 -0
- package/src/lib/framework/alert/alert-extensions.tsx +31 -0
- package/src/lib/framework/alert/alert-item.tsx +47 -0
- package/src/lib/framework/alert/alerts-indicator.tsx +23 -0
- package/src/lib/framework/alert/types.ts +13 -0
- package/src/lib/framework/dashboard-widget/base-widget.tsx +1 -0
- package/src/lib/framework/defaults.ts +34 -0
- package/src/lib/framework/document-introspection/get-document-structure.ts +1 -2
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +15 -5
- package/src/lib/framework/extension-api/extension-api-types.ts +81 -12
- package/src/lib/framework/layout-engine/layout-extensions.ts +3 -3
- package/src/lib/framework/layout-engine/page-layout.tsx +191 -35
- package/src/lib/framework/layout-engine/page-provider.tsx +10 -0
- package/src/lib/framework/page/detail-page.tsx +62 -9
- package/src/lib/framework/page/list-page.tsx +19 -0
- package/src/lib/framework/page/page-api.ts +1 -1
- package/src/lib/framework/page/use-detail-page.ts +81 -0
- package/src/lib/framework/registry/registry-types.ts +6 -2
- package/src/lib/graphql/graphql-env.d.ts +25 -9
- package/src/lib/hooks/use-auth.tsx +13 -1
- package/src/lib/hooks/use-channel.ts +13 -0
- package/src/lib/hooks/use-local-format.ts +28 -1
- package/src/lib/hooks/use-page.tsx +2 -3
- package/src/lib/hooks/use-permissions.ts +13 -0
- package/src/lib/index.ts +3 -4
- package/src/lib/providers/auth.tsx +11 -1
- package/src/lib/providers/channel-provider.tsx +8 -1
- package/vite/utils/ast-utils.spec.ts +2 -2
- package/vite/utils/schema-generator.ts +4 -5
- package/vite/utils/ui-config.ts +7 -3
- package/vite/vite-plugin-admin-api-schema.ts +0 -10
- package/vite/vite-plugin-config.ts +1 -0
- package/src/lib/components/ui/avatar.tsx +0 -38
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import { describe,
|
|
3
|
-
import {
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { findConfigExport, getPluginInfo } from './ast-utils.js';
|
|
4
4
|
describe('getPluginInfo', () => {
|
|
5
5
|
it('should return undefined when no plugin class is found', () => {
|
|
6
6
|
const sourceText = `
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GraphQLTypesLoader } from '@nestjs/graphql';
|
|
2
|
-
import {
|
|
2
|
+
import { getConfig, getFinalVendureSchema, resetConfig, runPluginConfigurations, setConfig, VENDURE_ADMIN_API_TYPE_PATHS, } from '@vendure/core';
|
|
3
3
|
import { buildSchema } from 'graphql';
|
|
4
4
|
let schemaPromise;
|
|
5
5
|
/**
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_AUTH_TOKEN_HEADER_KEY, DEFAULT_CHANNEL_TOKEN_KEY,
|
|
2
|
-
import { defaultAvailableLocales } from '../constants.js';
|
|
3
|
-
import { defaultLocale, defaultLanguage, defaultAvailableLanguages } from '../constants.js';
|
|
1
|
+
import { ADMIN_API_PATH, DEFAULT_AUTH_TOKEN_HEADER_KEY, DEFAULT_CHANNEL_TOKEN_KEY, } from '@vendure/common/lib/shared-constants';
|
|
2
|
+
import { defaultAvailableLanguages, defaultAvailableLocales, defaultLanguage, defaultLocale, } from '../constants.js';
|
|
4
3
|
export function getAdminUiConfig(config, adminUiConfig) {
|
|
5
4
|
const { authOptions, apiOptions } = config;
|
|
6
5
|
const propOrDefault = (prop, defaultVal, isArray = false) => {
|
|
@@ -3,17 +3,15 @@ export function viteConfigPlugin({ packageRoot }) {
|
|
|
3
3
|
return {
|
|
4
4
|
name: 'vendure:vite-config-plugin',
|
|
5
5
|
config: (config) => {
|
|
6
|
-
var _a, _b;
|
|
6
|
+
var _a, _b, _c, _d;
|
|
7
7
|
config.root = packageRoot;
|
|
8
8
|
config.resolve = {
|
|
9
|
-
alias: {
|
|
10
|
-
'@': path.resolve(packageRoot, './src/lib'),
|
|
11
|
-
},
|
|
9
|
+
alias: Object.assign(Object.assign({}, ((_b = (_a = config.resolve) === null || _a === void 0 ? void 0 : _a.alias) !== null && _b !== void 0 ? _b : {})), { '@': path.resolve(packageRoot, './src/lib') }),
|
|
12
10
|
};
|
|
13
11
|
// This is required to prevent Vite from pre-bundling the
|
|
14
12
|
// dashboard source when it resides in node_modules.
|
|
15
13
|
config.optimizeDeps = Object.assign(Object.assign({}, config.optimizeDeps), { exclude: [
|
|
16
|
-
...(((
|
|
14
|
+
...(((_c = config.optimizeDeps) === null || _c === void 0 ? void 0 : _c.exclude) || []),
|
|
17
15
|
'@vendure/dashboard',
|
|
18
16
|
'@/providers',
|
|
19
17
|
'@/framework',
|
|
@@ -28,7 +26,7 @@ export function viteConfigPlugin({ packageRoot }) {
|
|
|
28
26
|
// on lodash which is a CJS packages and _does_ require
|
|
29
27
|
// pre-bundling.
|
|
30
28
|
include: [
|
|
31
|
-
...(((
|
|
29
|
+
...(((_d = config.optimizeDeps) === null || _d === void 0 ? void 0 : _d.include) || []),
|
|
32
30
|
'@/components > recharts',
|
|
33
31
|
'@/components > react-dropzone',
|
|
34
32
|
] });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.3.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@radix-ui/react-switch": "^1.1.3",
|
|
72
72
|
"@radix-ui/react-tabs": "^1.1.3",
|
|
73
73
|
"@radix-ui/react-tooltip": "^1.1.8",
|
|
74
|
-
"@tailwindcss/vite": "^4.
|
|
74
|
+
"@tailwindcss/vite": "^4.1.5",
|
|
75
75
|
"@tanstack/eslint-plugin-query": "^5.66.1",
|
|
76
76
|
"@tanstack/react-query": "^5.66.7",
|
|
77
77
|
"@tanstack/react-query-devtools": "^5.68.0",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"@types/react-dom": "^19.0.4",
|
|
87
87
|
"@types/react-grid-layout": "^1.3.5",
|
|
88
88
|
"@uidotdev/usehooks": "^2.4.1",
|
|
89
|
-
"@vendure/common": "3.
|
|
90
|
-
"@vendure/core": "3.
|
|
89
|
+
"@vendure/common": "3.3.1",
|
|
90
|
+
"@vendure/core": "3.3.1",
|
|
91
91
|
"@vitejs/plugin-react": "^4.3.4",
|
|
92
92
|
"awesome-graphql-client": "^2.1.0",
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
@@ -108,12 +108,12 @@
|
|
|
108
108
|
"react-hook-form": "^7.54.2",
|
|
109
109
|
"recharts": "^2.15.1",
|
|
110
110
|
"sonner": "^2.0.1",
|
|
111
|
-
"tailwind-merge": "^3.0
|
|
112
|
-
"tailwindcss": "^4.
|
|
111
|
+
"tailwind-merge": "^3.2.0",
|
|
112
|
+
"tailwindcss": "^4.1.5",
|
|
113
113
|
"tailwindcss-animate": "^1.0.7",
|
|
114
114
|
"tsconfig-paths": "^4.2.0",
|
|
115
|
-
"tw-animate-css": "^1.2.
|
|
116
|
-
"vite": "^6.
|
|
115
|
+
"tw-animate-css": "^1.2.9",
|
|
116
|
+
"vite": "^6.3.5",
|
|
117
117
|
"zod": "^3.24.2"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
@@ -126,5 +126,9 @@
|
|
|
126
126
|
"globals": "^15.14.0",
|
|
127
127
|
"vite-plugin-dts": "^4.5.3"
|
|
128
128
|
},
|
|
129
|
-
"
|
|
129
|
+
"optionalDependencies": {
|
|
130
|
+
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
131
|
+
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
132
|
+
},
|
|
133
|
+
"gitHead": "32fbe4af70da48fef8b915e6e00abca74f092513"
|
|
130
134
|
}
|
|
@@ -23,7 +23,7 @@ export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
|
23
23
|
</AuthProvider>
|
|
24
24
|
</ThemeProvider>
|
|
25
25
|
</UserSettingsProvider>
|
|
26
|
-
|
|
26
|
+
{/*<ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-left" />*/}
|
|
27
27
|
</QueryClientProvider>
|
|
28
28
|
</I18nProvider>
|
|
29
29
|
);
|
|
@@ -200,7 +200,9 @@ function ProductVariantDetailPage() {
|
|
|
200
200
|
<Trans>Stock level</Trans>
|
|
201
201
|
</FormLabel>
|
|
202
202
|
<FormControl>
|
|
203
|
-
<Input type="number" {
|
|
203
|
+
<Input type="number" value={field.value} onChange={e => {
|
|
204
|
+
field.onChange(e.target.valueAsNumber);
|
|
205
|
+
}} />
|
|
204
206
|
</FormControl>
|
|
205
207
|
</FormItem>
|
|
206
208
|
)}
|
|
@@ -224,7 +226,11 @@ function ProductVariantDetailPage() {
|
|
|
224
226
|
<FormLabel>
|
|
225
227
|
<Trans>Track inventory</Trans>
|
|
226
228
|
</FormLabel>
|
|
227
|
-
<Select onValueChange={
|
|
229
|
+
<Select onValueChange={val => {
|
|
230
|
+
if (val) {
|
|
231
|
+
field.onChange(val)
|
|
232
|
+
}
|
|
233
|
+
}} value={field.value}>
|
|
228
234
|
<FormControl>
|
|
229
235
|
<SelectTrigger className="">
|
|
230
236
|
<SelectValue placeholder="Track inventory" />
|
|
@@ -57,6 +57,8 @@ function PromotionDetailPage() {
|
|
|
57
57
|
transformCreateInput: values => {
|
|
58
58
|
return {
|
|
59
59
|
...values,
|
|
60
|
+
startsAt: values.startsAt || undefined,
|
|
61
|
+
endsAt: values.endsAt || undefined,
|
|
60
62
|
conditions: values.conditions.filter(c => c.code !== ''),
|
|
61
63
|
actions: values.actions.filter(a => a.code !== ''),
|
|
62
64
|
};
|
|
@@ -98,6 +100,10 @@ function PromotionDetailPage() {
|
|
|
98
100
|
if (creatingNewEntity) {
|
|
99
101
|
await navigate({ to: `../${data.id}`, from: Route.id });
|
|
100
102
|
}
|
|
103
|
+
} else {
|
|
104
|
+
toast.error(i18n.t('Failed to update promotion'), {
|
|
105
|
+
description: data.message,
|
|
106
|
+
});
|
|
101
107
|
}
|
|
102
108
|
},
|
|
103
109
|
onError: err => {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
+
import { Badge } from '@/components/ui/badge.js';
|
|
2
|
+
import { Button } from '@/components/ui/button.js';
|
|
1
3
|
import { ListPage } from '@/framework/page/list-page.js';
|
|
4
|
+
import { api } from '@/graphql/api.js';
|
|
2
5
|
import { Trans } from '@/lib/trans.js';
|
|
3
6
|
import { createFileRoute } from '@tanstack/react-router';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { Button } from '@/components/ui/button.js';
|
|
7
|
+
import { formatRelative } from 'date-fns';
|
|
8
|
+
import { Ban, CheckCircle2Icon, CircleXIcon, ClockIcon, LoaderIcon, RotateCcw } from 'lucide-react';
|
|
7
9
|
import { PayloadDialog } from './components/payload-dialog.js';
|
|
8
|
-
import {
|
|
9
|
-
import { Ban, CircleXIcon, ClockIcon, LoaderIcon, RotateCcw } from 'lucide-react';
|
|
10
|
-
import { CheckCircle2Icon } from 'lucide-react';
|
|
11
|
-
import { api } from '@/graphql/api.js';
|
|
10
|
+
import { jobListDocument, jobQueueListDocument } from './job-queue.graphql.js';
|
|
12
11
|
|
|
13
12
|
export const Route = createFileRoute('/_authenticated/_system/job-queue')({
|
|
14
13
|
component: JobQueuePage,
|
|
@@ -58,7 +57,7 @@ function JobQueuePage() {
|
|
|
58
57
|
customizeColumns={{
|
|
59
58
|
createdAt: {
|
|
60
59
|
header: 'Created At',
|
|
61
|
-
cell: ({ row }) => formatRelative(row.original.createdAt, new Date())
|
|
60
|
+
cell: ({ row }) => <div title={row.original.createdAt}>{formatRelative(new Date(row.original.createdAt), new Date())}</div>,
|
|
62
61
|
},
|
|
63
62
|
data: {
|
|
64
63
|
header: 'Data',
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { FullWidthPageBlock, Page, PageLayout, PageTitle } from '@/framework/layout-engine/page-layout.js';
|
|
2
|
+
import { api } from '@/graphql/api.js';
|
|
3
|
+
import { graphql } from '@/graphql/graphql.js';
|
|
4
|
+
import { DataTable } from '@/components/data-table/data-table.js';
|
|
5
|
+
import { Trans, useLingui } from '@/lib/trans.js';
|
|
6
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
8
|
+
import { createColumnHelper } from '@tanstack/react-table';
|
|
9
|
+
import { ResultOf } from '@/graphql/graphql.js';
|
|
10
|
+
import { PayloadDialog } from './components/payload-dialog.js';
|
|
11
|
+
import { Button } from '@/components/ui/button.js';
|
|
12
|
+
import { Badge } from '@/components/ui/badge.js';
|
|
13
|
+
import { useLocalFormat } from '@/hooks/use-local-format.js';
|
|
14
|
+
import {
|
|
15
|
+
DropdownMenu,
|
|
16
|
+
DropdownMenuContent,
|
|
17
|
+
DropdownMenuItem,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from '@/components/ui/dropdown-menu.js';
|
|
20
|
+
import { CirclePlay, EllipsisIcon } from 'lucide-react';
|
|
21
|
+
import { toast } from 'sonner';
|
|
22
|
+
|
|
23
|
+
export const Route = createFileRoute('/_authenticated/_system/scheduled-tasks')({
|
|
24
|
+
component: ScheduledTasksPage,
|
|
25
|
+
loader: () => ({ breadcrumb: () => <Trans>Scheduled Tasks</Trans> }),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const getScheduledTasksDocument = graphql(`
|
|
29
|
+
query ScheduledTasks {
|
|
30
|
+
scheduledTasks {
|
|
31
|
+
id
|
|
32
|
+
description
|
|
33
|
+
schedule
|
|
34
|
+
scheduleDescription
|
|
35
|
+
lastExecutedAt
|
|
36
|
+
nextExecutionAt
|
|
37
|
+
isRunning
|
|
38
|
+
lastResult
|
|
39
|
+
enabled
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
const updateScheduledTaskDocument = graphql(`
|
|
45
|
+
mutation UpdateScheduledTask($input: UpdateScheduledTaskInput!) {
|
|
46
|
+
updateScheduledTask(input: $input) {
|
|
47
|
+
id
|
|
48
|
+
enabled
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
const runScheduledTaskDocument = graphql(`
|
|
54
|
+
mutation RunScheduledTask($id: String!) {
|
|
55
|
+
runScheduledTask(id: $id) {
|
|
56
|
+
success
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
type ScheduledTask = ResultOf<typeof getScheduledTasksDocument>['scheduledTasks'][number];
|
|
62
|
+
|
|
63
|
+
function ScheduledTasksPage() {
|
|
64
|
+
const { i18n } = useLingui();
|
|
65
|
+
const { data } = useQuery({
|
|
66
|
+
queryKey: ['scheduledTasks'],
|
|
67
|
+
queryFn: () => api.query(getScheduledTasksDocument),
|
|
68
|
+
});
|
|
69
|
+
const queryClient = useQueryClient();
|
|
70
|
+
const { mutate: updateScheduledTask } = useMutation({
|
|
71
|
+
mutationFn: api.mutate(updateScheduledTaskDocument),
|
|
72
|
+
onSuccess: (result) => {
|
|
73
|
+
refreshScheduledTasks();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const refreshScheduledTasks = () => {
|
|
77
|
+
queryClient.invalidateQueries({ queryKey: ['scheduledTasks'] });
|
|
78
|
+
}
|
|
79
|
+
const { mutate: runScheduledTask } = useMutation({
|
|
80
|
+
mutationFn: api.mutate(runScheduledTaskDocument),
|
|
81
|
+
onSuccess: (result) => {
|
|
82
|
+
if ((result as ResultOf<typeof runScheduledTaskDocument>).runScheduledTask.success) {
|
|
83
|
+
toast.success(i18n.t(`Scheduled task will be executed`));
|
|
84
|
+
queryClient.invalidateQueries({ queryKey: ['scheduledTasks'] });
|
|
85
|
+
} else {
|
|
86
|
+
toast.error(i18n.t(`Scheduled task could not be executed`));
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
const { formatDate, formatRelativeDate } = useLocalFormat();
|
|
91
|
+
const intlDateOptions = {
|
|
92
|
+
year: 'numeric',
|
|
93
|
+
month: 'short',
|
|
94
|
+
day: 'numeric',
|
|
95
|
+
hour: 'numeric',
|
|
96
|
+
minute: 'numeric',
|
|
97
|
+
second: 'numeric',
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
100
|
+
const columnHelper = createColumnHelper<ScheduledTask>();
|
|
101
|
+
const columns = [
|
|
102
|
+
columnHelper.accessor('id', {
|
|
103
|
+
header: 'ID',
|
|
104
|
+
}),
|
|
105
|
+
columnHelper.accessor('description', {
|
|
106
|
+
header: 'Description',
|
|
107
|
+
}),
|
|
108
|
+
columnHelper.accessor('enabled', {
|
|
109
|
+
header: 'Enabled',
|
|
110
|
+
cell: ({ row }) => {
|
|
111
|
+
return row.original.enabled ? (
|
|
112
|
+
<Badge variant="success">
|
|
113
|
+
<Trans>Enabled</Trans>
|
|
114
|
+
</Badge>
|
|
115
|
+
) : (
|
|
116
|
+
<Badge variant="secondary">
|
|
117
|
+
<Trans>Disabled</Trans>
|
|
118
|
+
</Badge>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
columnHelper.accessor('schedule', {
|
|
123
|
+
header: 'Schedule Pattern',
|
|
124
|
+
}),
|
|
125
|
+
columnHelper.accessor('scheduleDescription', {
|
|
126
|
+
header: 'Schedule',
|
|
127
|
+
}),
|
|
128
|
+
columnHelper.accessor('lastExecutedAt', {
|
|
129
|
+
header: 'Last Executed',
|
|
130
|
+
cell: ({ row }) => {
|
|
131
|
+
return row.original.lastExecutedAt ? (
|
|
132
|
+
<div title={row.original.lastExecutedAt}>
|
|
133
|
+
{formatRelativeDate(row.original.lastExecutedAt)}
|
|
134
|
+
</div>
|
|
135
|
+
) : (
|
|
136
|
+
<Trans>Never</Trans>
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
columnHelper.accessor('nextExecutionAt', {
|
|
141
|
+
header: 'Next Execution',
|
|
142
|
+
cell: ({ row }) => {
|
|
143
|
+
return row.original.nextExecutionAt ? (
|
|
144
|
+
formatDate(row.original.nextExecutionAt, intlDateOptions)
|
|
145
|
+
) : (
|
|
146
|
+
<Trans>Never</Trans>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
columnHelper.accessor('isRunning', {
|
|
151
|
+
header: 'Running',
|
|
152
|
+
cell: ({ row }) => {
|
|
153
|
+
return row.original.isRunning ? (
|
|
154
|
+
<Badge variant="success">
|
|
155
|
+
<Trans>Running</Trans>
|
|
156
|
+
</Badge>
|
|
157
|
+
) : (
|
|
158
|
+
<Badge variant="secondary">
|
|
159
|
+
<Trans>Not Running</Trans>
|
|
160
|
+
</Badge>
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
columnHelper.accessor('lastResult', {
|
|
165
|
+
header: 'Last Result',
|
|
166
|
+
cell: ({ row }) => {
|
|
167
|
+
return row.original.lastResult ? (
|
|
168
|
+
<PayloadDialog
|
|
169
|
+
payload={row.original.lastResult}
|
|
170
|
+
title={<Trans>View job result</Trans>}
|
|
171
|
+
description={<Trans>The result of the job</Trans>}
|
|
172
|
+
trigger={
|
|
173
|
+
<Button size="sm" variant="secondary">
|
|
174
|
+
View result
|
|
175
|
+
</Button>
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
) : (
|
|
179
|
+
<div className="text-muted-foreground">
|
|
180
|
+
<Trans>No result yet</Trans>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
columnHelper.display({
|
|
186
|
+
id: 'actions',
|
|
187
|
+
header: 'Actions',
|
|
188
|
+
cell: ({ row }) => {
|
|
189
|
+
return (
|
|
190
|
+
<DropdownMenu>
|
|
191
|
+
<DropdownMenuTrigger asChild>
|
|
192
|
+
<Button variant="ghost" size="icon">
|
|
193
|
+
<EllipsisIcon />
|
|
194
|
+
</Button>
|
|
195
|
+
</DropdownMenuTrigger>
|
|
196
|
+
<DropdownMenuContent>
|
|
197
|
+
{row.original.enabled && <DropdownMenuItem
|
|
198
|
+
onClick={() =>
|
|
199
|
+
runScheduledTask({
|
|
200
|
+
id: row.original.id,
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
>
|
|
204
|
+
<CirclePlay className="w-4 h-4" />
|
|
205
|
+
<Trans>Run</Trans>
|
|
206
|
+
</DropdownMenuItem>}
|
|
207
|
+
<DropdownMenuItem
|
|
208
|
+
onClick={() =>
|
|
209
|
+
updateScheduledTask({
|
|
210
|
+
input: { id: row.original.id, enabled: !row.original.enabled },
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
{row.original.enabled ? <Trans>Disable</Trans> : <Trans>Enable</Trans>}
|
|
215
|
+
</DropdownMenuItem>
|
|
216
|
+
</DropdownMenuContent>
|
|
217
|
+
</DropdownMenu>
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
}),
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Page pageId="scheduled-tasks-list">
|
|
225
|
+
<PageTitle>Scheduled Tasks</PageTitle>
|
|
226
|
+
<PageLayout>
|
|
227
|
+
<FullWidthPageBlock blockId="list-table">
|
|
228
|
+
<DataTable
|
|
229
|
+
onRefresh={refreshScheduledTasks}
|
|
230
|
+
columns={columns}
|
|
231
|
+
data={data?.scheduledTasks ?? []}
|
|
232
|
+
totalItems={data?.scheduledTasks?.length ?? 0}
|
|
233
|
+
defaultColumnVisibility={{
|
|
234
|
+
schedule: false,
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
</FullWidthPageBlock>
|
|
238
|
+
</PageLayout>
|
|
239
|
+
</Page>
|
|
240
|
+
);
|
|
241
|
+
}
|
package/src/app/styles.css
CHANGED
|
@@ -76,6 +76,21 @@
|
|
|
76
76
|
grid-column: span 2 / span 2;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
@layer utilities {
|
|
80
|
+
@keyframes rotate {
|
|
81
|
+
0% {
|
|
82
|
+
transform: rotate(0deg);
|
|
83
|
+
}
|
|
84
|
+
100% {
|
|
85
|
+
transform: rotate(360deg);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.animate-rotate {
|
|
90
|
+
animation: rotate 0.5s linear;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
79
94
|
/* Overrides for the react-grid-layout library */
|
|
80
95
|
.react-grid-item {
|
|
81
96
|
transition: none !important;
|
|
@@ -73,7 +73,7 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
|
|
|
73
73
|
<div className="flex items-center gap-2">
|
|
74
74
|
<DropdownMenu>
|
|
75
75
|
<DropdownMenuTrigger asChild>
|
|
76
|
-
<Button variant="
|
|
76
|
+
<Button variant="ghost" size="sm" className="ml-auto hidden h-8 lg:flex">
|
|
77
77
|
<Settings2 />
|
|
78
78
|
<Trans>Columns</Trans>
|
|
79
79
|
</Button>
|
|
@@ -23,6 +23,7 @@ import { AddFilterMenu } from './add-filter-menu.js';
|
|
|
23
23
|
import { DataTableFacetedFilter, DataTableFacetedFilterOption } from './data-table-faceted-filter.js';
|
|
24
24
|
import { DataTableFilterBadge } from './data-table-filter-badge.js';
|
|
25
25
|
import { useChannel } from '@/hooks/use-channel.js';
|
|
26
|
+
import { RefreshButton } from '@/components/data-table/refresh-button.js';
|
|
26
27
|
|
|
27
28
|
export interface FacetedFilter {
|
|
28
29
|
title: string;
|
|
@@ -31,8 +32,8 @@ export interface FacetedFilter {
|
|
|
31
32
|
options?: DataTableFacetedFilterOption[];
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
interface DataTableProps<TData
|
|
35
|
-
columns: ColumnDef<TData,
|
|
35
|
+
interface DataTableProps<TData> {
|
|
36
|
+
columns: ColumnDef<TData, any>[];
|
|
36
37
|
data: TData[];
|
|
37
38
|
totalItems: number;
|
|
38
39
|
page?: number;
|
|
@@ -52,26 +53,28 @@ interface DataTableProps<TData, TValue> {
|
|
|
52
53
|
* when needed.
|
|
53
54
|
*/
|
|
54
55
|
setTableOptions?: (table: TableOptions<TData>) => TableOptions<TData>;
|
|
56
|
+
onRefresh?: () => void;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
export function DataTable<TData
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
export function DataTable<TData>({
|
|
60
|
+
columns,
|
|
61
|
+
data,
|
|
62
|
+
totalItems,
|
|
63
|
+
page,
|
|
64
|
+
itemsPerPage,
|
|
65
|
+
sorting: sortingInitialState,
|
|
66
|
+
columnFilters: filtersInitialState,
|
|
67
|
+
onPageChange,
|
|
68
|
+
onSortChange,
|
|
69
|
+
onFilterChange,
|
|
70
|
+
onSearchTermChange,
|
|
71
|
+
onColumnVisibilityChange,
|
|
72
|
+
defaultColumnVisibility,
|
|
73
|
+
facetedFilters,
|
|
74
|
+
disableViewOptions,
|
|
75
|
+
setTableOptions,
|
|
76
|
+
onRefresh,
|
|
77
|
+
}: DataTableProps<TData>) {
|
|
75
78
|
const [sorting, setSorting] = React.useState<SortingState>(sortingInitialState || []);
|
|
76
79
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(filtersInitialState || []);
|
|
77
80
|
const { activeChannel } = useChannel();
|
|
@@ -160,15 +163,18 @@ export function DataTable<TData, TValue>({
|
|
|
160
163
|
const column = table.getColumn(f.id);
|
|
161
164
|
const currency = activeChannel?.defaultCurrencyCode ?? 'USD';
|
|
162
165
|
return <DataTableFilterBadge
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
key={f.id}
|
|
167
|
+
filter={f}
|
|
168
|
+
currencyCode={currency}
|
|
169
|
+
dataType={(column?.columnDef.meta as any)?.fieldInfo?.type ?? 'String'}
|
|
170
|
+
onRemove={() => setColumnFilters(old => old.filter(x => x.id !== f.id))} />;
|
|
168
171
|
})}
|
|
169
172
|
</div>
|
|
170
173
|
</div>
|
|
171
|
-
|
|
174
|
+
<div className="flex items-center justify-start gap-2">
|
|
175
|
+
{!disableViewOptions && <DataTableViewOptions table={table} />}
|
|
176
|
+
{onRefresh && <RefreshButton onRefresh={onRefresh} />}
|
|
177
|
+
</div>
|
|
172
178
|
</div>
|
|
173
179
|
<div className="rounded-md border my-2">
|
|
174
180
|
<Table>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button } from '@/components/ui/button.js';
|
|
3
|
+
import { RefreshCw } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export function RefreshButton({ onRefresh }: { onRefresh: () => void }) {
|
|
6
|
+
const [isRotating, setIsRotating] = useState(false);
|
|
7
|
+
|
|
8
|
+
const handleClick = () => {
|
|
9
|
+
if (!isRotating) {
|
|
10
|
+
setIsRotating(true);
|
|
11
|
+
onRefresh();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Button
|
|
17
|
+
variant="ghost"
|
|
18
|
+
size="sm"
|
|
19
|
+
onClick={handleClick}
|
|
20
|
+
>
|
|
21
|
+
<RefreshCw onAnimationEnd={() => setIsRotating(false)}
|
|
22
|
+
className={isRotating ? 'animate-rotate' : ''} />
|
|
23
|
+
</Button>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -4,7 +4,7 @@ import { useAuth } from '@/hooks/use-auth.js';
|
|
|
4
4
|
import { Link, useNavigate, useRouter } from '@tanstack/react-router';
|
|
5
5
|
import { ChevronsUpDown, LogOut, Monitor, Moon, Sparkles, Sun } from 'lucide-react';
|
|
6
6
|
|
|
7
|
-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.js';
|
|
7
|
+
// import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.js';
|
|
8
8
|
import {
|
|
9
9
|
DropdownMenu,
|
|
10
10
|
DropdownMenuContent,
|
|
@@ -64,10 +64,15 @@ export function NavUser() {
|
|
|
64
64
|
size="lg"
|
|
65
65
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
66
66
|
>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
{/* Avatar component temporarily disabled due to https://github.com/radix-ui/primitives/issues/3489
|
|
68
|
+
error in published package version */}
|
|
69
|
+
{/*<Avatar className="h-8 w-8 rounded-lg">*/}
|
|
70
|
+
{/* <AvatarImage src={user.id} alt={user.firstName} />*/}
|
|
71
|
+
{/* <AvatarFallback className="rounded-lg">{avatarFallback}</AvatarFallback>*/}
|
|
72
|
+
{/*</Avatar>*/}
|
|
73
|
+
<div className='relative flex rounded-lg border justify-center items-center w-8 h-8'>
|
|
74
|
+
{avatarFallback}
|
|
75
|
+
</div>
|
|
71
76
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
72
77
|
<span className="truncate font-semibold">
|
|
73
78
|
{user.firstName} {user.lastName}
|
|
@@ -85,12 +90,12 @@ export function NavUser() {
|
|
|
85
90
|
>
|
|
86
91
|
<DropdownMenuLabel className="p-0 font-normal">
|
|
87
92
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
{/*<Avatar className="h-8 w-8 rounded-lg">*/}
|
|
94
|
+
{/* <AvatarImage src={user.id} alt={user.firstName} />*/}
|
|
95
|
+
{/* <AvatarFallback className="rounded-lg">*/}
|
|
96
|
+
{/* {avatarFallback}*/}
|
|
97
|
+
{/* </AvatarFallback>*/}
|
|
98
|
+
{/*</Avatar>*/}
|
|
94
99
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
95
100
|
<span className="truncate font-semibold">
|
|
96
101
|
{user.firstName} {user.lastName}
|