@open-mercato/scheduler 0.4.6-develop-9ff1d4a9a2 → 0.4.6-develop-219dae16c5

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.
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
@@ -9,21 +9,15 @@ import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall"
9
9
  import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
10
10
  import { useT } from "@open-mercato/shared/lib/i18n/context";
11
11
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
12
- import { Switch } from "@open-mercato/ui/primitives/switch";
13
- import { z } from "zod";
14
- import { ComboboxInput } from "@open-mercato/ui/backend/inputs/ComboboxInput";
15
- import { Label } from "@open-mercato/ui/primitives/label";
16
- import { JsonBuilder } from "@open-mercato/ui/backend/JsonBuilder";
17
- function PayloadJsonEditor({ value, setValue, disabled }) {
18
- return /* @__PURE__ */ jsx(
19
- JsonBuilder,
20
- {
21
- value: value || {},
22
- onChange: setValue,
23
- disabled
24
- }
25
- );
26
- }
12
+ import {
13
+ createTargetOptionsLoader,
14
+ loadTimezoneOptions,
15
+ scheduledJobFormSchema,
16
+ scheduledJobFields,
17
+ scheduledJobGroups,
18
+ ScheduledJobEnabledSwitch,
19
+ buildScheduledJobPayload
20
+ } from "../../../../../lib/scheduledJobFormConfig.js";
27
21
  function EditSchedulePage({ params }) {
28
22
  const t = useT();
29
23
  const router = useRouter();
@@ -31,42 +25,10 @@ function EditSchedulePage({ params }) {
31
25
  const [error, setError] = React.useState(null);
32
26
  const [initialData, setInitialData] = React.useState(null);
33
27
  const [isEnabled, setIsEnabled] = React.useState(false);
34
- const targetOptionsRef = React.useRef(null);
35
- const loadTargetOptions = React.useCallback(async () => {
36
- if (targetOptionsRef.current) return targetOptionsRef.current;
37
- try {
38
- const { result } = await apiCall("/api/scheduler/targets");
39
- const options = result ?? { queues: [], commands: [] };
40
- targetOptionsRef.current = options;
41
- return options;
42
- } catch {
43
- return { queues: [], commands: [] };
44
- }
45
- }, []);
46
- const loadQueueOptions = React.useCallback(async (query) => {
47
- const options = await loadTargetOptions();
48
- if (!query) return options.queues;
49
- const lower = query.toLowerCase();
50
- return options.queues.filter((q) => q.label.toLowerCase().includes(lower));
51
- }, [loadTargetOptions]);
52
- const loadCommandOptions = React.useCallback(async (query) => {
53
- const options = await loadTargetOptions();
54
- if (!query) return options.commands;
55
- const lower = query.toLowerCase();
56
- return options.commands.filter((c) => c.label.toLowerCase().includes(lower));
57
- }, [loadTargetOptions]);
58
- const loadTimezoneOptions = React.useCallback(async (query) => {
59
- try {
60
- const allTz = Intl.supportedValuesOf("timeZone");
61
- const filtered = query ? allTz.filter((tz) => tz.toLowerCase().includes(query.toLowerCase())) : allTz;
62
- return filtered.slice(0, 100).map((tz) => ({
63
- value: tz,
64
- label: tz
65
- }));
66
- } catch {
67
- return [{ value: "UTC", label: "UTC" }];
68
- }
69
- }, []);
28
+ const { loadQueueOptions, loadCommandOptions } = React.useMemo(
29
+ () => createTargetOptionsLoader(apiCall),
30
+ []
31
+ );
70
32
  React.useEffect(() => {
71
33
  async function fetchSchedule() {
72
34
  try {
@@ -98,151 +60,12 @@ function EditSchedulePage({ params }) {
98
60
  }
99
61
  fetchSchedule();
100
62
  }, [params.id, t]);
101
- const formSchema = React.useMemo(
102
- () => z.object({
103
- name: z.string().min(1, t("scheduler.form.name.required", "Name is required")),
104
- description: z.string().optional(),
105
- scopeType: z.enum(["system", "organization", "tenant"]),
106
- scheduleType: z.enum(["cron", "interval"]),
107
- scheduleValue: z.string().min(1, t("scheduler.form.schedule.required", "Schedule is required")),
108
- timezone: z.string(),
109
- targetType: z.enum(["queue", "command"]),
110
- targetQueue: z.string().optional(),
111
- targetCommand: z.string().optional(),
112
- targetPayload: z.record(z.string(), z.unknown()).optional(),
113
- isEnabled: z.boolean()
114
- }),
115
- [t]
116
- );
63
+ const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t]);
117
64
  const fields = React.useMemo(
118
- () => [
119
- {
120
- id: "name",
121
- type: "text",
122
- label: t("scheduler.form.name", "Name"),
123
- required: true
124
- },
125
- {
126
- id: "description",
127
- type: "textarea",
128
- label: t("scheduler.form.description", "Description")
129
- },
130
- {
131
- id: "scopeType",
132
- type: "select",
133
- label: t("scheduler.form.scope_type", "Scope"),
134
- required: true,
135
- options: [
136
- { value: "system", label: t("scheduler.scope.system", "System") },
137
- { value: "organization", label: t("scheduler.scope.organization", "Organization") },
138
- { value: "tenant", label: t("scheduler.scope.tenant", "Tenant") }
139
- ]
140
- },
141
- {
142
- id: "scheduleType",
143
- type: "select",
144
- label: t("scheduler.form.schedule_type", "Schedule Type"),
145
- required: true,
146
- options: [
147
- { value: "cron", label: t("scheduler.type.cron", "Cron Expression") },
148
- { value: "interval", label: t("scheduler.type.interval", "Simple Interval") }
149
- ]
150
- },
151
- {
152
- id: "scheduleValue",
153
- type: "text",
154
- label: t("scheduler.form.schedule_value", "Schedule Value"),
155
- placeholder: t("scheduler.form.schedule_value.placeholder", "e.g. 0 */6 * * * or 15m"),
156
- description: t("scheduler.form.schedule_value.description", 'For cron: use cron expression (e.g., "0 0 * * *"). For interval: use format like "15m", "2h", "1d" (s=seconds, m=minutes, h=hours, d=days)'),
157
- required: true
158
- },
159
- {
160
- id: "timezone",
161
- type: "combobox",
162
- label: t("scheduler.form.timezone", "Timezone"),
163
- placeholder: t("scheduler.form.timezone.placeholder", "Search timezone..."),
164
- required: true,
165
- loadOptions: loadTimezoneOptions,
166
- allowCustomValues: false
167
- },
168
- {
169
- id: "targetType",
170
- type: "select",
171
- label: t("scheduler.form.target_type", "Target Type"),
172
- required: true,
173
- options: [
174
- { value: "queue", label: t("scheduler.target.queue", "Queue") },
175
- { value: "command", label: t("scheduler.target.command", "Command") }
176
- ]
177
- },
178
- {
179
- id: "targetFields",
180
- type: "custom",
181
- label: "",
182
- component: ({ values, setFormValue }) => {
183
- const targetType = values?.targetType;
184
- const targetQueue = values?.targetQueue || "";
185
- const targetCommand = values?.targetCommand || "";
186
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
187
- targetType === "queue" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
188
- /* @__PURE__ */ jsx(Label, { htmlFor: "targetQueue", children: t("scheduler.form.target_queue", "Target Queue") }),
189
- /* @__PURE__ */ jsx(
190
- ComboboxInput,
191
- {
192
- value: targetQueue,
193
- onChange: (next) => setFormValue && setFormValue("targetQueue", next),
194
- placeholder: t("scheduler.form.target_queue.placeholder", "Search queues..."),
195
- loadSuggestions: loadQueueOptions,
196
- allowCustomValues: true
197
- }
198
- )
199
- ] }),
200
- targetType === "command" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
201
- /* @__PURE__ */ jsx(Label, { htmlFor: "targetCommand", children: t("scheduler.form.target_command", "Target Command") }),
202
- /* @__PURE__ */ jsx(
203
- ComboboxInput,
204
- {
205
- value: targetCommand,
206
- onChange: (next) => setFormValue && setFormValue("targetCommand", next),
207
- placeholder: t("scheduler.form.target_command.placeholder", "Search commands..."),
208
- loadSuggestions: loadCommandOptions,
209
- allowCustomValues: false
210
- }
211
- )
212
- ] })
213
- ] });
214
- }
215
- },
216
- {
217
- id: "targetPayload",
218
- type: "custom",
219
- label: t("scheduler.form.target_payload", "Job Arguments (JSON)"),
220
- description: t("scheduler.form.target_payload.description", "Optional JSON payload. Fields tenantId and organizationId are injected automatically at execution time."),
221
- component: (props) => /* @__PURE__ */ jsx(PayloadJsonEditor, { ...props })
222
- }
223
- ],
224
- [t, loadTimezoneOptions, loadQueueOptions, loadCommandOptions]
225
- );
226
- const groups = React.useMemo(
227
- () => [
228
- {
229
- id: "basic",
230
- title: t("scheduler.form.group.basic", "Basic Information"),
231
- fields: ["name", "description", "scopeType"]
232
- },
233
- {
234
- id: "schedule",
235
- title: t("scheduler.form.group.schedule", "Schedule Configuration"),
236
- fields: ["scheduleType", "scheduleValue", "timezone"]
237
- },
238
- {
239
- id: "target",
240
- title: t("scheduler.form.group.target", "Target Configuration"),
241
- fields: ["targetType", "targetFields", "targetPayload"]
242
- }
243
- ],
244
- [t]
65
+ () => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),
66
+ [t, loadQueueOptions, loadCommandOptions]
245
67
  );
