@arqel-dev/ui 0.16.0 → 0.17.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.
- package/dist/ArqelEditPage-7OFWPULE.js +12 -0
- package/dist/{ArqelEditPage-RRYFYOTC.js.map → ArqelEditPage-7OFWPULE.js.map} +1 -1
- package/dist/ArqelIndexPage-ZAVCCM6V.js +14 -0
- package/dist/{ArqelIndexPage-J2EUV2PI.js.map → ArqelIndexPage-ZAVCCM6V.js.map} +1 -1
- package/dist/action.js +2 -1
- package/dist/{chunk-SK6H7ARE.js → chunk-3E66JNQO.js} +3 -18
- package/dist/chunk-3E66JNQO.js.map +1 -0
- package/dist/chunk-53J74IYR.js +22 -0
- package/dist/chunk-53J74IYR.js.map +1 -0
- package/dist/{chunk-SLLTFOOS.js → chunk-7NGUPQON.js} +5 -192
- package/dist/chunk-7NGUPQON.js.map +1 -0
- package/dist/chunk-DO3PRGVV.js +198 -0
- package/dist/chunk-DO3PRGVV.js.map +1 -0
- package/dist/{chunk-X4ZFKOPX.js → chunk-DOQUHQND.js} +4 -4
- package/dist/{chunk-X4ZFKOPX.js.map → chunk-DOQUHQND.js.map} +1 -1
- package/dist/{chunk-S5UNPCMQ.js → chunk-JKD6SE75.js} +3 -269
- package/dist/chunk-JKD6SE75.js.map +1 -0
- package/dist/{chunk-EG2DASKR.js → chunk-KGAY2AZH.js} +4 -3
- package/dist/chunk-KGAY2AZH.js.map +1 -0
- package/dist/chunk-WKIYS2FQ.js +509 -0
- package/dist/chunk-WKIYS2FQ.js.map +1 -0
- package/dist/chunk-YEPQWAD6.js +275 -0
- package/dist/chunk-YEPQWAD6.js.map +1 -0
- package/dist/index.js +7 -4
- package/dist/pages.js +10 -7
- package/dist/pages.js.map +1 -1
- package/dist/resource.js +3 -2
- package/dist/table.js +2 -1
- package/dist/utility.js +2 -1
- package/package.json +6 -6
- package/dist/ArqelEditPage-RRYFYOTC.js +0 -8
- package/dist/ArqelIndexPage-J2EUV2PI.js +0 -12
- package/dist/chunk-EG2DASKR.js.map +0 -1
- package/dist/chunk-S5UNPCMQ.js.map +0 -1
- package/dist/chunk-SK6H7ARE.js.map +0 -1
- package/dist/chunk-SLLTFOOS.js.map +0 -1
- package/dist/chunk-YQ6KXR3U.js +0 -55
- package/dist/chunk-YQ6KXR3U.js.map +0 -1
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { ErrorState } from './chunk-53J74IYR.js';
|
|
2
|
+
import { PageHeader } from './chunk-MXUXMHOK.js';
|
|
3
|
+
import { DataTable } from './chunk-JKD6SE75.js';
|
|
4
|
+
import { ConfirmDialog, Dialog, DialogContent, DialogHeader, DialogTitle } from './chunk-DO3PRGVV.js';
|
|
5
|
+
import { FormActions } from './chunk-MZMYV43C.js';
|
|
6
|
+
import { FormRenderer } from './chunk-CMNZLA32.js';
|
|
7
|
+
import { Button } from './chunk-H23VX3RR.js';
|
|
8
|
+
import { cn } from './chunk-B7ULUJVO.js';
|
|
9
|
+
import { useArqelForm } from '@arqel-dev/hooks';
|
|
10
|
+
import { useArqelTranslations } from '@arqel-dev/react/utils';
|
|
11
|
+
import { usePage, router } from '@inertiajs/react';
|
|
12
|
+
import { useState, useEffect, useMemo, useId } from 'react';
|
|
13
|
+
import { Tabs as Tabs$1 } from 'radix-ui';
|
|
14
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
15
|
+
|
|
16
|
+
function Tabs({
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}) {
|
|
20
|
+
return /* @__PURE__ */ jsx(
|
|
21
|
+
Tabs$1.Root,
|
|
22
|
+
{
|
|
23
|
+
"data-slot": "tabs",
|
|
24
|
+
className: cn("flex flex-col gap-2", className),
|
|
25
|
+
...props
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function TabsList({
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}) {
|
|
33
|
+
return /* @__PURE__ */ jsx(
|
|
34
|
+
Tabs$1.List,
|
|
35
|
+
{
|
|
36
|
+
"data-slot": "tabs-list",
|
|
37
|
+
className: cn(
|
|
38
|
+
"inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground",
|
|
39
|
+
className
|
|
40
|
+
),
|
|
41
|
+
...props
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function TabsTrigger({
|
|
46
|
+
className,
|
|
47
|
+
...props
|
|
48
|
+
}) {
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
Tabs$1.Trigger,
|
|
51
|
+
{
|
|
52
|
+
"data-slot": "tabs-trigger",
|
|
53
|
+
className: cn(
|
|
54
|
+
"inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:shadow-sm dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
55
|
+
className
|
|
56
|
+
),
|
|
57
|
+
...props
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function TabsContent({
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
}) {
|
|
65
|
+
return /* @__PURE__ */ jsx(
|
|
66
|
+
Tabs$1.Content,
|
|
67
|
+
{
|
|
68
|
+
"data-slot": "tabs-content",
|
|
69
|
+
className: cn("flex-1 outline-none", className),
|
|
70
|
+
...props
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
function AttachModal({
|
|
75
|
+
open,
|
|
76
|
+
onClose,
|
|
77
|
+
relation,
|
|
78
|
+
parentSlug,
|
|
79
|
+
parentId,
|
|
80
|
+
basePath = "/admin",
|
|
81
|
+
onSuccess
|
|
82
|
+
}) {
|
|
83
|
+
const t = useArqelTranslations();
|
|
84
|
+
const inputId = useId();
|
|
85
|
+
const [related, setRelated] = useState("");
|
|
86
|
+
const [processing, setProcessing] = useState(false);
|
|
87
|
+
const [errors, setErrors] = useState({});
|
|
88
|
+
if (!open) return null;
|
|
89
|
+
const url = `${basePath}/${parentSlug}/${parentId}/relations/${relation.slug}/attach`;
|
|
90
|
+
const submit = () => {
|
|
91
|
+
setProcessing(true);
|
|
92
|
+
router.post(
|
|
93
|
+
url,
|
|
94
|
+
{ related, pivot: {} },
|
|
95
|
+
{
|
|
96
|
+
preserveScroll: true,
|
|
97
|
+
onSuccess: () => {
|
|
98
|
+
setProcessing(false);
|
|
99
|
+
onSuccess?.();
|
|
100
|
+
onClose();
|
|
101
|
+
},
|
|
102
|
+
onError: (formErrors) => {
|
|
103
|
+
setProcessing(false);
|
|
104
|
+
setErrors(formErrors);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "overflow-auto sm:max-w-[28rem]", children: [
|
|
110
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("arqel.relations.attach", "Attach :label", { label: relation.label }) }) }),
|
|
111
|
+
/* @__PURE__ */ jsxs(
|
|
112
|
+
"form",
|
|
113
|
+
{
|
|
114
|
+
className: "flex flex-col gap-4",
|
|
115
|
+
onSubmit: (e) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
submit();
|
|
118
|
+
},
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
121
|
+
/* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "text-sm font-medium", children: relation.label }),
|
|
122
|
+
/* @__PURE__ */ jsx(
|
|
123
|
+
"input",
|
|
124
|
+
{
|
|
125
|
+
id: inputId,
|
|
126
|
+
type: "text",
|
|
127
|
+
className: "rounded-sm border border-border bg-background px-3 py-1.5 text-sm",
|
|
128
|
+
value: related,
|
|
129
|
+
disabled: processing,
|
|
130
|
+
"aria-invalid": errors["related"] !== void 0 || void 0,
|
|
131
|
+
onChange: (e) => setRelated(e.target.value)
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
errors["related"] && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errors["related"] })
|
|
135
|
+
] }),
|
|
136
|
+
/* @__PURE__ */ jsx(
|
|
137
|
+
FormActions,
|
|
138
|
+
{
|
|
139
|
+
processing,
|
|
140
|
+
submitLabel: t("arqel.relations.attach_action", "Attach"),
|
|
141
|
+
onCancel: onClose
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
] }) });
|
|
148
|
+
}
|
|
149
|
+
function RelationFormModal({
|
|
150
|
+
open,
|
|
151
|
+
onClose,
|
|
152
|
+
relation,
|
|
153
|
+
parentSlug,
|
|
154
|
+
parentId,
|
|
155
|
+
basePath = "/admin",
|
|
156
|
+
recordId,
|
|
157
|
+
initialValues = {},
|
|
158
|
+
onSuccess
|
|
159
|
+
}) {
|
|
160
|
+
const t = useArqelTranslations();
|
|
161
|
+
const [values, setValues] = useState(initialValues);
|
|
162
|
+
const [errors, setErrors] = useState({});
|
|
163
|
+
const [processing, setProcessing] = useState(false);
|
|
164
|
+
const fields = relation.fields;
|
|
165
|
+
const schema = useMemo(
|
|
166
|
+
() => ({
|
|
167
|
+
schema: fields.map((field) => ({
|
|
168
|
+
kind: "field",
|
|
169
|
+
name: field.name,
|
|
170
|
+
type: field.type
|
|
171
|
+
})),
|
|
172
|
+
columns: 1,
|
|
173
|
+
model: null,
|
|
174
|
+
inline: false,
|
|
175
|
+
disabled: false
|
|
176
|
+
}),
|
|
177
|
+
[fields]
|
|
178
|
+
);
|
|
179
|
+
if (!open) return null;
|
|
180
|
+
const base = `${basePath}/${parentSlug}/${parentId}/relations/${relation.slug}`;
|
|
181
|
+
const url = recordId ? `${base}/${recordId}` : base;
|
|
182
|
+
const method = recordId ? "put" : "post";
|
|
183
|
+
const submit = () => {
|
|
184
|
+
setProcessing(true);
|
|
185
|
+
router[method](url, values, {
|
|
186
|
+
preserveScroll: true,
|
|
187
|
+
onSuccess: () => {
|
|
188
|
+
setProcessing(false);
|
|
189
|
+
onSuccess?.();
|
|
190
|
+
onClose();
|
|
191
|
+
},
|
|
192
|
+
onError: (formErrors) => {
|
|
193
|
+
setProcessing(false);
|
|
194
|
+
setErrors(
|
|
195
|
+
Object.fromEntries(Object.entries(formErrors).map(([key, message]) => [key, [message]]))
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const formRendererProps = {
|
|
201
|
+
schema,
|
|
202
|
+
fields,
|
|
203
|
+
values,
|
|
204
|
+
errors,
|
|
205
|
+
onChange: (name, value) => setValues((prev) => ({ ...prev, [name]: value })),
|
|
206
|
+
disabled: processing,
|
|
207
|
+
onSubmit: submit
|
|
208
|
+
};
|
|
209
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "overflow-auto sm:max-w-[32rem]", children: [
|
|
210
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: relation.label }) }),
|
|
211
|
+
/* @__PURE__ */ jsxs(
|
|
212
|
+
"form",
|
|
213
|
+
{
|
|
214
|
+
className: "flex flex-col gap-4",
|
|
215
|
+
onSubmit: (e) => {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
submit();
|
|
218
|
+
},
|
|
219
|
+
children: [
|
|
220
|
+
/* @__PURE__ */ jsx(FormRenderer, { ...formRendererProps }),
|
|
221
|
+
/* @__PURE__ */ jsx(
|
|
222
|
+
FormActions,
|
|
223
|
+
{
|
|
224
|
+
processing,
|
|
225
|
+
submitLabel: t("form.save", "Save"),
|
|
226
|
+
onCancel: onClose
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
] }) });
|
|
233
|
+
}
|
|
234
|
+
function relationColumns(table) {
|
|
235
|
+
if (table && typeof table === "object" && Array.isArray(table.columns)) {
|
|
236
|
+
return table.columns;
|
|
237
|
+
}
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
function RelationManagerPanel({
|
|
241
|
+
relation,
|
|
242
|
+
parentSlug,
|
|
243
|
+
parentId,
|
|
244
|
+
records: initialRecords,
|
|
245
|
+
basePath = "/admin",
|
|
246
|
+
refreshKey,
|
|
247
|
+
fetcher,
|
|
248
|
+
onEdit,
|
|
249
|
+
onCreate,
|
|
250
|
+
onAttach,
|
|
251
|
+
onMutated
|
|
252
|
+
}) {
|
|
253
|
+
const { abilities, type, slug } = relation;
|
|
254
|
+
const columns = relationColumns(relation.table);
|
|
255
|
+
const [records, setRecords] = useState(initialRecords);
|
|
256
|
+
const [loading, setLoading] = useState(false);
|
|
257
|
+
const [error, setError] = useState(false);
|
|
258
|
+
const [pendingAction, setPendingAction] = useState(null);
|
|
259
|
+
const [mutating, setMutating] = useState(false);
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
let cancelled = false;
|
|
262
|
+
const url = `${basePath}/${parentSlug}/${parentId}/relations/${slug}`;
|
|
263
|
+
const doFetch = async () => {
|
|
264
|
+
setLoading(true);
|
|
265
|
+
try {
|
|
266
|
+
const body = fetcher ? await fetcher(url) : await fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
|
|
267
|
+
const fetched = body && typeof body === "object" && Array.isArray(body.records) ? body.records : void 0;
|
|
268
|
+
if (!cancelled && fetched) {
|
|
269
|
+
setRecords(fetched);
|
|
270
|
+
setError(false);
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
if (!cancelled) setError(true);
|
|
274
|
+
} finally {
|
|
275
|
+
if (!cancelled) setLoading(false);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
void doFetch();
|
|
279
|
+
return () => {
|
|
280
|
+
cancelled = true;
|
|
281
|
+
};
|
|
282
|
+
}, [basePath, parentSlug, parentId, slug, refreshKey, fetcher]);
|
|
283
|
+
const canDelete = type !== "belongsToMany" && abilities.delete;
|
|
284
|
+
const canDetach = type === "belongsToMany" && abilities.detach;
|
|
285
|
+
const mutationUrl = (id, kind) => {
|
|
286
|
+
const base = `${basePath}/${parentSlug}/${parentId}/relations/${slug}/${id}`;
|
|
287
|
+
return kind === "detach" ? `${base}/detach` : base;
|
|
288
|
+
};
|
|
289
|
+
const confirmMutation = () => {
|
|
290
|
+
if (!pendingAction) return;
|
|
291
|
+
const { kind, id } = pendingAction;
|
|
292
|
+
setMutating(true);
|
|
293
|
+
router.delete(mutationUrl(id, kind), {
|
|
294
|
+
preserveScroll: true,
|
|
295
|
+
onSuccess: () => {
|
|
296
|
+
setMutating(false);
|
|
297
|
+
setPendingAction(null);
|
|
298
|
+
onMutated?.();
|
|
299
|
+
},
|
|
300
|
+
onError: () => {
|
|
301
|
+
setMutating(false);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
306
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
307
|
+
abilities.create && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: onCreate, children: "New" }),
|
|
308
|
+
type === "belongsToMany" && abilities.attach && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "secondary", onClick: onAttach, children: "Attach" })
|
|
309
|
+
] }),
|
|
310
|
+
error && records.length === 0 ? /* @__PURE__ */ jsx(ErrorState, { title: "Couldn't load records", description: "Please try again." }) : /* @__PURE__ */ jsx(
|
|
311
|
+
DataTable,
|
|
312
|
+
{
|
|
313
|
+
columns,
|
|
314
|
+
records,
|
|
315
|
+
loading,
|
|
316
|
+
rowActions: abilities.update || canDelete || canDetach ? (record) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
317
|
+
abilities.update && /* @__PURE__ */ jsx(
|
|
318
|
+
Button,
|
|
319
|
+
{
|
|
320
|
+
type: "button",
|
|
321
|
+
size: "sm",
|
|
322
|
+
variant: "ghost",
|
|
323
|
+
onClick: () => onEdit(record.id),
|
|
324
|
+
children: "Edit"
|
|
325
|
+
}
|
|
326
|
+
),
|
|
327
|
+
canDelete && /* @__PURE__ */ jsx(
|
|
328
|
+
Button,
|
|
329
|
+
{
|
|
330
|
+
type: "button",
|
|
331
|
+
size: "sm",
|
|
332
|
+
variant: "ghost",
|
|
333
|
+
onClick: () => setPendingAction({ kind: "delete", id: record.id }),
|
|
334
|
+
children: "Delete"
|
|
335
|
+
}
|
|
336
|
+
),
|
|
337
|
+
canDetach && /* @__PURE__ */ jsx(
|
|
338
|
+
Button,
|
|
339
|
+
{
|
|
340
|
+
type: "button",
|
|
341
|
+
size: "sm",
|
|
342
|
+
variant: "ghost",
|
|
343
|
+
onClick: () => setPendingAction({ kind: "detach", id: record.id }),
|
|
344
|
+
children: "Detach"
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
] }) : void 0
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
/* @__PURE__ */ jsx(
|
|
351
|
+
ConfirmDialog,
|
|
352
|
+
{
|
|
353
|
+
open: pendingAction !== null,
|
|
354
|
+
onOpenChange: (next) => {
|
|
355
|
+
if (!next) setPendingAction(null);
|
|
356
|
+
},
|
|
357
|
+
onConfirm: confirmMutation,
|
|
358
|
+
processing: mutating,
|
|
359
|
+
config: pendingAction?.kind === "detach" ? {
|
|
360
|
+
heading: "Detach this record?",
|
|
361
|
+
color: "destructive",
|
|
362
|
+
submitLabel: "Detach"
|
|
363
|
+
} : {
|
|
364
|
+
heading: "Are you sure?",
|
|
365
|
+
color: "destructive",
|
|
366
|
+
submitLabel: "Delete"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
] });
|
|
371
|
+
}
|
|
372
|
+
function initialTab(relations) {
|
|
373
|
+
const fromQuery = new URLSearchParams(window.location.search).get("tab");
|
|
374
|
+
if (fromQuery && (fromQuery === "data" || relations.some((r) => r.slug === fromQuery))) {
|
|
375
|
+
return fromQuery;
|
|
376
|
+
}
|
|
377
|
+
return "data";
|
|
378
|
+
}
|
|
379
|
+
function ResourceEditTabs({
|
|
380
|
+
relations,
|
|
381
|
+
parentSlug,
|
|
382
|
+
parentId,
|
|
383
|
+
basePath = "/admin",
|
|
384
|
+
children
|
|
385
|
+
}) {
|
|
386
|
+
const [tab, setTab] = useState(() => initialTab(relations));
|
|
387
|
+
const [modal, setModal] = useState(null);
|
|
388
|
+
const [attachSlug, setAttachSlug] = useState(null);
|
|
389
|
+
const [refreshKeys, setRefreshKeys] = useState({});
|
|
390
|
+
const bumpRefresh = (slug) => setRefreshKeys((prev) => ({ ...prev, [slug]: (prev[slug] ?? 0) + 1 }));
|
|
391
|
+
if (relations.length === 0) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
392
|
+
const setActiveTab = (next) => {
|
|
393
|
+
setTab(next);
|
|
394
|
+
const url = new URL(window.location.href);
|
|
395
|
+
if (next === "data") {
|
|
396
|
+
url.searchParams.delete("tab");
|
|
397
|
+
} else {
|
|
398
|
+
url.searchParams.set("tab", next);
|
|
399
|
+
}
|
|
400
|
+
window.history.replaceState(window.history.state, "", url);
|
|
401
|
+
};
|
|
402
|
+
const activeRelation = relations.find((r) => r.slug === modal?.slug);
|
|
403
|
+
const attachRelation = relations.find((r) => r.slug === attachSlug);
|
|
404
|
+
return /* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: setActiveTab, children: [
|
|
405
|
+
/* @__PURE__ */ jsxs(TabsList, { children: [
|
|
406
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "data", children: "Data" }),
|
|
407
|
+
relations.map((relation) => /* @__PURE__ */ jsx(TabsTrigger, { value: relation.slug, children: relation.label }, relation.slug))
|
|
408
|
+
] }),
|
|
409
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "data", children }),
|
|
410
|
+
relations.map((relation) => /* @__PURE__ */ jsx(TabsContent, { value: relation.slug, children: /* @__PURE__ */ jsx(
|
|
411
|
+
RelationManagerPanel,
|
|
412
|
+
{
|
|
413
|
+
relation,
|
|
414
|
+
parentSlug,
|
|
415
|
+
parentId,
|
|
416
|
+
basePath,
|
|
417
|
+
records: [],
|
|
418
|
+
refreshKey: refreshKeys[relation.slug] ?? 0,
|
|
419
|
+
onCreate: () => setModal({ slug: relation.slug }),
|
|
420
|
+
onEdit: (id) => setModal({ slug: relation.slug, recordId: id }),
|
|
421
|
+
onAttach: () => setAttachSlug(relation.slug),
|
|
422
|
+
onMutated: () => bumpRefresh(relation.slug)
|
|
423
|
+
}
|
|
424
|
+
) }, relation.slug)),
|
|
425
|
+
modal && activeRelation && /* @__PURE__ */ jsx(
|
|
426
|
+
RelationFormModal,
|
|
427
|
+
{
|
|
428
|
+
open: true,
|
|
429
|
+
onClose: () => setModal(null),
|
|
430
|
+
relation: activeRelation,
|
|
431
|
+
parentSlug,
|
|
432
|
+
parentId,
|
|
433
|
+
basePath,
|
|
434
|
+
onSuccess: () => bumpRefresh(activeRelation.slug),
|
|
435
|
+
...modal.recordId !== void 0 ? { recordId: modal.recordId } : {}
|
|
436
|
+
}
|
|
437
|
+
),
|
|
438
|
+
attachSlug && attachRelation && /* @__PURE__ */ jsx(
|
|
439
|
+
AttachModal,
|
|
440
|
+
{
|
|
441
|
+
open: true,
|
|
442
|
+
onClose: () => setAttachSlug(null),
|
|
443
|
+
relation: attachRelation,
|
|
444
|
+
parentSlug,
|
|
445
|
+
parentId,
|
|
446
|
+
basePath,
|
|
447
|
+
onSuccess: () => bumpRefresh(attachRelation.slug)
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
] });
|
|
451
|
+
}
|
|
452
|
+
var FALLBACK_SCHEMA = {
|
|
453
|
+
schema: [],
|
|
454
|
+
columns: 1,
|
|
455
|
+
model: null,
|
|
456
|
+
inline: false,
|
|
457
|
+
disabled: false
|
|
458
|
+
};
|
|
459
|
+
function ArqelEditPage() {
|
|
460
|
+
const page = usePage();
|
|
461
|
+
const t = useArqelTranslations();
|
|
462
|
+
const props = page.props;
|
|
463
|
+
const fields = props.fields ?? [];
|
|
464
|
+
const record = props.record;
|
|
465
|
+
const schema = props.form ?? FALLBACK_SCHEMA;
|
|
466
|
+
const relations = props.relations ?? [];
|
|
467
|
+
const form = useArqelForm({ fields, record });
|
|
468
|
+
const submit = (e) => {
|
|
469
|
+
e.preventDefault();
|
|
470
|
+
const id = String(record?.id ?? "");
|
|
471
|
+
const slug = props.resource?.slug ?? "";
|
|
472
|
+
const basePath = props.resource?.panelPath ?? "/admin";
|
|
473
|
+
router.put(`${basePath}/${slug}/${id}`, form.data, {
|
|
474
|
+
preserveScroll: true
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
const label = props.resource?.label ?? t("arqel.pages.fallback", "record");
|
|
478
|
+
const editTitle = props.recordTitle ?? t("arqel.pages.edit", `Edit ${label}`, { label });
|
|
479
|
+
const formContent = /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-6", children: [
|
|
480
|
+
/* @__PURE__ */ jsx(
|
|
481
|
+
FormRenderer,
|
|
482
|
+
{
|
|
483
|
+
schema,
|
|
484
|
+
fields,
|
|
485
|
+
values: form.data,
|
|
486
|
+
onChange: (name, value) => form.setData(name, value),
|
|
487
|
+
errors: form.errors
|
|
488
|
+
}
|
|
489
|
+
),
|
|
490
|
+
/* @__PURE__ */ jsx(FormActions, { processing: form.processing, onCancel: () => window.history.back() })
|
|
491
|
+
] });
|
|
492
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
493
|
+
/* @__PURE__ */ jsx(PageHeader, { title: editTitle, description: props.recordSubtitle ?? null }),
|
|
494
|
+
/* @__PURE__ */ jsx(
|
|
495
|
+
ResourceEditTabs,
|
|
496
|
+
{
|
|
497
|
+
relations,
|
|
498
|
+
parentSlug: props.resource?.slug ?? "",
|
|
499
|
+
parentId: record?.id ?? "",
|
|
500
|
+
basePath: props.resource?.panelPath ?? "/admin",
|
|
501
|
+
children: formContent
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
] });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export { ArqelEditPage };
|
|
508
|
+
//# sourceMappingURL=chunk-WKIYS2FQ.js.map
|
|
509
|
+
//# sourceMappingURL=chunk-WKIYS2FQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shadcn/ui/tabs.tsx","../src/relations/AttachModal.tsx","../src/relations/RelationFormModal.tsx","../src/relations/RelationManagerPanel.tsx","../src/relations/ResourceEditTabs.tsx","../src/pages/ArqelEditPage.tsx"],"names":["TabsPrimitive","jsx","useArqelTranslations","useState","router","jsxs","Fragment"],"mappings":"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","file":"chunk-WKIYS2FQ.js","sourcesContent":["import * as React from \"react\"\nimport { Tabs as TabsPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/utils/cn\"\n\nfunction Tabs({\n className,\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.Root>) {\n return (\n <TabsPrimitive.Root\n data-slot=\"tabs\"\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n />\n )\n}\n\nfunction TabsList({\n className,\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.List>) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n className={cn(\n \"inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction TabsTrigger({\n className,\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n return (\n <TabsPrimitive.Trigger\n data-slot=\"tabs-trigger\"\n className={cn(\n \"inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:shadow-sm dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction TabsContent({\n className,\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n return (\n <TabsPrimitive.Content\n data-slot=\"tabs-content\"\n className={cn(\"flex-1 outline-none\", className)}\n {...props}\n />\n )\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n","/**\n * `<AttachModal>` — attach an existing record to a `belongsToMany`\n * relation from a `RelationManagerPanel` tab.\n *\n * Phase 1 ships a plain id-entry picker rather than reusing\n * `@arqel-dev/fields`' `BelongsToInput`: that component lives in\n * `arqel-dev/fields`, which depends on `@arqel-dev/ui` (registers into its\n * `FieldRegistry`) — importing it back here would be a circular package\n * dependency. `RelationManager::toArray()` also doesn't yet emit a search\n * route for the attach picker (only `RelationController::attach()`'s\n * `{ related, pivot }` contract), so there's nothing to debounce-search\n * against yet. A follow-up ticket can upgrade this to a search combobox\n * once the server side emits `relation.attachSearchRoute` (or similar).\n *\n * On success the modal closes and calls `onSuccess` (if given) so the\n * caller can refresh the relation's table — see `RelationFormModal`'s\n * docblock for why a partial Inertia reload no longer applies (Task 13a).\n */\n\nimport { useArqelTranslations } from '@arqel-dev/react/utils';\nimport type { RelationManagerProps } from '@arqel-dev/types/relations';\nimport { router } from '@inertiajs/react';\nimport { useId, useState } from 'react';\nimport { FormActions } from '../form/FormActions.js';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../shadcn/ui/dialog.js';\n\nexport interface AttachModalProps {\n open: boolean;\n onClose(): void;\n relation: RelationManagerProps;\n parentSlug: string;\n parentId: string | number;\n basePath?: string;\n onSuccess?: () => void;\n}\n\nexport function AttachModal({\n open,\n onClose,\n relation,\n parentSlug,\n parentId,\n basePath = '/admin',\n onSuccess,\n}: AttachModalProps) {\n const t = useArqelTranslations();\n const inputId = useId();\n const [related, setRelated] = useState('');\n const [processing, setProcessing] = useState(false);\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n if (!open) return null;\n\n const url = `${basePath}/${parentSlug}/${parentId}/relations/${relation.slug}/attach`;\n\n const submit = () => {\n setProcessing(true);\n router.post(\n url,\n { related, pivot: {} },\n {\n preserveScroll: true,\n onSuccess: () => {\n setProcessing(false);\n onSuccess?.();\n onClose();\n },\n onError: (formErrors: Record<string, string>) => {\n setProcessing(false);\n setErrors(formErrors);\n },\n },\n );\n };\n\n return (\n <Dialog open={open} onOpenChange={(next) => !next && onClose()}>\n <DialogContent className=\"overflow-auto sm:max-w-[28rem]\">\n <DialogHeader>\n <DialogTitle>\n {t('arqel.relations.attach', 'Attach :label', { label: relation.label })}\n </DialogTitle>\n </DialogHeader>\n <form\n className=\"flex flex-col gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n <div className=\"flex flex-col gap-1\">\n <label htmlFor={inputId} className=\"text-sm font-medium\">\n {relation.label}\n </label>\n <input\n id={inputId}\n type=\"text\"\n className=\"rounded-sm border border-border bg-background px-3 py-1.5 text-sm\"\n value={related}\n disabled={processing}\n aria-invalid={errors['related'] !== undefined || undefined}\n onChange={(e) => setRelated(e.target.value)}\n />\n {errors['related'] && <p className=\"text-sm text-destructive\">{errors['related']}</p>}\n </div>\n <FormActions\n processing={processing}\n submitLabel={t('arqel.relations.attach_action', 'Attach')}\n onCancel={onClose}\n />\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n","/**\n * `<RelationFormModal>` — create/edit a related record from within a\n * `RelationManagerPanel` tab. Reuses `<FormRenderer>` for the field list\n * (a flat schema built from `relation.fields`, mirroring how\n * `<ActionFormModal>` derives a `FormSchema` from a flat `ActionFormField[]`)\n * and submits via Inertia `router.post`/`router.put`.\n *\n * On success the modal closes and calls `onSuccess` (if given) so the\n * caller can refresh the relation's table — records now come from\n * `RelationManagerPanel`'s own `fetch()` to `RelationController::index()`\n * (Task 13a), not from an Inertia `relations` prop, so a partial reload\n * would no longer do anything useful here; `ResourceEditTabs` uses\n * `onSuccess` to bump that panel's `refreshKey`.\n */\n\nimport { useArqelTranslations } from '@arqel-dev/react/utils';\nimport type { FieldSchema } from '@arqel-dev/types/fields';\nimport type { FormSchema } from '@arqel-dev/types/forms';\nimport type { RelationManagerProps } from '@arqel-dev/types/relations';\nimport { router } from '@inertiajs/react';\nimport { useMemo, useState } from 'react';\nimport { FormActions } from '../form/FormActions.js';\nimport { FormRenderer, type FormRendererProps } from '../form/FormRenderer.js';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../shadcn/ui/dialog.js';\n\nexport interface RelationFormModalProps {\n open: boolean;\n onClose(): void;\n relation: RelationManagerProps;\n parentSlug: string;\n parentId: string | number;\n basePath?: string;\n recordId?: string | number;\n initialValues?: Record<string, unknown>;\n onSuccess?: () => void;\n}\n\nexport function RelationFormModal({\n open,\n onClose,\n relation,\n parentSlug,\n parentId,\n basePath = '/admin',\n recordId,\n initialValues = {},\n onSuccess,\n}: RelationFormModalProps) {\n const t = useArqelTranslations();\n const [values, setValues] = useState<Record<string, unknown>>(initialValues);\n const [errors, setErrors] = useState<Record<string, string[]>>({});\n const [processing, setProcessing] = useState(false);\n\n const fields = relation.fields as FieldSchema[];\n\n const schema: FormSchema = useMemo(\n () => ({\n schema: fields.map((field) => ({\n kind: 'field' as const,\n name: field.name,\n type: field.type,\n })),\n columns: 1,\n model: null,\n inline: false,\n disabled: false,\n }),\n [fields],\n );\n\n if (!open) return null;\n\n const base = `${basePath}/${parentSlug}/${parentId}/relations/${relation.slug}`;\n const url = recordId ? `${base}/${recordId}` : base;\n const method = recordId ? 'put' : 'post';\n\n const submit = () => {\n setProcessing(true);\n router[method](url, values as Record<string, never>, {\n preserveScroll: true,\n onSuccess: () => {\n setProcessing(false);\n onSuccess?.();\n onClose();\n },\n onError: (formErrors: Record<string, string>) => {\n setProcessing(false);\n setErrors(\n Object.fromEntries(Object.entries(formErrors).map(([key, message]) => [key, [message]])),\n );\n },\n });\n };\n\n // `FormRenderer` itself is submit-agnostic (state lives in the caller);\n // the wrapping `<form onSubmit>` below drives real submission, mirroring\n // `ArqelEditPage`/`ActionFormModal`. The stray `onSubmit` in this typed\n // props bag is inert for the real component but lets a test double\n // (which renders a plain submit button) exercise the same `submit`.\n const formRendererProps: FormRendererProps & { onSubmit: () => void } = {\n schema,\n fields,\n values,\n errors,\n onChange: (name, value) => setValues((prev) => ({ ...prev, [name]: value })),\n disabled: processing,\n onSubmit: submit,\n };\n\n return (\n <Dialog open={open} onOpenChange={(next) => !next && onClose()}>\n <DialogContent className=\"overflow-auto sm:max-w-[32rem]\">\n <DialogHeader>\n <DialogTitle>{relation.label}</DialogTitle>\n </DialogHeader>\n <form\n className=\"flex flex-col gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n <FormRenderer {...formRendererProps} />\n <FormActions\n processing={processing}\n submitLabel={t('form.save', 'Save')}\n onCancel={onClose}\n />\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n","/**\n * `<RelationManagerPanel>` — one relation-manager tab on a parent's edit\n * page: a `DataTable` of related records with ability-gated toolbar (New /\n * Attach) and per-row Edit/Delete/Detach actions. Create/attach/edit\n * mutations are delegated to the parent page via the callbacks; Delete and\n * Detach are owned by this component directly (Task 13c) since they need\n * no form — just a confirmation and an Inertia `router.delete()` call,\n * mirroring how `ArqelIndexPage`'s row actions confirm via `ConfirmDialog`\n * before dispatching.\n *\n * Delete targets `hasMany`/`morphMany` relations (`abilities.delete`);\n * Detach targets `belongsToMany` (`abilities.detach`) and only removes the\n * pivot row — the related record survives (guaranteed server-side by\n * `RelationController::detach()`). The two are mutually exclusive by\n * `relation.type`, matching the existing New/Attach toolbar gating.\n *\n * `relation.table` is the PHP `Table::toArray()` payload (untyped on the\n * wire per `RelationManagerProps`'s docblock); the column list lives at\n * `relation.table.columns`, mirroring how `ArqelIndexPage`/`ResourceIndex`\n * feed `DataTable` from `ResourceIndexProps.columns`.\n *\n * **Records loading**: `RelationManager::toArray()` deliberately does NOT\n * include the related records (see `RelationManagerProps`'s docblock) — the\n * actual rows live behind `RelationController::index()`\n * (`GET {basePath}/{parentSlug}/{parentId}/relations/{slug}`), a plain JSON\n * endpoint (`{ records, table, abilities }`, not an Inertia partial). This\n * panel is therefore self-fetching: it mirrors `WidgetRenderer`'s\n * `useEffect` + native `fetch()` pattern (see that component's docblock) —\n * fetch on mount, `Accept: application/json`, swallow failures so a bad\n * request just leaves the previous records on screen instead of crashing\n * the tab. The `records` prop is kept as the *initial* seed (SSR-friendly,\n * and it's what the existing unit tests assert against) — the fetch result\n * replaces it once it resolves. `refreshKey` lets a caller force a refetch\n * (e.g. after a successful create/edit/attach/detach mutation) by bumping a\n * counter/string, since those mutations no longer flow through an Inertia\n * partial reload of a `relations` prop.\n */\n\nimport type { RelationManagerProps } from '@arqel-dev/types/relations';\nimport type { ColumnSchema } from '@arqel-dev/types/tables';\nimport { router } from '@inertiajs/react';\nimport { useEffect, useState } from 'react';\nimport { Button } from '../action/Button.js';\nimport { ConfirmDialog } from '../action/ConfirmDialog.js';\nimport { DataTable, type DataTableRecord } from '../table/DataTable.js';\nimport { ErrorState } from '../utility/ErrorState.js';\n\nexport interface RelationManagerPanelProps {\n relation: RelationManagerProps;\n parentSlug: string;\n parentId: string | number;\n records: unknown[];\n basePath?: string;\n /** Bump this (e.g. increment a counter) to force a refetch after a mutation. */\n refreshKey?: string | number;\n /** Test/DI seam — defaults to the native `fetch` API, mirroring `WidgetRenderer`. */\n fetcher?: ((url: string) => Promise<unknown>) | undefined;\n onEdit(id: string | number): void;\n onCreate(): void;\n onAttach(): void;\n /** Called after a Delete or Detach `router.delete()` succeeds, so the caller can bump `refreshKey`. */\n onMutated?: () => void;\n}\n\nfunction relationColumns(table: unknown): ColumnSchema[] {\n if (\n table &&\n typeof table === 'object' &&\n Array.isArray((table as { columns?: unknown }).columns)\n ) {\n return (table as { columns: ColumnSchema[] }).columns;\n }\n return [];\n}\n\nexport function RelationManagerPanel({\n relation,\n parentSlug,\n parentId,\n records: initialRecords,\n basePath = '/admin',\n refreshKey,\n fetcher,\n onEdit,\n onCreate,\n onAttach,\n onMutated,\n}: RelationManagerPanelProps) {\n const { abilities, type, slug } = relation;\n const columns = relationColumns(relation.table);\n\n const [records, setRecords] = useState<unknown[]>(initialRecords);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(false);\n const [pendingAction, setPendingAction] = useState<{\n kind: 'delete' | 'detach';\n id: string | number;\n } | null>(null);\n const [mutating, setMutating] = useState(false);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: refreshKey is a deliberate re-fetch trigger (bumped by the caller post-mutation), not a value the effect reads.\n useEffect(() => {\n let cancelled = false;\n const url = `${basePath}/${parentSlug}/${parentId}/relations/${slug}`;\n\n const doFetch = async () => {\n setLoading(true);\n try {\n const body = fetcher\n ? await fetcher(url)\n : await fetch(url, { headers: { Accept: 'application/json' } }).then((r) => r.json());\n const fetched =\n body && typeof body === 'object' && Array.isArray((body as { records?: unknown }).records)\n ? (body as { records: unknown[] }).records\n : undefined;\n if (!cancelled && fetched) {\n setRecords(fetched);\n setError(false);\n }\n } catch {\n // Swallow — the previous records (initial seed or last successful\n // fetch) stay on screen rather than blanking the tab.\n if (!cancelled) setError(true);\n } finally {\n if (!cancelled) setLoading(false);\n }\n };\n\n void doFetch();\n\n return () => {\n cancelled = true;\n };\n }, [basePath, parentSlug, parentId, slug, refreshKey, fetcher]);\n\n const canDelete = type !== 'belongsToMany' && abilities.delete;\n const canDetach = type === 'belongsToMany' && abilities.detach;\n\n const mutationUrl = (id: string | number, kind: 'delete' | 'detach'): string => {\n const base = `${basePath}/${parentSlug}/${parentId}/relations/${slug}/${id}`;\n return kind === 'detach' ? `${base}/detach` : base;\n };\n\n const confirmMutation = () => {\n if (!pendingAction) return;\n const { kind, id } = pendingAction;\n setMutating(true);\n router.delete(mutationUrl(id, kind), {\n preserveScroll: true,\n onSuccess: () => {\n setMutating(false);\n setPendingAction(null);\n onMutated?.();\n },\n onError: () => {\n setMutating(false);\n },\n });\n };\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-end gap-2\">\n {abilities.create && (\n <Button type=\"button\" size=\"sm\" onClick={onCreate}>\n New\n </Button>\n )}\n {type === 'belongsToMany' && abilities.attach && (\n <Button type=\"button\" size=\"sm\" variant=\"secondary\" onClick={onAttach}>\n Attach\n </Button>\n )}\n </div>\n {error && records.length === 0 ? (\n <ErrorState title=\"Couldn't load records\" description=\"Please try again.\" />\n ) : (\n <DataTable<DataTableRecord>\n columns={columns}\n records={records as DataTableRecord[]}\n loading={loading}\n rowActions={\n abilities.update || canDelete || canDetach\n ? (record) => (\n <>\n {abilities.update && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => onEdit(record.id)}\n >\n Edit\n </Button>\n )}\n {canDelete && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => setPendingAction({ kind: 'delete', id: record.id })}\n >\n Delete\n </Button>\n )}\n {canDetach && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => setPendingAction({ kind: 'detach', id: record.id })}\n >\n Detach\n </Button>\n )}\n </>\n )\n : undefined\n }\n />\n )}\n <ConfirmDialog\n open={pendingAction !== null}\n onOpenChange={(next) => {\n if (!next) setPendingAction(null);\n }}\n onConfirm={confirmMutation}\n processing={mutating}\n config={\n pendingAction?.kind === 'detach'\n ? {\n heading: 'Detach this record?',\n color: 'destructive',\n submitLabel: 'Detach',\n }\n : {\n heading: 'Are you sure?',\n color: 'destructive',\n submitLabel: 'Delete',\n }\n }\n />\n </div>\n );\n}\n","/**\n * `<ResourceEditTabs>` — page-level tabs wrapping an edit page: a \"Data\"\n * tab (the existing form, passed as `children`) plus one tab per declared\n * `RelationManager`. Zero regression is the hard requirement: when\n * `relations` is empty (the overwhelming majority of Resources, which\n * declare no relation managers), this renders `children` directly with no\n * `Tabs` wrapper at all — so `ArqelEditPage`'s existing DOM/behavior is\n * byte-for-byte unchanged for every Resource that hasn't opted in.\n *\n * The active tab is deep-linked via `?tab=slug` (read once on mount from\n * `window.location.search`) so a reload / shared link lands back on the\n * same relation tab; navigating tabs updates the query string in place\n * (no Inertia visit — this is pure client-side tab state, not a page nav).\n *\n * Records source: `RelationManager::toArray()` (the server payload backing\n * each entry in `relations`) deliberately does NOT include the related\n * records — only `slug`/`label`/`type`/`table`/`fields`/`abilities`\n * (see `arqel-dev/types`' `RelationManagerProps` docblock). The actual rows\n * live behind `RelationController::index()`\n * (`GET {resource}/{parent}/relations/{relation}`), a plain JSON endpoint\n * (not an Inertia partial reload — it returns `{ records, table, abilities\n * }` directly), so each `RelationManagerPanel` is self-fetching (mirrors\n * `WidgetRenderer`'s `useEffect` + native `fetch()` pattern — see that\n * panel's own docblock). This keeps `ResourceEditTabs` from needing to own\n * a records cache itself; it only tracks a per-relation `refreshKey`\n * counter, bumped after a modal's mutation succeeds, so the panel's effect\n * re-runs and reloads the table without a full Inertia visit.\n *\n * Labels are a literal English fallback (\"Data\") rather than routed\n * through `useArqelTranslations`: that hook resolves its dictionary via\n * `usePage()`, and this component (like `RelationManagerPanel`, see its\n * docblock) is exercised directly in unit tests without a full Inertia\n * page context. `ArqelEditPage` itself already sits inside that context\n * and can localize its own strings; the tab-shell chrome added here\n * stays framework-agnostic.\n */\n\nimport type { RelationManagerProps } from '@arqel-dev/types/relations';\nimport { type ReactNode, useState } from 'react';\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '../shadcn/ui/tabs.js';\nimport { AttachModal } from './AttachModal.js';\nimport { RelationFormModal } from './RelationFormModal.js';\nimport { RelationManagerPanel } from './RelationManagerPanel.js';\n\nexport interface ResourceEditTabsProps {\n relations: RelationManagerProps[];\n parentSlug: string;\n parentId: string | number;\n basePath?: string;\n children: ReactNode;\n}\n\nfunction initialTab(relations: RelationManagerProps[]): string {\n const fromQuery = new URLSearchParams(window.location.search).get('tab');\n if (fromQuery && (fromQuery === 'data' || relations.some((r) => r.slug === fromQuery))) {\n return fromQuery;\n }\n return 'data';\n}\n\nexport function ResourceEditTabs({\n relations,\n parentSlug,\n parentId,\n basePath = '/admin',\n children,\n}: ResourceEditTabsProps) {\n // Hooks below only ever run when `relations` is non-empty in practice,\n // but React requires them unconditionally — cheap for the empty case.\n const [tab, setTab] = useState(() => initialTab(relations));\n const [modal, setModal] = useState<{ slug: string; recordId?: string | number } | null>(null);\n const [attachSlug, setAttachSlug] = useState<string | null>(null);\n // Bumped per-slug after a create/edit/attach mutation succeeds, so the\n // matching `RelationManagerPanel`'s fetch effect re-runs (see that\n // component's docblock — records no longer arrive via an Inertia\n // `relations` prop, so there is nothing left for a partial reload to\n // refresh).\n const [refreshKeys, setRefreshKeys] = useState<Record<string, number>>({});\n const bumpRefresh = (slug: string) =>\n setRefreshKeys((prev) => ({ ...prev, [slug]: (prev[slug] ?? 0) + 1 }));\n\n if (relations.length === 0) return <>{children}</>;\n\n const setActiveTab = (next: string) => {\n setTab(next);\n const url = new URL(window.location.href);\n if (next === 'data') {\n url.searchParams.delete('tab');\n } else {\n url.searchParams.set('tab', next);\n }\n window.history.replaceState(window.history.state, '', url);\n };\n\n const activeRelation = relations.find((r) => r.slug === modal?.slug);\n const attachRelation = relations.find((r) => r.slug === attachSlug);\n\n return (\n <Tabs value={tab} onValueChange={setActiveTab}>\n <TabsList>\n <TabsTrigger value=\"data\">Data</TabsTrigger>\n {relations.map((relation) => (\n <TabsTrigger key={relation.slug} value={relation.slug}>\n {relation.label}\n </TabsTrigger>\n ))}\n </TabsList>\n <TabsContent value=\"data\">{children}</TabsContent>\n {relations.map((relation) => (\n <TabsContent key={relation.slug} value={relation.slug}>\n <RelationManagerPanel\n relation={relation}\n parentSlug={parentSlug}\n parentId={parentId}\n basePath={basePath}\n records={[]}\n refreshKey={refreshKeys[relation.slug] ?? 0}\n onCreate={() => setModal({ slug: relation.slug })}\n onEdit={(id) => setModal({ slug: relation.slug, recordId: id })}\n onAttach={() => setAttachSlug(relation.slug)}\n onMutated={() => bumpRefresh(relation.slug)}\n />\n </TabsContent>\n ))}\n {modal && activeRelation && (\n <RelationFormModal\n open\n onClose={() => setModal(null)}\n relation={activeRelation}\n parentSlug={parentSlug}\n parentId={parentId}\n basePath={basePath}\n onSuccess={() => bumpRefresh(activeRelation.slug)}\n {...(modal.recordId !== undefined ? { recordId: modal.recordId } : {})}\n />\n )}\n {attachSlug && attachRelation && (\n <AttachModal\n open\n onClose={() => setAttachSlug(null)}\n relation={attachRelation}\n parentSlug={parentSlug}\n parentId={parentId}\n basePath={basePath}\n onSuccess={() => bumpRefresh(attachRelation.slug)}\n />\n )}\n </Tabs>\n );\n}\n","/**\n * Default Inertia page for `arqel::edit`.\n *\n * Same shape as `ArqelCreatePage` but seeds the form with the\n * server-emitted `record`. Submission posts to PUT `/{slug}/{id}`.\n */\n\nimport { useArqelForm } from '@arqel-dev/hooks';\nimport { useArqelTranslations } from '@arqel-dev/react/utils';\nimport type { FieldSchema } from '@arqel-dev/types/fields';\nimport type { FormSchema } from '@arqel-dev/types/forms';\nimport type { RelationManagerProps } from '@arqel-dev/types/relations';\nimport type { RecordType, ResourceEditProps } from '@arqel-dev/types/resources';\nimport { router, usePage } from '@inertiajs/react';\nimport type { FormEvent, JSX } from 'react';\nimport { FormActions } from '../form/FormActions.js';\nimport { FormRenderer } from '../form/FormRenderer.js';\nimport { ResourceEditTabs } from '../relations/ResourceEditTabs.js';\nimport { PageHeader } from '../utility/PageHeader.js';\n\nconst FALLBACK_SCHEMA: FormSchema = {\n schema: [],\n columns: 1,\n model: null,\n inline: false,\n disabled: false,\n};\n\ninterface ArqelFormShape {\n data: Record<string, unknown>;\n setData: (name: string, value: unknown) => void;\n errors: Record<string, string[]>;\n processing: boolean;\n}\n\nexport default function ArqelEditPage<TRecord extends RecordType = RecordType>(): JSX.Element {\n const page = usePage();\n const t = useArqelTranslations();\n const props = page.props as unknown as ResourceEditProps<TRecord>;\n const fields = (props.fields ?? []) as FieldSchema[];\n const record = props.record;\n const schema = (props as unknown as { form?: FormSchema }).form ?? FALLBACK_SCHEMA;\n const relations = (props.relations ?? []) as RelationManagerProps[];\n\n const form = useArqelForm({ fields, record }) as unknown as ArqelFormShape;\n\n const submit = (e: FormEvent): void => {\n e.preventDefault();\n const id = String((record as { id?: string | number } | undefined)?.id ?? '');\n const slug = props.resource?.slug ?? '';\n const basePath = props.resource?.panelPath ?? '/admin';\n router.put(`${basePath}/${slug}/${id}`, form.data as Record<string, never>, {\n preserveScroll: true,\n });\n };\n\n // Pre-interpolate the label into the English fallback so the title stays\n // correct even when the `arqel.pages.edit` key is absent from the shared\n // dictionary (the hook does not re-interpolate a raw fallback).\n const label = props.resource?.label ?? t('arqel.pages.fallback', 'record');\n const editTitle = props.recordTitle ?? t('arqel.pages.edit', `Edit ${label}`, { label });\n\n const formContent = (\n <form onSubmit={submit} className=\"space-y-6\">\n <FormRenderer\n schema={schema}\n fields={fields}\n values={form.data}\n onChange={(name, value) => form.setData(name, value)}\n errors={form.errors}\n />\n <FormActions processing={form.processing} onCancel={() => window.history.back()} />\n </form>\n );\n\n return (\n <div className=\"space-y-6\">\n <PageHeader title={editTitle} description={props.recordSubtitle ?? null} />\n <ResourceEditTabs\n relations={relations}\n parentSlug={props.resource?.slug ?? ''}\n parentId={(record as { id?: string | number } | undefined)?.id ?? ''}\n basePath={props.resource?.panelPath ?? '/admin'}\n >\n {formContent}\n </ResourceEditTabs>\n </div>\n );\n}\n"]}
|