@10x-media/jobs 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,573 @@
1
+ "use client";
2
+ import { t as keys } from "../keys-BYs8GTJy.js";
3
+ import { t as deriveJobStatus } from "../deriveJobStatus-CM_sCsgm.js";
4
+ import { t as jobStatusMeta } from "../jobStatusMeta-BLBSPUcE.js";
5
+ import { Link, Pill, useConfig, useDocumentInfo, useField, useForm, useFormFields, useTranslation } from "@payloadcms/ui";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { formatDate } from "@payloadcms/ui/shared";
9
+ import { createPortal } from "react-dom";
10
+ //#region src/translations/useTranslation.ts
11
+ /**
12
+ * `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without
13
+ * a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.
14
+ */
15
+ const useTranslation$1 = () => useTranslation();
16
+ //#endregion
17
+ //#region src/jobs/JobDocDescription.tsx
18
+ /** Document-header description for a job: the record ID, kept accessible and copyable. */
19
+ const JobDocDescription = () => {
20
+ const { id } = useDocumentInfo();
21
+ const { t } = useTranslation$1();
22
+ const [copied, setCopied] = useState(false);
23
+ const timer = useRef(void 0);
24
+ useEffect(() => () => clearTimeout(timer.current), []);
25
+ if (id == null) return null;
26
+ const value = String(id);
27
+ const copy = () => {
28
+ navigator.clipboard?.writeText(value);
29
+ setCopied(true);
30
+ clearTimeout(timer.current);
31
+ timer.current = setTimeout(() => setCopied(false), 1200);
32
+ };
33
+ return /* @__PURE__ */ jsxs("div", {
34
+ style: {
35
+ alignItems: "center",
36
+ color: "var(--theme-elevation-500)",
37
+ display: "flex",
38
+ fontSize: "0.8125rem",
39
+ gap: "6px"
40
+ },
41
+ children: [
42
+ /* @__PURE__ */ jsx("span", {
43
+ style: {
44
+ fontSize: "0.6875rem",
45
+ letterSpacing: "0.04em",
46
+ textTransform: "uppercase"
47
+ },
48
+ children: t(keys.fieldId)
49
+ }),
50
+ /* @__PURE__ */ jsx("code", {
51
+ style: { fontFamily: "var(--font-mono, monospace)" },
52
+ children: value
53
+ }),
54
+ /* @__PURE__ */ jsx("button", {
55
+ onClick: copy,
56
+ style: {
57
+ background: "none",
58
+ border: "none",
59
+ color: "var(--theme-elevation-400)",
60
+ cursor: "pointer",
61
+ font: "inherit",
62
+ padding: 0,
63
+ textDecoration: "underline"
64
+ },
65
+ type: "button",
66
+ children: copied ? t(keys.copied) : t(keys.copy)
67
+ })
68
+ ]
69
+ });
70
+ };
71
+ //#endregion
72
+ //#region src/jobs/extractErrorMessage.ts
73
+ /**
74
+ * Pull a short, human-readable reason out of a job's `error` value: a string as
75
+ * is, an object's `message` or `name`, or `"Cancelled"` for a cancellation.
76
+ * Returns `undefined` when there is nothing meaningful to show.
77
+ */
78
+ const extractErrorMessage = (error) => {
79
+ if (typeof error === "string") return error || void 0;
80
+ if (typeof error === "object" && error !== null) {
81
+ const value = error;
82
+ if (value.cancelled === true) return "Cancelled";
83
+ if (typeof value.message === "string" && value.message) return value.message;
84
+ if (typeof value.name === "string" && value.name) return value.name;
85
+ }
86
+ };
87
+ //#endregion
88
+ //#region src/jobs/JobErrorPanel.tsx
89
+ const isCancelled = (value) => typeof value === "object" && value !== null && value.cancelled === true;
90
+ /**
91
+ * Field component for a job's `error`: renders the reason as a read-only panel
92
+ * instead of raw JSON. Cancellations read neutral; real failures read as errors.
93
+ */
94
+ const JobErrorPanel = ({ path }) => {
95
+ const { value } = useField({ path });
96
+ const { t } = useTranslation$1();
97
+ const message = extractErrorMessage(value);
98
+ if (!message) return null;
99
+ const cancelled = isCancelled(value);
100
+ const accent = cancelled ? "var(--theme-elevation-400)" : "var(--theme-error-500)";
101
+ return /* @__PURE__ */ jsxs("div", {
102
+ style: { marginBottom: "var(--base)" },
103
+ children: [/* @__PURE__ */ jsx("div", {
104
+ style: {
105
+ color: "var(--theme-elevation-500)",
106
+ fontSize: "0.6875rem",
107
+ letterSpacing: "0.04em",
108
+ marginBottom: "4px",
109
+ textTransform: "uppercase"
110
+ },
111
+ children: cancelled ? t(keys.outcome) : t(keys.fieldError)
112
+ }), /* @__PURE__ */ jsx("div", {
113
+ style: {
114
+ backgroundColor: "var(--theme-elevation-50)",
115
+ borderLeft: `3px solid ${accent}`,
116
+ borderRadius: "0 4px 4px 0",
117
+ color: "var(--theme-elevation-800)",
118
+ padding: "calc(var(--base) / 1.5)",
119
+ wordBreak: "break-word"
120
+ },
121
+ children: message
122
+ })]
123
+ });
124
+ };
125
+ //#endregion
126
+ //#region src/jobs/formatDuration.ts
127
+ /** Human duration between two ISO timestamps; undefined when zero, unknown, or invalid. */
128
+ const formatDuration = (start, end) => {
129
+ if (!start || !end) return;
130
+ const ms = new Date(end).getTime() - new Date(start).getTime();
131
+ if (!Number.isFinite(ms) || ms <= 0) return;
132
+ return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)}s`;
133
+ };
134
+ //#endregion
135
+ //#region src/jobs/formatRelativeTime.ts
136
+ const DIVISIONS = [
137
+ {
138
+ amount: 60,
139
+ unit: "second"
140
+ },
141
+ {
142
+ amount: 60,
143
+ unit: "minute"
144
+ },
145
+ {
146
+ amount: 24,
147
+ unit: "hour"
148
+ },
149
+ {
150
+ amount: 7,
151
+ unit: "day"
152
+ },
153
+ {
154
+ amount: 4.34524,
155
+ unit: "week"
156
+ },
157
+ {
158
+ amount: 12,
159
+ unit: "month"
160
+ },
161
+ {
162
+ amount: Number.POSITIVE_INFINITY,
163
+ unit: "year"
164
+ }
165
+ ];
166
+ const toMillis = (value) => value instanceof Date ? value.getTime() : new Date(value).getTime();
167
+ /**
168
+ * Format a timestamp relative to `now`, for example `2 minutes ago` or
169
+ * `in 1 hour`. Returns an empty string for missing or unparseable input. Pure,
170
+ * so it can be unit-tested and reused outside the cell.
171
+ */
172
+ const formatRelativeTime = (value, now = Date.now()) => {
173
+ if (value === null || value === void 0) return "";
174
+ const time = toMillis(value);
175
+ if (Number.isNaN(time)) return "";
176
+ const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
177
+ let duration = (time - now) / 1e3;
178
+ for (const division of DIVISIONS) {
179
+ if (Math.abs(duration) < division.amount) return formatter.format(Math.round(duration), division.unit);
180
+ duration /= division.amount;
181
+ }
182
+ return formatter.format(Math.round(duration), "year");
183
+ };
184
+ //#endregion
185
+ //#region src/jobs/JobLogTimeline.tsx
186
+ const DEFAULT_DATE_FORMAT$2 = "MMMM do yyyy, h:mm a";
187
+ const hasKeys = (value) => typeof value === "object" && value !== null && Object.keys(value).length > 0;
188
+ const Chevron = ({ open }) => /* @__PURE__ */ jsx("svg", {
189
+ "aria-hidden": "true",
190
+ height: "10",
191
+ style: {
192
+ flexShrink: 0,
193
+ transform: open ? "rotate(90deg)" : "none",
194
+ transition: "transform 0.15s"
195
+ },
196
+ viewBox: "0 0 10 10",
197
+ width: "10",
198
+ children: /* @__PURE__ */ jsx("path", {
199
+ d: "M3 1 L7 5 L3 9",
200
+ fill: "none",
201
+ stroke: "currentColor",
202
+ strokeWidth: "1.5"
203
+ })
204
+ });
205
+ const Detail = ({ label, value }) => /* @__PURE__ */ jsxs("div", {
206
+ style: {
207
+ display: "flex",
208
+ flexDirection: "column",
209
+ gap: "2px"
210
+ },
211
+ children: [/* @__PURE__ */ jsx("span", {
212
+ style: {
213
+ color: "var(--theme-elevation-500)",
214
+ fontSize: "0.6875rem",
215
+ textTransform: "uppercase"
216
+ },
217
+ children: label
218
+ }), /* @__PURE__ */ jsx("span", { children: value })]
219
+ });
220
+ const JsonBlock = ({ label, value }) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
221
+ style: {
222
+ color: "var(--theme-elevation-500)",
223
+ fontSize: "0.6875rem",
224
+ marginBottom: "2px",
225
+ textTransform: "uppercase"
226
+ },
227
+ children: label
228
+ }), /* @__PURE__ */ jsx("pre", {
229
+ style: {
230
+ backgroundColor: "var(--theme-elevation-100)",
231
+ borderRadius: "4px",
232
+ fontSize: "0.75rem",
233
+ margin: 0,
234
+ maxHeight: "220px",
235
+ overflow: "auto",
236
+ padding: "calc(var(--base) / 2)",
237
+ whiteSpace: "pre-wrap",
238
+ wordBreak: "break-word"
239
+ },
240
+ children: JSON.stringify(value, null, 2)
241
+ })] });
242
+ const LogRow = ({ entry, index, pattern }) => {
243
+ const { i18n, t } = useTranslation$1();
244
+ const [open, setOpen] = useState(false);
245
+ const failed = entry.state === "failed";
246
+ const reason = failed ? extractErrorMessage(entry.error) : void 0;
247
+ const duration = formatDuration(entry.executedAt, entry.completedAt);
248
+ const relative = formatRelativeTime(entry.executedAt ?? null);
249
+ const absolute = (date) => date ? formatDate({
250
+ date,
251
+ i18n,
252
+ pattern
253
+ }) : "—";
254
+ return /* @__PURE__ */ jsxs("div", {
255
+ style: {
256
+ backgroundColor: "var(--theme-elevation-50)",
257
+ borderLeft: `3px solid ${failed ? "var(--theme-error-500)" : "var(--theme-success-500)"}`,
258
+ borderRadius: "0 4px 4px 0"
259
+ },
260
+ children: [/* @__PURE__ */ jsxs("button", {
261
+ "aria-expanded": open,
262
+ onClick: () => setOpen((prev) => !prev),
263
+ style: {
264
+ alignItems: "center",
265
+ background: "none",
266
+ border: "none",
267
+ color: "inherit",
268
+ cursor: "pointer",
269
+ display: "flex",
270
+ flexWrap: "wrap",
271
+ font: "inherit",
272
+ gap: "calc(var(--base) / 2)",
273
+ padding: "calc(var(--base) / 2)",
274
+ textAlign: "left",
275
+ width: "100%"
276
+ },
277
+ type: "button",
278
+ children: [
279
+ /* @__PURE__ */ jsx(Chevron, { open }),
280
+ /* @__PURE__ */ jsxs("span", {
281
+ style: { color: "var(--theme-elevation-400)" },
282
+ children: ["#", index + 1]
283
+ }),
284
+ /* @__PURE__ */ jsx(Pill, {
285
+ pillStyle: failed ? "error" : "success",
286
+ size: "small",
287
+ children: failed ? t(keys.statusFailed) : t(keys.statusSucceeded)
288
+ }),
289
+ /* @__PURE__ */ jsx("strong", { children: entry.taskSlug || t(keys.fieldTask) }),
290
+ relative ? /* @__PURE__ */ jsx("span", {
291
+ style: { color: "var(--theme-elevation-500)" },
292
+ children: relative
293
+ }) : null,
294
+ duration ? /* @__PURE__ */ jsx("span", {
295
+ style: { color: "var(--theme-elevation-500)" },
296
+ children: duration
297
+ }) : null,
298
+ reason ? /* @__PURE__ */ jsx("span", {
299
+ style: {
300
+ color: "var(--theme-elevation-500)",
301
+ maxWidth: "32ch",
302
+ overflow: "hidden",
303
+ textOverflow: "ellipsis",
304
+ whiteSpace: "nowrap"
305
+ },
306
+ children: reason
307
+ }) : null
308
+ ]
309
+ }), /* @__PURE__ */ jsx("div", {
310
+ style: {
311
+ display: "grid",
312
+ gridTemplateRows: open ? "1fr" : "0fr",
313
+ transition: "grid-template-rows 0.2s ease"
314
+ },
315
+ children: /* @__PURE__ */ jsx("div", {
316
+ style: { overflow: "hidden" },
317
+ children: /* @__PURE__ */ jsxs("div", {
318
+ style: {
319
+ display: "flex",
320
+ flexDirection: "column",
321
+ gap: "calc(var(--base) / 2)",
322
+ padding: "0 calc(var(--base) / 2) calc(var(--base) / 2)"
323
+ },
324
+ children: [
325
+ /* @__PURE__ */ jsxs("div", {
326
+ style: {
327
+ columnGap: "var(--base)",
328
+ display: "flex",
329
+ flexWrap: "wrap",
330
+ rowGap: "8px"
331
+ },
332
+ children: [
333
+ /* @__PURE__ */ jsx(Detail, {
334
+ label: t(keys.fieldExecuted),
335
+ value: absolute(entry.executedAt)
336
+ }),
337
+ /* @__PURE__ */ jsx(Detail, {
338
+ label: t(keys.fieldCompleted),
339
+ value: absolute(entry.completedAt)
340
+ }),
341
+ entry.taskID ? /* @__PURE__ */ jsx(Detail, {
342
+ label: t(keys.fieldTaskId),
343
+ value: entry.taskID
344
+ }) : null
345
+ ]
346
+ }),
347
+ /* @__PURE__ */ jsx(JsonBlock, {
348
+ label: t(keys.fieldInput),
349
+ value: entry.input ?? {}
350
+ }),
351
+ hasKeys(entry.output) ? /* @__PURE__ */ jsx(JsonBlock, {
352
+ label: t(keys.fieldOutput),
353
+ value: entry.output
354
+ }) : null,
355
+ entry.error != null ? /* @__PURE__ */ jsx(JsonBlock, {
356
+ label: t(keys.fieldError),
357
+ value: entry.error
358
+ }) : null
359
+ ]
360
+ })
361
+ })
362
+ })]
363
+ });
364
+ };
365
+ /**
366
+ * Field component for a job's `log`: a read-only per-attempt timeline. Each
367
+ * attempt collapses to a one-line summary and expands to reveal its full data
368
+ * (timings, task ID, input, output, error) so nothing is hidden.
369
+ */
370
+ const JobLogTimeline = ({ path }) => {
371
+ const { getDataByPath } = useForm();
372
+ const { config } = useConfig();
373
+ const { t } = useTranslation$1();
374
+ const pattern = config.admin?.dateFormat ?? DEFAULT_DATE_FORMAT$2;
375
+ const raw = getDataByPath(path);
376
+ const entries = Array.isArray(raw) ? raw : [];
377
+ if (entries.length === 0) return /* @__PURE__ */ jsx("div", {
378
+ style: { color: "var(--theme-elevation-450)" },
379
+ children: t(keys.noAttempts)
380
+ });
381
+ return /* @__PURE__ */ jsx("div", {
382
+ style: {
383
+ display: "flex",
384
+ flexDirection: "column",
385
+ gap: "calc(var(--base) / 2)"
386
+ },
387
+ children: entries.map((entry, index) => /* @__PURE__ */ jsx(LogRow, {
388
+ entry,
389
+ index,
390
+ pattern
391
+ }, entry.id ?? index))
392
+ });
393
+ };
394
+ //#endregion
395
+ //#region src/jobs/JobStatusCell.tsx
396
+ /**
397
+ * List cell that renders a job's derived status as a native Payload Pill. On the
398
+ * linked (first) column it wraps the badge in a link to the document, matching
399
+ * Payload's default cell behavior.
400
+ */
401
+ const JobStatusCell = ({ collectionSlug, link, linkURL, rowData }) => {
402
+ const { config } = useConfig();
403
+ const { t } = useTranslation$1();
404
+ const { labelKey, pillStyle } = jobStatusMeta[deriveJobStatus(rowData ?? {})];
405
+ const pill = /* @__PURE__ */ jsx(Pill, {
406
+ pillStyle,
407
+ size: "small",
408
+ children: t(labelKey)
409
+ });
410
+ if (link && collectionSlug && rowData?.id != null) {
411
+ const adminRoute = config.routes?.admin ?? "/admin";
412
+ return /* @__PURE__ */ jsx(Link, {
413
+ href: linkURL ?? `${adminRoute}/collections/${collectionSlug}/${encodeURIComponent(String(rowData.id))}`,
414
+ prefetch: false,
415
+ children: pill
416
+ });
417
+ }
418
+ return pill;
419
+ };
420
+ //#endregion
421
+ //#region src/jobs/JobStatusHeader.tsx
422
+ const DEFAULT_DATE_FORMAT$1 = "MMMM do yyyy, h:mm a";
423
+ const Fact = ({ label, tooltip, value }) => {
424
+ const [coords, setCoords] = useState(null);
425
+ const [mounted, setMounted] = useState(false);
426
+ useEffect(() => setMounted(true), []);
427
+ return /* @__PURE__ */ jsxs("div", {
428
+ style: {
429
+ display: "flex",
430
+ flexDirection: "column",
431
+ gap: "2px"
432
+ },
433
+ children: [
434
+ /* @__PURE__ */ jsx("span", {
435
+ style: {
436
+ color: "var(--theme-elevation-500)",
437
+ fontSize: "0.6875rem",
438
+ letterSpacing: "0.04em",
439
+ textTransform: "uppercase"
440
+ },
441
+ children: label
442
+ }),
443
+ /* @__PURE__ */ jsx("span", {
444
+ onMouseEnter: (event) => {
445
+ if (!tooltip) return;
446
+ const rect = event.currentTarget.getBoundingClientRect();
447
+ setCoords({
448
+ left: rect.left,
449
+ top: rect.bottom
450
+ });
451
+ },
452
+ onMouseLeave: () => setCoords(null),
453
+ style: {
454
+ cursor: tooltip ? "help" : void 0,
455
+ fontWeight: 600,
456
+ width: "fit-content"
457
+ },
458
+ children: value
459
+ }),
460
+ mounted && tooltip && coords ? createPortal(/* @__PURE__ */ jsx("span", {
461
+ style: {
462
+ backgroundColor: "var(--theme-elevation-900)",
463
+ borderRadius: "4px",
464
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.25)",
465
+ color: "var(--theme-elevation-0)",
466
+ fontSize: "0.75rem",
467
+ left: `${coords.left}px`,
468
+ padding: "4px 8px",
469
+ pointerEvents: "none",
470
+ position: "fixed",
471
+ top: `${coords.top + 6}px`,
472
+ whiteSpace: "nowrap",
473
+ zIndex: 9999
474
+ },
475
+ children: tooltip
476
+ }), document.body) : null
477
+ ]
478
+ });
479
+ };
480
+ /**
481
+ * Document header for a job: the derived status as a Pill plus the key facts,
482
+ * read from the form so it stays in sync. Rendered at the top of the edit view.
483
+ */
484
+ const JobStatusHeader = () => {
485
+ const fields = useFormFields(([formFields]) => formFields);
486
+ const { config } = useConfig();
487
+ const { i18n, t } = useTranslation$1();
488
+ const get = (name) => fields?.[name]?.value;
489
+ const meta = jobStatusMeta[deriveJobStatus({
490
+ completedAt: get("completedAt"),
491
+ error: get("error"),
492
+ hasError: get("hasError"),
493
+ processing: get("processing"),
494
+ totalTried: get("totalTried"),
495
+ waitUntil: get("waitUntil")
496
+ })];
497
+ const workflowSlug = get("workflowSlug");
498
+ const taskSlug = get("taskSlug");
499
+ const runsLabel = workflowSlug ? t(keys.fieldWorkflow) : taskSlug ? t(keys.fieldTask) : t(keys.fieldJob);
500
+ const runsValue = String(workflowSlug || taskSlug || "—");
501
+ const pattern = config.admin?.dateFormat ?? DEFAULT_DATE_FORMAT$1;
502
+ const createdAt = get("createdAt");
503
+ const completedAt = get("completedAt");
504
+ const absolute = (date) => date ? formatDate({
505
+ date,
506
+ i18n,
507
+ pattern
508
+ }) : void 0;
509
+ return /* @__PURE__ */ jsxs("div", {
510
+ style: {
511
+ alignItems: "flex-end",
512
+ borderBottom: "1px solid var(--theme-elevation-100)",
513
+ columnGap: "calc(var(--base) * 1.5)",
514
+ display: "flex",
515
+ flexWrap: "wrap",
516
+ marginBottom: "var(--base)",
517
+ paddingBottom: "var(--base)",
518
+ rowGap: "var(--base)"
519
+ },
520
+ children: [
521
+ /* @__PURE__ */ jsx(Pill, {
522
+ pillStyle: meta.pillStyle,
523
+ size: "medium",
524
+ children: t(meta.labelKey)
525
+ }),
526
+ /* @__PURE__ */ jsx(Fact, {
527
+ label: runsLabel,
528
+ value: runsValue
529
+ }),
530
+ /* @__PURE__ */ jsx(Fact, {
531
+ label: t(keys.fieldQueue),
532
+ value: String(get("queue") || "default")
533
+ }),
534
+ /* @__PURE__ */ jsx(Fact, {
535
+ label: t(keys.fieldAttempts),
536
+ value: String(get("totalTried") ?? 0)
537
+ }),
538
+ /* @__PURE__ */ jsx(Fact, {
539
+ label: t(keys.fieldCreated),
540
+ tooltip: absolute(createdAt),
541
+ value: formatRelativeTime(createdAt) || "—"
542
+ }),
543
+ /* @__PURE__ */ jsx(Fact, {
544
+ label: t(keys.fieldCompleted),
545
+ tooltip: absolute(completedAt),
546
+ value: completedAt ? formatRelativeTime(completedAt) : "—"
547
+ })
548
+ ]
549
+ });
550
+ };
551
+ //#endregion
552
+ //#region src/jobs/RelativeTimeCell.tsx
553
+ const DEFAULT_DATE_FORMAT = "MMMM do yyyy, h:mm a";
554
+ /** List cell for date fields: relative time, with the admin-formatted timestamp on hover. */
555
+ const RelativeTimeCell = ({ cellData }) => {
556
+ const { config } = useConfig();
557
+ const { i18n } = useTranslation$1();
558
+ if (cellData === null || cellData === void 0) return null;
559
+ const relative = formatRelativeTime(cellData);
560
+ if (!relative) return null;
561
+ return /* @__PURE__ */ jsx("span", {
562
+ title: formatDate({
563
+ date: cellData,
564
+ i18n,
565
+ pattern: config.admin?.dateFormat ?? DEFAULT_DATE_FORMAT
566
+ }),
567
+ children: relative
568
+ });
569
+ };
570
+ //#endregion
571
+ export { JobDocDescription, JobErrorPanel, JobLogTimeline, JobStatusCell, JobStatusHeader, RelativeTimeCell };
572
+
573
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["useTranslation","usePayloadTranslation","useTranslation","useTranslation","DEFAULT_DATE_FORMAT","useTranslation","useTranslation","DEFAULT_DATE_FORMAT","useTranslation","useTranslation"],"sources":["../../src/translations/useTranslation.ts","../../src/jobs/JobDocDescription.tsx","../../src/jobs/extractErrorMessage.ts","../../src/jobs/JobErrorPanel.tsx","../../src/jobs/formatDuration.ts","../../src/jobs/formatRelativeTime.ts","../../src/jobs/JobLogTimeline.tsx","../../src/jobs/JobStatusCell.tsx","../../src/jobs/JobStatusHeader.tsx","../../src/jobs/RelativeTimeCell.tsx"],"sourcesContent":["'use client'\n\nimport { useTranslation as usePayloadTranslation } from '@payloadcms/ui'\n\nimport type { TranslationKey } from './keys'\n\n/**\n * `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without\n * a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.\n */\nexport const useTranslation = () => usePayloadTranslation<Record<string, never>, TranslationKey>()\n","'use client'\n\nimport { useDocumentInfo } from '@payloadcms/ui'\nimport { useEffect, useRef, useState } from 'react'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\n/** Document-header description for a job: the record ID, kept accessible and copyable. */\nexport const JobDocDescription = () => {\n\tconst { id } = useDocumentInfo()\n\tconst { t } = useTranslation()\n\tconst [copied, setCopied] = useState(false)\n\tconst timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n\tuseEffect(() => () => clearTimeout(timer.current), [])\n\n\tif (id == null) {\n\t\treturn null\n\t}\n\tconst value = String(id)\n\tconst copy = () => {\n\t\tvoid navigator.clipboard?.writeText(value)\n\t\tsetCopied(true)\n\t\tclearTimeout(timer.current)\n\t\ttimer.current = setTimeout(() => setCopied(false), 1200)\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\talignItems: 'center',\n\t\t\t\tcolor: 'var(--theme-elevation-500)',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tfontSize: '0.8125rem',\n\t\t\t\tgap: '6px',\n\t\t\t}}\n\t\t>\n\t\t\t<span style={{ fontSize: '0.6875rem', letterSpacing: '0.04em', textTransform: 'uppercase' }}>\n\t\t\t\t{t(keys.fieldId)}\n\t\t\t</span>\n\t\t\t<code style={{ fontFamily: 'var(--font-mono, monospace)' }}>{value}</code>\n\t\t\t<button\n\t\t\t\tonClick={copy}\n\t\t\t\tstyle={{\n\t\t\t\t\tbackground: 'none',\n\t\t\t\t\tborder: 'none',\n\t\t\t\t\tcolor: 'var(--theme-elevation-400)',\n\t\t\t\t\tcursor: 'pointer',\n\t\t\t\t\tfont: 'inherit',\n\t\t\t\t\tpadding: 0,\n\t\t\t\t\ttextDecoration: 'underline',\n\t\t\t\t}}\n\t\t\t\ttype=\"button\"\n\t\t\t>\n\t\t\t\t{copied ? t(keys.copied) : t(keys.copy)}\n\t\t\t</button>\n\t\t</div>\n\t)\n}\n","/**\n * Pull a short, human-readable reason out of a job's `error` value: a string as\n * is, an object's `message` or `name`, or `\"Cancelled\"` for a cancellation.\n * Returns `undefined` when there is nothing meaningful to show.\n */\nexport const extractErrorMessage = (error: unknown): string | undefined => {\n\tif (typeof error === 'string') {\n\t\treturn error || undefined\n\t}\n\tif (typeof error === 'object' && error !== null) {\n\t\tconst value = error as { cancelled?: unknown; message?: unknown; name?: unknown }\n\t\tif (value.cancelled === true) {\n\t\t\treturn 'Cancelled'\n\t\t}\n\t\tif (typeof value.message === 'string' && value.message) {\n\t\t\treturn value.message\n\t\t}\n\t\tif (typeof value.name === 'string' && value.name) {\n\t\t\treturn value.name\n\t\t}\n\t}\n\treturn undefined\n}\n","'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport type { JSONFieldClientComponent } from 'payload'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport { extractErrorMessage } from './extractErrorMessage'\n\nconst isCancelled = (value: unknown): boolean =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { cancelled?: unknown }).cancelled === true\n\n/**\n * Field component for a job's `error`: renders the reason as a read-only panel\n * instead of raw JSON. Cancellations read neutral; real failures read as errors.\n */\nexport const JobErrorPanel: JSONFieldClientComponent = ({ path }) => {\n\tconst { value } = useField<unknown>({ path })\n\tconst { t } = useTranslation()\n\tconst message = extractErrorMessage(value)\n\tif (!message) {\n\t\treturn null\n\t}\n\n\tconst cancelled = isCancelled(value)\n\tconst accent = cancelled ? 'var(--theme-elevation-400)' : 'var(--theme-error-500)'\n\n\treturn (\n\t\t<div style={{ marginBottom: 'var(--base)' }}>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tcolor: 'var(--theme-elevation-500)',\n\t\t\t\t\tfontSize: '0.6875rem',\n\t\t\t\t\tletterSpacing: '0.04em',\n\t\t\t\t\tmarginBottom: '4px',\n\t\t\t\t\ttextTransform: 'uppercase',\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{cancelled ? t(keys.outcome) : t(keys.fieldError)}\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tbackgroundColor: 'var(--theme-elevation-50)',\n\t\t\t\t\tborderLeft: `3px solid ${accent}`,\n\t\t\t\t\tborderRadius: '0 4px 4px 0',\n\t\t\t\t\tcolor: 'var(--theme-elevation-800)',\n\t\t\t\t\tpadding: 'calc(var(--base) / 1.5)',\n\t\t\t\t\twordBreak: 'break-word',\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{message}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n","/** Human duration between two ISO timestamps; undefined when zero, unknown, or invalid. */\nexport const formatDuration = (start?: string, end?: string): string | undefined => {\n\tif (!start || !end) {\n\t\treturn undefined\n\t}\n\tconst ms = new Date(end).getTime() - new Date(start).getTime()\n\tif (!Number.isFinite(ms) || ms <= 0) {\n\t\treturn undefined\n\t}\n\treturn ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s`\n}\n","const DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [\n\t{ amount: 60, unit: 'second' },\n\t{ amount: 60, unit: 'minute' },\n\t{ amount: 24, unit: 'hour' },\n\t{ amount: 7, unit: 'day' },\n\t{ amount: 4.34524, unit: 'week' },\n\t{ amount: 12, unit: 'month' },\n\t{ amount: Number.POSITIVE_INFINITY, unit: 'year' },\n]\n\nconst toMillis = (value: Date | number | string): number =>\n\tvalue instanceof Date ? value.getTime() : new Date(value).getTime()\n\n/**\n * Format a timestamp relative to `now`, for example `2 minutes ago` or\n * `in 1 hour`. Returns an empty string for missing or unparseable input. Pure,\n * so it can be unit-tested and reused outside the cell.\n */\nexport const formatRelativeTime = (\n\tvalue: Date | null | number | string | undefined,\n\tnow: number = Date.now()\n): string => {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tconst time = toMillis(value)\n\tif (Number.isNaN(time)) {\n\t\treturn ''\n\t}\n\n\tconst formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })\n\tlet duration = (time - now) / 1000\n\n\tfor (const division of DIVISIONS) {\n\t\tif (Math.abs(duration) < division.amount) {\n\t\t\treturn formatter.format(Math.round(duration), division.unit)\n\t\t}\n\t\tduration /= division.amount\n\t}\n\treturn formatter.format(Math.round(duration), 'year')\n}\n","'use client'\n\nimport { Pill, useConfig, useForm } from '@payloadcms/ui'\nimport { formatDate } from '@payloadcms/ui/shared'\nimport type { ArrayFieldClientComponent } from 'payload'\nimport { useState } from 'react'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport { extractErrorMessage } from './extractErrorMessage'\nimport { formatDuration } from './formatDuration'\nimport { formatRelativeTime } from './formatRelativeTime'\n\nconst DEFAULT_DATE_FORMAT = 'MMMM do yyyy, h:mm a'\n\ninterface LogEntry {\n\tcompletedAt?: string\n\terror?: unknown\n\texecutedAt?: string\n\tid?: string\n\tinput?: unknown\n\toutput?: unknown\n\tstate?: 'failed' | 'succeeded'\n\ttaskID?: string\n\ttaskSlug?: string\n}\n\nconst hasKeys = (value: unknown): boolean =>\n\ttypeof value === 'object' && value !== null && Object.keys(value).length > 0\n\nconst Chevron = ({ open }: { open: boolean }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\theight=\"10\"\n\t\tstyle={{\n\t\t\tflexShrink: 0,\n\t\t\ttransform: open ? 'rotate(90deg)' : 'none',\n\t\t\ttransition: 'transform 0.15s',\n\t\t}}\n\t\tviewBox=\"0 0 10 10\"\n\t\twidth=\"10\"\n\t>\n\t\t<path d=\"M3 1 L7 5 L3 9\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n\t</svg>\n)\n\nconst Detail = ({ label, value }: { label: string; value: string }) => (\n\t<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>\n\t\t<span\n\t\t\tstyle={{\n\t\t\t\tcolor: 'var(--theme-elevation-500)',\n\t\t\t\tfontSize: '0.6875rem',\n\t\t\t\ttextTransform: 'uppercase',\n\t\t\t}}\n\t\t>\n\t\t\t{label}\n\t\t</span>\n\t\t<span>{value}</span>\n\t</div>\n)\n\nconst JsonBlock = ({ label, value }: { label: string; value: unknown }) => (\n\t<div>\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tcolor: 'var(--theme-elevation-500)',\n\t\t\t\tfontSize: '0.6875rem',\n\t\t\t\tmarginBottom: '2px',\n\t\t\t\ttextTransform: 'uppercase',\n\t\t\t}}\n\t\t>\n\t\t\t{label}\n\t\t</div>\n\t\t<pre\n\t\t\tstyle={{\n\t\t\t\tbackgroundColor: 'var(--theme-elevation-100)',\n\t\t\t\tborderRadius: '4px',\n\t\t\t\tfontSize: '0.75rem',\n\t\t\t\tmargin: 0,\n\t\t\t\tmaxHeight: '220px',\n\t\t\t\toverflow: 'auto',\n\t\t\t\tpadding: 'calc(var(--base) / 2)',\n\t\t\t\twhiteSpace: 'pre-wrap',\n\t\t\t\twordBreak: 'break-word',\n\t\t\t}}\n\t\t>\n\t\t\t{JSON.stringify(value, null, 2)}\n\t\t</pre>\n\t</div>\n)\n\nconst LogRow = ({ entry, index, pattern }: { entry: LogEntry; index: number; pattern: string }) => {\n\tconst { i18n, t } = useTranslation()\n\tconst [open, setOpen] = useState(false)\n\tconst failed = entry.state === 'failed'\n\tconst reason = failed ? extractErrorMessage(entry.error) : undefined\n\tconst duration = formatDuration(entry.executedAt, entry.completedAt)\n\tconst relative = formatRelativeTime(entry.executedAt ?? null)\n\tconst absolute = (date?: string): string => (date ? formatDate({ date, i18n, pattern }) : '—')\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tbackgroundColor: 'var(--theme-elevation-50)',\n\t\t\t\tborderLeft: `3px solid ${failed ? 'var(--theme-error-500)' : 'var(--theme-success-500)'}`,\n\t\t\t\tborderRadius: '0 4px 4px 0',\n\t\t\t}}\n\t\t>\n\t\t\t<button\n\t\t\t\taria-expanded={open}\n\t\t\t\tonClick={() => setOpen((prev) => !prev)}\n\t\t\t\tstyle={{\n\t\t\t\t\talignItems: 'center',\n\t\t\t\t\tbackground: 'none',\n\t\t\t\t\tborder: 'none',\n\t\t\t\t\tcolor: 'inherit',\n\t\t\t\t\tcursor: 'pointer',\n\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t\tflexWrap: 'wrap',\n\t\t\t\t\tfont: 'inherit',\n\t\t\t\t\tgap: 'calc(var(--base) / 2)',\n\t\t\t\t\tpadding: 'calc(var(--base) / 2)',\n\t\t\t\t\ttextAlign: 'left',\n\t\t\t\t\twidth: '100%',\n\t\t\t\t}}\n\t\t\t\ttype=\"button\"\n\t\t\t>\n\t\t\t\t<Chevron open={open} />\n\t\t\t\t<span style={{ color: 'var(--theme-elevation-400)' }}>#{index + 1}</span>\n\t\t\t\t<Pill pillStyle={failed ? 'error' : 'success'} size=\"small\">\n\t\t\t\t\t{failed ? t(keys.statusFailed) : t(keys.statusSucceeded)}\n\t\t\t\t</Pill>\n\t\t\t\t<strong>{entry.taskSlug || t(keys.fieldTask)}</strong>\n\t\t\t\t{relative ? <span style={{ color: 'var(--theme-elevation-500)' }}>{relative}</span> : null}\n\t\t\t\t{duration ? <span style={{ color: 'var(--theme-elevation-500)' }}>{duration}</span> : null}\n\t\t\t\t{reason ? (\n\t\t\t\t\t<span\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tcolor: 'var(--theme-elevation-500)',\n\t\t\t\t\t\t\tmaxWidth: '32ch',\n\t\t\t\t\t\t\toverflow: 'hidden',\n\t\t\t\t\t\t\ttextOverflow: 'ellipsis',\n\t\t\t\t\t\t\twhiteSpace: 'nowrap',\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{reason}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t</button>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: 'grid',\n\t\t\t\t\tgridTemplateRows: open ? '1fr' : '0fr',\n\t\t\t\t\ttransition: 'grid-template-rows 0.2s ease',\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={{ overflow: 'hidden' }}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\t\t\tgap: 'calc(var(--base) / 2)',\n\t\t\t\t\t\t\tpadding: '0 calc(var(--base) / 2) calc(var(--base) / 2)',\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tstyle={{ columnGap: 'var(--base)', display: 'flex', flexWrap: 'wrap', rowGap: '8px' }}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Detail label={t(keys.fieldExecuted)} value={absolute(entry.executedAt)} />\n\t\t\t\t\t\t\t<Detail label={t(keys.fieldCompleted)} value={absolute(entry.completedAt)} />\n\t\t\t\t\t\t\t{entry.taskID ? <Detail label={t(keys.fieldTaskId)} value={entry.taskID} /> : null}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<JsonBlock label={t(keys.fieldInput)} value={entry.input ?? {}} />\n\t\t\t\t\t\t{hasKeys(entry.output) ? (\n\t\t\t\t\t\t\t<JsonBlock label={t(keys.fieldOutput)} value={entry.output} />\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{entry.error != null ? (\n\t\t\t\t\t\t\t<JsonBlock label={t(keys.fieldError)} value={entry.error} />\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n/**\n * Field component for a job's `log`: a read-only per-attempt timeline. Each\n * attempt collapses to a one-line summary and expands to reveal its full data\n * (timings, task ID, input, output, error) so nothing is hidden.\n */\nexport const JobLogTimeline: ArrayFieldClientComponent = ({ path }) => {\n\tconst { getDataByPath } = useForm()\n\tconst { config } = useConfig()\n\tconst { t } = useTranslation()\n\tconst pattern = config.admin?.dateFormat ?? DEFAULT_DATE_FORMAT\n\tconst raw = getDataByPath<LogEntry[]>(path)\n\tconst entries = Array.isArray(raw) ? raw : []\n\n\tif (entries.length === 0) {\n\t\treturn <div style={{ color: 'var(--theme-elevation-450)' }}>{t(keys.noAttempts)}</div>\n\t}\n\n\treturn (\n\t\t<div style={{ display: 'flex', flexDirection: 'column', gap: 'calc(var(--base) / 2)' }}>\n\t\t\t{entries.map((entry, index) => (\n\t\t\t\t<LogRow entry={entry} index={index} key={entry.id ?? index} pattern={pattern} />\n\t\t\t))}\n\t\t</div>\n\t)\n}\n","'use client'\n\nimport { Link, Pill, useConfig } from '@payloadcms/ui'\nimport type { DefaultCellComponentProps } from 'payload'\n\nimport { useTranslation } from '../translations/useTranslation'\nimport { deriveJobStatus } from './deriveJobStatus'\nimport { jobStatusMeta } from './jobStatusMeta'\n\n/**\n * List cell that renders a job's derived status as a native Payload Pill. On the\n * linked (first) column it wraps the badge in a link to the document, matching\n * Payload's default cell behavior.\n */\nexport const JobStatusCell = ({\n\tcollectionSlug,\n\tlink,\n\tlinkURL,\n\trowData,\n}: DefaultCellComponentProps) => {\n\tconst { config } = useConfig()\n\tconst { t } = useTranslation()\n\tconst { labelKey, pillStyle } = jobStatusMeta[deriveJobStatus(rowData ?? {})]\n\tconst pill = (\n\t\t<Pill pillStyle={pillStyle} size=\"small\">\n\t\t\t{t(labelKey)}\n\t\t</Pill>\n\t)\n\n\tif (link && collectionSlug && rowData?.id != null) {\n\t\tconst adminRoute = config.routes?.admin ?? '/admin'\n\t\tconst href =\n\t\t\tlinkURL ??\n\t\t\t`${adminRoute}/collections/${collectionSlug}/${encodeURIComponent(String(rowData.id))}`\n\t\treturn (\n\t\t\t<Link href={href} prefetch={false}>\n\t\t\t\t{pill}\n\t\t\t</Link>\n\t\t)\n\t}\n\n\treturn pill\n}\n","'use client'\n\nimport { Pill, useConfig, useFormFields } from '@payloadcms/ui'\nimport { formatDate } from '@payloadcms/ui/shared'\nimport type { UIFieldClientComponent } from 'payload'\nimport { useEffect, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport { deriveJobStatus } from './deriveJobStatus'\nimport { formatRelativeTime } from './formatRelativeTime'\nimport { jobStatusMeta } from './jobStatusMeta'\n\nconst DEFAULT_DATE_FORMAT = 'MMMM do yyyy, h:mm a'\n\nconst Fact = ({ label, tooltip, value }: { label: string; tooltip?: string; value: string }) => {\n\tconst [coords, setCoords] = useState<{ left: number; top: number } | null>(null)\n\tconst [mounted, setMounted] = useState(false)\n\tuseEffect(() => setMounted(true), [])\n\n\treturn (\n\t\t<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>\n\t\t\t<span\n\t\t\t\tstyle={{\n\t\t\t\t\tcolor: 'var(--theme-elevation-500)',\n\t\t\t\t\tfontSize: '0.6875rem',\n\t\t\t\t\tletterSpacing: '0.04em',\n\t\t\t\t\ttextTransform: 'uppercase',\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{label}\n\t\t\t</span>\n\t\t\t{/* biome-ignore lint/a11y/noStaticElementInteractions: supplementary hover tooltip on a read-only text value */}\n\t\t\t<span\n\t\t\t\tonMouseEnter={(event) => {\n\t\t\t\t\tif (!tooltip) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst rect = event.currentTarget.getBoundingClientRect()\n\t\t\t\t\tsetCoords({ left: rect.left, top: rect.bottom })\n\t\t\t\t}}\n\t\t\t\tonMouseLeave={() => setCoords(null)}\n\t\t\t\tstyle={{ cursor: tooltip ? 'help' : undefined, fontWeight: 600, width: 'fit-content' }}\n\t\t\t>\n\t\t\t\t{value}\n\t\t\t</span>\n\t\t\t{mounted && tooltip && coords\n\t\t\t\t? createPortal(\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tbackgroundColor: 'var(--theme-elevation-900)',\n\t\t\t\t\t\t\t\tborderRadius: '4px',\n\t\t\t\t\t\t\t\tboxShadow: '0 2px 8px rgba(0, 0, 0, 0.25)',\n\t\t\t\t\t\t\t\tcolor: 'var(--theme-elevation-0)',\n\t\t\t\t\t\t\t\tfontSize: '0.75rem',\n\t\t\t\t\t\t\t\tleft: `${coords.left}px`,\n\t\t\t\t\t\t\t\tpadding: '4px 8px',\n\t\t\t\t\t\t\t\tpointerEvents: 'none',\n\t\t\t\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\t\t\t\ttop: `${coords.top + 6}px`,\n\t\t\t\t\t\t\t\twhiteSpace: 'nowrap',\n\t\t\t\t\t\t\t\tzIndex: 9999,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{tooltip}\n\t\t\t\t\t\t</span>,\n\t\t\t\t\t\tdocument.body\n\t\t\t\t\t)\n\t\t\t\t: null}\n\t\t</div>\n\t)\n}\n\n/**\n * Document header for a job: the derived status as a Pill plus the key facts,\n * read from the form so it stays in sync. Rendered at the top of the edit view.\n */\nexport const JobStatusHeader: UIFieldClientComponent = () => {\n\tconst fields = useFormFields(([formFields]) => formFields)\n\tconst { config } = useConfig()\n\tconst { i18n, t } = useTranslation()\n\tconst get = (name: string): unknown => fields?.[name]?.value\n\n\tconst status = deriveJobStatus({\n\t\tcompletedAt: get('completedAt') as null | string,\n\t\terror: get('error'),\n\t\thasError: get('hasError') as boolean,\n\t\tprocessing: get('processing') as boolean,\n\t\ttotalTried: get('totalTried') as number,\n\t\twaitUntil: get('waitUntil') as null | string,\n\t})\n\tconst meta = jobStatusMeta[status]\n\n\tconst workflowSlug = get('workflowSlug')\n\tconst taskSlug = get('taskSlug')\n\tconst runsLabel = workflowSlug\n\t\t? t(keys.fieldWorkflow)\n\t\t: taskSlug\n\t\t\t? t(keys.fieldTask)\n\t\t\t: t(keys.fieldJob)\n\tconst runsValue = String(workflowSlug || taskSlug || '—')\n\n\tconst pattern = config.admin?.dateFormat ?? DEFAULT_DATE_FORMAT\n\tconst createdAt = get('createdAt') as null | string\n\tconst completedAt = get('completedAt') as null | string\n\tconst absolute = (date: null | string): string | undefined =>\n\t\tdate ? formatDate({ date, i18n, pattern }) : undefined\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\talignItems: 'flex-end',\n\t\t\t\tborderBottom: '1px solid var(--theme-elevation-100)',\n\t\t\t\tcolumnGap: 'calc(var(--base) * 1.5)',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tflexWrap: 'wrap',\n\t\t\t\tmarginBottom: 'var(--base)',\n\t\t\t\tpaddingBottom: 'var(--base)',\n\t\t\t\trowGap: 'var(--base)',\n\t\t\t}}\n\t\t>\n\t\t\t<Pill pillStyle={meta.pillStyle} size=\"medium\">\n\t\t\t\t{t(meta.labelKey)}\n\t\t\t</Pill>\n\t\t\t<Fact label={runsLabel} value={runsValue} />\n\t\t\t<Fact label={t(keys.fieldQueue)} value={String(get('queue') || 'default')} />\n\t\t\t<Fact label={t(keys.fieldAttempts)} value={String((get('totalTried') as number) ?? 0)} />\n\t\t\t<Fact\n\t\t\t\tlabel={t(keys.fieldCreated)}\n\t\t\t\ttooltip={absolute(createdAt)}\n\t\t\t\tvalue={formatRelativeTime(createdAt) || '—'}\n\t\t\t/>\n\t\t\t<Fact\n\t\t\t\tlabel={t(keys.fieldCompleted)}\n\t\t\t\ttooltip={absolute(completedAt)}\n\t\t\t\tvalue={completedAt ? formatRelativeTime(completedAt) : '—'}\n\t\t\t/>\n\t\t</div>\n\t)\n}\n","'use client'\n\nimport { useConfig } from '@payloadcms/ui'\nimport { formatDate } from '@payloadcms/ui/shared'\nimport type { DefaultCellComponentProps } from 'payload'\n\nimport { useTranslation } from '../translations/useTranslation'\nimport { formatRelativeTime } from './formatRelativeTime'\n\nconst DEFAULT_DATE_FORMAT = 'MMMM do yyyy, h:mm a'\n\n/** List cell for date fields: relative time, with the admin-formatted timestamp on hover. */\nexport const RelativeTimeCell = ({ cellData }: DefaultCellComponentProps) => {\n\tconst { config } = useConfig()\n\tconst { i18n } = useTranslation()\n\n\tif (cellData === null || cellData === undefined) {\n\t\treturn null\n\t}\n\tconst relative = formatRelativeTime(cellData)\n\tif (!relative) {\n\t\treturn null\n\t}\n\tconst exact = formatDate({\n\t\tdate: cellData,\n\t\ti18n,\n\t\tpattern: config.admin?.dateFormat ?? DEFAULT_DATE_FORMAT,\n\t})\n\treturn <span title={exact}>{relative}</span>\n}\n"],"mappings":"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"}