68
+ const groups = React.useMemo(() => scheduledJobGroups(t), [t]);
246
69
  if (loading) {
247
70
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("scheduler.loading", "Loading schedule...") }) }) });
248
71
  }
@@ -260,27 +83,12 @@ function EditSchedulePage({ params }) {
260
83
  submitLabel: t("scheduler.form.save", "Save Changes"),
261
84
  cancelHref: "/backend/config/scheduled-jobs",
262
85
  schema: formSchema,
263
- extraActions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
264
- /* @__PURE__ */ jsx(Label, { htmlFor: "enabled-switch", className: "text-sm font-medium cursor-pointer", children: isEnabled ? t("scheduler.form.enabled", "Enabled") : t("scheduler.form.disabled", "Disabled") }),
265
- /* @__PURE__ */ jsx(
266
- Switch,
267
- {
268
- id: "enabled-switch",
269
- checked: isEnabled,
270
- onCheckedChange: setIsEnabled
271
- }
272
- )
273
- ] }),
86
+ extraActions: /* @__PURE__ */ jsx(ScheduledJobEnabledSwitch, { isEnabled, setIsEnabled, t }),
274
87
  onSubmit: async (values) => {
275
- const targetPayload = values.targetPayload && Object.keys(values.targetPayload).length > 0 ? values.targetPayload : null;
88
+ const payload = buildScheduledJobPayload(values, isEnabled);
276
89
  await updateCrud(
277
90
  "scheduler/jobs",
278
- {
279
- id: params.id,
280
- ...values,
281
- targetPayload,
282
- isEnabled
283
- }
91
+ { id: params.id, ...payload }
284
92
  );
285
93
  flash(t("scheduler.success.updated", "Schedule updated successfully"), "success");
286
94
  router.push("/backend/config/scheduled-jobs");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/scheduler/backend/config/scheduled-jobs/%5Bid%5D/edit/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { z } from 'zod'\nimport { ComboboxInput, type ComboboxOption } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'\n\ntype ScheduleFormValues = {\n name: string\n description?: string\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone?: string\n targetType: 'queue' | 'command'\n targetQueue?: string\n targetCommand?: string\n targetPayload?: Record<string, unknown>\n isEnabled: boolean\n}\n\ntype ScheduleData = {\n id: string\n name: string\n description?: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue?: string | null\n targetCommand?: string | null\n targetPayload?: Record<string, unknown> | null\n isEnabled: boolean\n}\n\ntype TargetOptions = {\n queues: ComboboxOption[]\n commands: ComboboxOption[]\n}\n\nfunction PayloadJsonEditor({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n return (\n <JsonBuilder\n value={value || {}}\n onChange={setValue}\n disabled={disabled}\n />\n )\n}\n\nexport default function EditSchedulePage({ params }: { params: { id: string } }) {\n const t = useT()\n const router = useRouter()\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [initialData, setInitialData] = React.useState<Partial<ScheduleFormValues> | null>(null)\n const [isEnabled, setIsEnabled] = React.useState(false)\n const targetOptionsRef = React.useRef<TargetOptions | null>(null)\n\n const loadTargetOptions = React.useCallback(async (): Promise<TargetOptions> => {\n if (targetOptionsRef.current) return targetOptionsRef.current\n try {\n const { result } = await apiCall<TargetOptions>('/api/scheduler/targets')\n const options = result ?? { queues: [], commands: [] }\n targetOptionsRef.current = options\n return options\n } catch {\n return { queues: [], commands: [] }\n }\n }, [])\n\n const loadQueueOptions = React.useCallback(async (query?: string): Promise<ComboboxOption[]> => {\n const options = await loadTargetOptions()\n if (!query) return options.queues\n const lower = query.toLowerCase()\n return options.queues.filter((q) => q.label.toLowerCase().includes(lower))\n }, [loadTargetOptions])\n\n const loadCommandOptions = React.useCallback(async (query?: string): Promise<ComboboxOption[]> => {\n const options = await loadTargetOptions()\n if (!query) return options.commands\n const lower = query.toLowerCase()\n return options.commands.filter((c) => c.label.toLowerCase().includes(lower))\n }, [loadTargetOptions])\n\n // Load timezone options - filtering on query for better performance\n const loadTimezoneOptions = React.useCallback(async (query?: string) => {\n try {\n const allTz = Intl.supportedValuesOf('timeZone')\n const filtered = query\n ? allTz.filter((tz) => tz.toLowerCase().includes(query.toLowerCase()))\n : allTz\n return filtered.slice(0, 100).map((tz) => ({\n value: tz,\n label: tz,\n }))\n } catch {\n return [{ value: 'UTC', label: 'UTC' }]\n }\n }, [])\n\n React.useEffect(() => {\n async function fetchSchedule() {\n try {\n const { result } = await apiCallOrThrow<{ items: ScheduleData[] }>(\n `/api/scheduler/jobs?id=${params.id}`\n )\n\n const schedule = result?.items?.[0]\n if (schedule) {\n setIsEnabled(schedule.isEnabled)\n \n setInitialData({\n name: schedule.name,\n description: schedule.description || undefined,\n scopeType: schedule.scopeType,\n scheduleType: schedule.scheduleType,\n scheduleValue: schedule.scheduleValue,\n timezone: schedule.timezone,\n targetType: schedule.targetType,\n targetQueue: schedule.targetQueue || undefined,\n targetCommand: schedule.targetCommand || undefined,\n targetPayload: (schedule.targetPayload && Object.keys(schedule.targetPayload).length > 0)\n ? schedule.targetPayload\n : undefined,\n isEnabled: schedule.isEnabled,\n })\n }\n } catch (err) {\n setError(t('scheduler.error.load_failed', 'Failed to load schedule'))\n } finally {\n setLoading(false)\n }\n }\n\n fetchSchedule()\n }, [params.id, t])\n\n const formSchema = React.useMemo(\n () =>\n z.object({\n name: z.string().min(1, t('scheduler.form.name.required', 'Name is required')),\n description: z.string().optional(),\n scopeType: z.enum(['system', 'organization', 'tenant']),\n scheduleType: z.enum(['cron', 'interval']),\n scheduleValue: z.string().min(1, t('scheduler.form.schedule.required', 'Schedule is required')),\n timezone: z.string(),\n targetType: z.enum(['queue', 'command']),\n targetQueue: z.string().optional(),\n targetCommand: z.string().optional(),\n targetPayload: z.record(z.string(), z.unknown()).optional(),\n isEnabled: z.boolean(),\n }),\n [t]\n )\n\n const fields = React.useMemo<CrudField[]>(\n () => [\n {\n id: 'name',\n type: 'text',\n label: t('scheduler.form.name', 'Name'),\n required: true,\n },\n {\n id: 'description',\n type: 'textarea',\n label: t('scheduler.form.description', 'Description'),\n },\n {\n id: 'scopeType',\n type: 'select',\n label: t('scheduler.form.scope_type', 'Scope'),\n required: true,\n options: [\n { value: 'system', label: t('scheduler.scope.system', 'System') },\n { value: 'organization', label: t('scheduler.scope.organization', 'Organization') },\n { value: 'tenant', label: t('scheduler.scope.tenant', 'Tenant') },\n ],\n },\n {\n id: 'scheduleType',\n type: 'select',\n label: t('scheduler.form.schedule_type', 'Schedule Type'),\n required: true,\n options: [\n { value: 'cron', label: t('scheduler.type.cron', 'Cron Expression') },\n { value: 'interval', label: t('scheduler.type.interval', 'Simple Interval') },\n ],\n },\n {\n id: 'scheduleValue',\n type: 'text',\n label: t('scheduler.form.schedule_value', 'Schedule Value'),\n placeholder: t('scheduler.form.schedule_value.placeholder', 'e.g. 0 */6 * * * or 15m'),\n description: t('scheduler.form.schedule_value.description', 'For cron: use cron expression (e.g., \"0 0 * * *\"). For interval: use format like \"15m\", \"2h\", \"1d\" (s=seconds, m=minutes, h=hours, d=days)'),\n required: true,\n },\n {\n id: 'timezone',\n type: 'combobox',\n label: t('scheduler.form.timezone', 'Timezone'),\n placeholder: t('scheduler.form.timezone.placeholder', 'Search timezone...'),\n required: true,\n loadOptions: loadTimezoneOptions,\n allowCustomValues: false,\n },\n {\n id: 'targetType',\n type: 'select',\n label: t('scheduler.form.target_type', 'Target Type'),\n required: true,\n options: [\n { value: 'queue', label: t('scheduler.target.queue', 'Queue') },\n { value: 'command', label: t('scheduler.target.command', 'Command') },\n ],\n },\n {\n id: 'targetFields',\n type: 'custom',\n label: '',\n component: ({ values, setFormValue }) => {\n const targetType = values?.targetType as string | undefined\n const targetQueue = (values?.targetQueue as string) || ''\n const targetCommand = (values?.targetCommand as string) || ''\n\n return (\n <div className=\"space-y-4\">\n {targetType === 'queue' && (\n <div className=\"space-y-1\">\n <Label htmlFor=\"targetQueue\">\n {t('scheduler.form.target_queue', 'Target Queue')}\n </Label>\n <ComboboxInput\n value={targetQueue}\n onChange={(next) => setFormValue && setFormValue('targetQueue', next)}\n placeholder={t('scheduler.form.target_queue.placeholder', 'Search queues...')}\n loadSuggestions={loadQueueOptions}\n allowCustomValues={true}\n />\n </div>\n )}\n {targetType === 'command' && (\n <div className=\"space-y-1\">\n <Label htmlFor=\"targetCommand\">\n {t('scheduler.form.target_command', 'Target Command')}\n </Label>\n <ComboboxInput\n value={targetCommand}\n onChange={(next) => setFormValue && setFormValue('targetCommand', next)}\n placeholder={t('scheduler.form.target_command.placeholder', 'Search commands...')}\n loadSuggestions={loadCommandOptions}\n allowCustomValues={false}\n />\n </div>\n )}\n </div>\n )\n },\n },\n {\n id: 'targetPayload',\n type: 'custom',\n label: t('scheduler.form.target_payload', 'Job Arguments (JSON)'),\n description: t('scheduler.form.target_payload.description', 'Optional JSON payload. Fields tenantId and organizationId are injected automatically at execution time.'),\n component: (props) => <PayloadJsonEditor {...props} />,\n },\n ],\n [t, loadTimezoneOptions, loadQueueOptions, loadCommandOptions]\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n title: t('scheduler.form.group.basic', 'Basic Information'),\n fields: ['name', 'description', 'scopeType'],\n },\n {\n id: 'schedule',\n title: t('scheduler.form.group.schedule', 'Schedule Configuration'),\n fields: ['scheduleType', 'scheduleValue', 'timezone'],\n },\n {\n id: 'target',\n title: t('scheduler.form.group.target', 'Target Configuration'),\n fields: ['targetType', 'targetFields', 'targetPayload'],\n },\n ],\n [t]\n )\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('scheduler.loading', 'Loading schedule...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !initialData) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error || t('scheduler.error.not_found', 'Schedule not found')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<ScheduleFormValues>\n title={t('scheduler.edit.title', 'Edit Schedule')}\n backHref=\"/backend/config/scheduled-jobs\"\n fields={fields}\n groups={groups}\n initialValues={initialData}\n submitLabel={t('scheduler.form.save', 'Save Changes')}\n cancelHref=\"/backend/config/scheduled-jobs\"\n schema={formSchema}\n extraActions={\n <div className=\"flex items-center gap-2\">\n <Label htmlFor=\"enabled-switch\" className=\"text-sm font-medium cursor-pointer\">\n {isEnabled ? t('scheduler.form.enabled', 'Enabled') : t('scheduler.form.disabled', 'Disabled')}\n </Label>\n <Switch\n id=\"enabled-switch\"\n checked={isEnabled}\n onCheckedChange={setIsEnabled}\n />\n </div>\n }\n onSubmit={async (values) => {\n const targetPayload = values.targetPayload && Object.keys(values.targetPayload).length > 0\n ? values.targetPayload\n : null\n\n await updateCrud(\n 'scheduler/jobs',\n { \n id: params.id, \n ...values, \n targetPayload,\n isEnabled \n }\n )\n\n flash(t('scheduler.success.updated', 'Schedule updated successfully'), 'success')\n router.push('/backend/config/scheduled-jobs')\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport {\n type ScheduleFormValues,\n createTargetOptionsLoader,\n loadTimezoneOptions,\n scheduledJobFormSchema,\n scheduledJobFields,\n scheduledJobGroups,\n ScheduledJobEnabledSwitch,\n buildScheduledJobPayload,\n} from '../../../../../lib/scheduledJobFormConfig'\n\ntype ScheduleData = {\n id: string\n name: string\n description?: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue?: string | null\n targetCommand?: string | null\n targetPayload?: Record<string, unknown> | null\n isEnabled: boolean\n}\n\nexport default function EditSchedulePage({ params }: { params: { id: string } }) {\n const t = useT()\n const router = useRouter()\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [initialData, setInitialData] = React.useState<Partial<ScheduleFormValues> | null>(null)\n const [isEnabled, setIsEnabled] = React.useState(false)\n\n const { loadQueueOptions, loadCommandOptions } = React.useMemo(\n () => createTargetOptionsLoader(apiCall),\n []\n )\n\n React.useEffect(() => {\n async function fetchSchedule() {\n try {\n const { result } = await apiCallOrThrow<{ items: ScheduleData[] }>(\n `/api/scheduler/jobs?id=${params.id}`\n )\n\n const schedule = result?.items?.[0]\n if (schedule) {\n setIsEnabled(schedule.isEnabled)\n\n setInitialData({\n name: schedule.name,\n description: schedule.description || undefined,\n scopeType: schedule.scopeType,\n scheduleType: schedule.scheduleType,\n scheduleValue: schedule.scheduleValue,\n timezone: schedule.timezone,\n targetType: schedule.targetType,\n targetQueue: schedule.targetQueue || undefined,\n targetCommand: schedule.targetCommand || undefined,\n targetPayload: (schedule.targetPayload && Object.keys(schedule.targetPayload).length > 0)\n ? schedule.targetPayload\n : undefined,\n isEnabled: schedule.isEnabled,\n })\n }\n } catch (err) {\n setError(t('scheduler.error.load_failed', 'Failed to load schedule'))\n } finally {\n setLoading(false)\n }\n }\n\n fetchSchedule()\n }, [params.id, t])\n\n const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t])\n\n const fields = React.useMemo(\n () => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),\n [t, loadQueueOptions, loadCommandOptions]\n )\n\n const groups = React.useMemo(() => scheduledJobGroups(t), [t])\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('scheduler.loading', 'Loading schedule...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !initialData) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error || t('scheduler.error.not_found', 'Schedule not found')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<ScheduleFormValues>\n title={t('scheduler.edit.title', 'Edit Schedule')}\n backHref=\"/backend/config/scheduled-jobs\"\n fields={fields}\n groups={groups}\n initialValues={initialData}\n submitLabel={t('scheduler.form.save', 'Save Changes')}\n cancelHref=\"/backend/config/scheduled-jobs\"\n schema={formSchema}\n extraActions={\n <ScheduledJobEnabledSwitch isEnabled={isEnabled} setIsEnabled={setIsEnabled} t={t} />\n }\n onSubmit={async (values) => {\n const payload = buildScheduledJobPayload(values, isEnabled)\n\n await updateCrud(\n 'scheduler/jobs',\n { id: params.id, ...payload }\n )\n\n flash(t('scheduler.success.updated', 'Schedule updated successfully'), 'success')\n router.push('/backend/config/scheduled-jobs')\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
@@ -7,207 +7,30 @@ import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
7
7
  import { createCrud } from "@open-mercato/ui/backend/utils/crud";
8
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
9
9
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
10
- import { Switch } from "@open-mercato/ui/primitives/switch";
11
- import { Label } from "@open-mercato/ui/primitives/label";
12
- import { z } from "zod";
13
- import { ComboboxInput } from "@open-mercato/ui/backend/inputs/ComboboxInput";
14
10
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
15
- import { JsonBuilder } from "@open-mercato/ui/backend/JsonBuilder";
16
- function PayloadJsonEditor({ value, setValue, disabled }) {
17
- return /* @__PURE__ */ jsx(
18
- JsonBuilder,
19
- {
20
- value: value || {},
21
- onChange: setValue,
22
- disabled
23
- }
24
- );
25
- }
11
+ import {
12
+ createTargetOptionsLoader,
13
+ loadTimezoneOptions,
14
+ scheduledJobFormSchema,
15
+ scheduledJobFields,
16
+ scheduledJobGroups,
17
+ ScheduledJobEnabledSwitch,
18
+ buildScheduledJobPayload
19
+ } from "../../../../lib/scheduledJobFormConfig.js";
26
20
  function NewSchedulePage() {
27
21
  const t = useT();
28
22
  const router = useRouter();
29
23
  const [isEnabled, setIsEnabled] = React.useState(true);
30
- const targetOptionsRef = React.useRef(null);
31
- const loadTargetOptions = React.useCallback(async () => {
32
- if (targetOptionsRef.current) return targetOptionsRef.current;
33
- try {
34
- const { result } = await apiCall("/api/scheduler/targets");
35
- const options = result ?? { queues: [], commands: [] };
36
- targetOptionsRef.current = options;
37
- return options;
38
- } catch {
39
- return { queues: [], commands: [] };
40
- }
41
- }, []);
42
- const loadQueueOptions = React.useCallback(async (query) => {
43
- const options = await loadTargetOptions();
44
- if (!query) return options.queues;
45
- const lower = query.toLowerCase();
46
- return options.queues.filter((q) => q.label.toLowerCase().includes(lower));
47
- }, [loadTargetOptions]);
48
- const loadCommandOptions = React.useCallback(async (query) => {
49
- const options = await loadTargetOptions();
50
- if (!query) return options.commands;
51
- const lower = query.toLowerCase();
52
- return options.commands.filter((c) => c.label.toLowerCase().includes(lower));
53
- }, [loadTargetOptions]);
54
- const loadTimezoneOptions = React.useCallback(async (query) => {
55
- try {
56
- const allTz = Intl.supportedValuesOf("timeZone");
57
- const filtered = query ? allTz.filter((tz) => tz.toLowerCase().includes(query.toLowerCase())) : allTz;
58
- return filtered.slice(0, 100).map((tz) => ({
59
- value: tz,
60
- label: tz
61
- }));
62
- } catch {
63
- return [{ value: "UTC", label: "UTC" }];
64
- }
65
- }, []);
66
- const formSchema = React.useMemo(
67
- () => z.object({
68
- name: z.string().min(1, t("scheduler.form.name.required", "Name is required")),
69
- description: z.string().optional(),
70
- scopeType: z.enum(["system", "organization", "tenant"]),
71
- scheduleType: z.enum(["cron", "interval"]),
72
- scheduleValue: z.string().min(1, t("scheduler.form.schedule.required", "Schedule is required")),
73
- timezone: z.string(),
74
- targetType: z.enum(["queue", "command"]),
75
- targetQueue: z.string().optional(),
76
- targetCommand: z.string().optional(),
77
- targetPayload: z.record(z.string(), z.unknown()).optional(),
78
- isEnabled: z.boolean()
79
- }),
80
- [t]
24
+ const { loadQueueOptions, loadCommandOptions } = React.useMemo(
25
+ () => createTargetOptionsLoader(apiCall),
26
+ []
81
27
  );
28
+ const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t]);
82
29
  const fields = React.useMemo(
83
- () => [
84
- {
85
- id: "name",
86
- type: "text",
87
- label: t("scheduler.form.name", "Name"),
88
- required: true
89
- },
90
- {
91
- id: "description",
92
- type: "textarea",
93
- label: t("scheduler.form.description", "Description")
94
- },
95
- {
96
- id: "scopeType",
97
- type: "select",
98
- label: t("scheduler.form.scope_type", "Scope"),
99
- required: true,
100
- options: [
101
- { value: "system", label: t("scheduler.scope.system", "System") },
102
- { value: "organization", label: t("scheduler.scope.organization", "Organization") },
103
- { value: "tenant", label: t("scheduler.scope.tenant", "Tenant") }
104
- ]
105
- },
106
- {
107
- id: "scheduleType",
108
- type: "select",
109
- label: t("scheduler.form.schedule_type", "Schedule Type"),
110
- required: true,
111
- options: [
112
- { value: "cron", label: t("scheduler.type.cron", "Cron Expression") },
113
- { value: "interval", label: t("scheduler.type.interval", "Simple Interval") }
114
- ]
115
- },
116
- {
117
- id: "scheduleValue",
118
- type: "text",
119
- label: t("scheduler.form.schedule_value", "Schedule Value"),
120
- placeholder: t("scheduler.form.schedule_value.placeholder", "e.g. 0 */6 * * * or 15m"),
121
- description: t("scheduler.form.schedule_value.description", 'For cron: use cron expression (e.g., "0 0 * * *"). For interval: use format like "15m", "2h", "1d" (s=seconds, m=minutes, h=hours, d=days)'),
122
- required: true
123
- },
124
- {
125
- id: "timezone",
126
- type: "combobox",
127
- label: t("scheduler.form.timezone", "Timezone"),
128
- placeholder: t("scheduler.form.timezone.placeholder", "Search timezone..."),
129
- required: true,
130
- loadOptions: loadTimezoneOptions,
131
- allowCustomValues: false
132
- },
133
- {
134
- id: "targetType",
135
- type: "select",
136
- label: t("scheduler.form.target_type", "Target Type"),
137
- required: true,
138
- options: [
139
- { value: "queue", label: t("scheduler.target.queue", "Queue") },
140
- { value: "command", label: t("scheduler.target.command", "Command") }
141
- ]
142
- },
143
- {
144
- id: "targetFields",
145
- type: "custom",
146
- label: "",
147
- component: ({ values, setFormValue }) => {
148
- const targetType = values?.targetType;
149
- const targetQueue = values?.targetQueue || "";
150
- const targetCommand = values?.targetCommand || "";
151
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
152
- targetType === "queue" && /* @__PURE__ */ jsxs("div", { children: [
153
- /* @__PURE__ */ jsx(Label, { htmlFor: "targetQueue", children: t("scheduler.form.target_queue", "Target Queue") }),
154
- /* @__PURE__ */ jsx(
155
- ComboboxInput,
156
- {
157
- value: targetQueue,
158
- onChange: (next) => setFormValue && setFormValue("targetQueue", next),
159
- placeholder: t("scheduler.form.target_queue.placeholder", "Search queues..."),
160
- loadSuggestions: loadQueueOptions,
161
- allowCustomValues: true
162
- }
163
- )
164
- ] }),
165
- targetType === "command" && /* @__PURE__ */ jsxs("div", { children: [
166
- /* @__PURE__ */ jsx(Label, { htmlFor: "targetCommand", children: t("scheduler.form.target_command", "Target Command") }),
167
- /* @__PURE__ */ jsx(
168
- ComboboxInput,
169
- {
170
- value: targetCommand,
171
- onChange: (next) => setFormValue && setFormValue("targetCommand", next),
172
- placeholder: t("scheduler.form.target_command.placeholder", "Search commands..."),
173
- loadSuggestions: loadCommandOptions,
174
- allowCustomValues: false
175
- }
176
- )
177
- ] })
178
- ] });
179
- }
180
- },
181
- {
182
- id: "targetPayload",
183
- type: "custom",
184
- label: t("scheduler.form.target_payload", "Job Arguments (JSON)"),
185
- description: t("scheduler.form.target_payload.description", "Optional JSON payload. Fields tenantId and organizationId are injected automatically at execution time."),
186
- component: (props) => /* @__PURE__ */ jsx(PayloadJsonEditor, { ...props })
187
- }
188
- ],
189
- [t, loadTimezoneOptions, loadQueueOptions, loadCommandOptions]
190
- );
191
- const groups = React.useMemo(
192
- () => [
193
- {
194
- id: "basic",
195
- title: t("scheduler.form.group.basic", "Basic Information"),
196
- fields: ["name", "description", "scopeType"]
197
- },
198
- {
199
- id: "schedule",
200
- title: t("scheduler.form.group.schedule", "Schedule Configuration"),
201
- fields: ["scheduleType", "scheduleValue", "timezone"]
202
- },
203
- {
204
- id: "target",
205
- title: t("scheduler.form.group.target", "Target Configuration"),
206
- fields: ["targetType", "targetFields", "targetPayload"]
207
- }
208
- ],
209
- [t]
30
+ () => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),
31
+ [t, loadQueueOptions, loadCommandOptions]
210
32
  );
33
+ const groups = React.useMemo(() => scheduledJobGroups(t), [t]);
211
34
  const initialValues = React.useMemo(
212
35
  () => ({
213
36
  scopeType: "tenant",
@@ -229,26 +52,12 @@ function NewSchedulePage() {
229
52
  submitLabel: t("scheduler.form.submit", "Create Schedule"),
230
53
  cancelHref: "/backend/config/scheduled-jobs",
231
54
  schema: formSchema,
232
- extraActions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
233
- /* @__PURE__ */ jsx(Label, { htmlFor: "enabled-switch", className: "text-sm font-medium cursor-pointer", children: isEnabled ? t("scheduler.form.enabled", "Enabled") : t("scheduler.form.disabled", "Disabled") }),
234
- /* @__PURE__ */ jsx(
235
- Switch,
236
- {
237
- id: "enabled-switch",
238
- checked: isEnabled,
239
- onCheckedChange: setIsEnabled
240
- }
241
- )
242
- ] }),
55
+ extraActions: /* @__PURE__ */ jsx(ScheduledJobEnabledSwitch, { isEnabled, setIsEnabled, t }),
243
56
  onSubmit: async (values) => {
244
- const targetPayload = values.targetPayload && Object.keys(values.targetPayload).length > 0 ? values.targetPayload : null;
57
+ const payload = buildScheduledJobPayload(values, isEnabled);
245
58
  await createCrud(
246
59
  "scheduler/jobs",
247
- {
248
- ...values,
249
- targetPayload,
250
- isEnabled
251
- }
60
+ payload
252
61
  );
253
62
  flash(t("scheduler.success.created", "Schedule created successfully"), "success");
254
63
  router.push("/backend/config/scheduled-jobs");