@nestledjs/data-browser 0.1.24 → 1.0.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.
@@ -8,6 +8,7 @@ import { TrashIcon } from "@heroicons/react/24/solid";
8
8
  import { ErrorBoundary } from "@nestledjs/shared-components";
9
9
  import { Form } from "@nestledjs/forms";
10
10
  import { useAdminDataContext } from "../context/AdminDataContext.js";
11
+ import { AdminDataStateMessage } from "../components/AdminDataStateMessage.js";
11
12
  import { useParams, Link, useNavigate } from "react-router";
12
13
  import { getAdminDocuments, buildFormFields, cleanFormInput } from "../utils/graphql-utils.js";
13
14
  function toLowerCamelCase(name) {
@@ -34,6 +35,106 @@ const sanitizeInput = (input) => {
34
35
  const toKebabCase = (str) => {
35
36
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
36
37
  };
38
+ function processRelationFieldValue(field, item) {
39
+ var _a;
40
+ const relationFieldName = ((_a = field.relationFromFields) == null ? void 0 : _a[0]) || `${field.name}Id`;
41
+ let value = item[relationFieldName];
42
+ if (value === void 0) {
43
+ const relationObject = item[field.name];
44
+ if (relationObject && typeof relationObject === "object" && relationObject.id) {
45
+ value = relationObject.id;
46
+ }
47
+ }
48
+ if (value && typeof value === "object") {
49
+ value = value.id || "";
50
+ }
51
+ return value || "";
52
+ }
53
+ function processDateFieldValue(field, value) {
54
+ if (value === null || value === void 0 || value === "") {
55
+ return "";
56
+ }
57
+ try {
58
+ const dateValue = value instanceof Date ? value : new Date(value);
59
+ const fieldTypeLower = field.type.toLowerCase();
60
+ if (fieldTypeLower === "date") {
61
+ return dateValue.toISOString().split("T")[0];
62
+ } else {
63
+ return dateValue.toISOString().substring(0, 16);
64
+ }
65
+ } catch (e) {
66
+ return "";
67
+ }
68
+ }
69
+ function sanitizeFieldValue(value, field) {
70
+ const fieldTypeLower = field.type.toLowerCase();
71
+ if (value === null) {
72
+ return fieldTypeLower === "boolean" ? false : "";
73
+ }
74
+ if (fieldTypeLower === "boolean") {
75
+ return Boolean(value);
76
+ }
77
+ if (value !== null && typeof value === "object" && "id" in value) {
78
+ return value.id || "";
79
+ }
80
+ if (typeof value === "object") {
81
+ return "";
82
+ }
83
+ return value;
84
+ }
85
+ function performFinalSafetyChecks(initialValues, model) {
86
+ for (const [key, value] of Object.entries(initialValues)) {
87
+ if (value === void 0) {
88
+ initialValues[key] = "";
89
+ continue;
90
+ }
91
+ if (value === null || typeof value !== "object") {
92
+ continue;
93
+ }
94
+ if ("id" in value && typeof value.id === "string") {
95
+ initialValues[key] = value.id;
96
+ } else if (value instanceof Date) {
97
+ const field = model.fields.find((f) => f.name === key);
98
+ initialValues[key] = (field == null ? void 0 : field.type.toLowerCase()) === "date" ? value.toISOString().split("T")[0] : value.toISOString();
99
+ } else {
100
+ initialValues[key] = "";
101
+ }
102
+ }
103
+ }
104
+ function extractInitialValues(model, item) {
105
+ const initialValues = {};
106
+ if (!item) {
107
+ return initialValues;
108
+ }
109
+ const idField = model.fields.find((f) => f.isId);
110
+ if (idField) {
111
+ initialValues[idField.name] = item[idField.name];
112
+ }
113
+ const editableFields = model.fields.filter((field) => {
114
+ if (field.isId || field.isReadOnly || field.isGenerated) return false;
115
+ if (field.isUpdatedAt || field.name === "createdAt") return false;
116
+ if (field.relationName && field.isList) return false;
117
+ return true;
118
+ });
119
+ editableFields.forEach((field) => {
120
+ var _a;
121
+ let value = item[field.name];
122
+ if (field.relationName && !field.isList) {
123
+ const relationFieldName = ((_a = field.relationFromFields) == null ? void 0 : _a[0]) || `${field.name}Id`;
124
+ initialValues[relationFieldName] = processRelationFieldValue(field, item);
125
+ } else {
126
+ const fieldTypeLower = field.type.toLowerCase();
127
+ if (fieldTypeLower === "datetime" || fieldTypeLower === "date") {
128
+ value = processDateFieldValue(field, value);
129
+ } else {
130
+ value = sanitizeFieldValue(value, field);
131
+ }
132
+ initialValues[field.name] = value;
133
+ }
134
+ });
135
+ performFinalSafetyChecks(initialValues, model);
136
+ return initialValues;
137
+ }
37
138
  const validateId = (id) => {
38
139
  if (!id) return null;
39
140
  const sanitized = sanitizeInput(id);
@@ -45,7 +146,7 @@ const validateId = (id) => {
45
146
  };
46
147
  function AdminDataEditPage() {
47
148
  const { dataType, id } = useParams();
48
- const { databaseModels, basePath = "/admin/data", formTheme } = useAdminDataContext();
149
+ const { databaseModels, basePath = "/admin/data", formTheme, displayFieldConfig } = useAdminDataContext();
49
150
  const findModelByName = (name) => {
50
151
  return databaseModels.find((model2) => model2.name === name);
51
152
  };
@@ -99,9 +200,138 @@ function AdminDataEditPage() {
99
200
  ) })
100
201
  ] }) }) }) });
101
202
  }
102
- return /* @__PURE__ */ jsx(AdminDataEditPageContent, { model, id: validatedId, basePath, formTheme });
203
+ return /* @__PURE__ */ jsx(AdminDataEditPageContent, { model, id: validatedId, basePath, formTheme, displayFieldConfig });
204
+ }
205
+ function DeleteConfirmModal({ show, modelName, isDeleting, onConfirm, onCancel }) {
206
+ if (!show) return null;
207
+ return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-gray-600 dark:bg-gray-900 bg-opacity-50 dark:bg-opacity-75 overflow-y-auto h-full w-full z-50 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl max-w-md w-full mx-4", children: [
208
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
209
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "h-6 w-6 text-red-600" }) }),
210
+ /* @__PURE__ */ jsxs("div", { className: "ml-3", children: [
211
+ /* @__PURE__ */ jsxs("h3", { className: "text-lg font-medium text-gray-900 dark:text-gray-100", children: [
212
+ "Delete ",
213
+ toReadableText(modelName)
214
+ ] }),
215
+ /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
216
+ "Are you sure you want to delete this ",
217
+ toReadableText(modelName).toLowerCase(),
218
+ "? This action cannot be undone."
219
+ ] }) })
220
+ ] })
221
+ ] }),
222
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 sm:mt-4 sm:flex sm:flex-row-reverse", children: [
223
+ /* @__PURE__ */ jsx(
224
+ "button",
225
+ {
226
+ onClick: onConfirm,
227
+ disabled: isDeleting,
228
+ className: "w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed",
229
+ children: isDeleting ? "Deleting..." : "Delete"
230
+ }
231
+ ),
232
+ /* @__PURE__ */ jsx(
233
+ "button",
234
+ {
235
+ onClick: onCancel,
236
+ disabled: isDeleting,
237
+ className: "mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web sm:mt-0 sm:w-auto sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed",
238
+ children: "Cancel"
239
+ }
240
+ )
241
+ ] })
242
+ ] }) });
103
243
  }
104
- function AdminDataEditPageContent({ model, id, basePath, formTheme }) {
244
+ function getStatusColors(isSuccess, isError) {
245
+ if (isSuccess) {
246
+ return {
247
+ bgColor: "bg-green-50 border border-green-200",
248
+ textColor: "text-green-800"
249
+ };
250
+ }
251
+ if (isError) {
252
+ return {
253
+ bgColor: "bg-red-50 border border-red-200",
254
+ textColor: "text-red-800"
255
+ };
256
+ }
257
+ return {
258
+ bgColor: "bg-blue-50 border border-blue-200",
259
+ textColor: "text-blue-800"
260
+ };
261
+ }
262
+ function getStatusIcon(isSuccess, isError) {
263
+ if (isSuccess) {
264
+ return /* @__PURE__ */ jsx(CheckCircleIcon, { className: "h-5 w-5 text-green-400" });
265
+ }
266
+ if (isError) {
267
+ return /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "h-5 w-5 text-red-400" });
268
+ }
269
+ return /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" });
270
+ }
271
+ function StatusMessage({ submissionStatus, deleteStatus, submissionMessage, deleteMessage, modelName }) {
272
+ if (submissionStatus === "idle" && deleteStatus === "idle") return null;
273
+ const isSuccess = submissionStatus === "success" || deleteStatus === "success";
274
+ const isError = submissionStatus === "error" || deleteStatus === "error";
275
+ const isLoading = submissionStatus === "loading" || deleteStatus === "loading";
276
+ const { bgColor, textColor } = getStatusColors(isSuccess, isError);
277
+ const loadingMessage = submissionStatus === "loading" ? `Updating ${toReadableText(modelName)}...` : `Deleting ${toReadableText(modelName)}...`;
278
+ const message = isLoading ? loadingMessage : submissionMessage || deleteMessage;
279
+ return /* @__PURE__ */ jsx("div", { className: `mb-6 rounded-md p-4 ${bgColor}`, children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
280
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: getStatusIcon(isSuccess, isError) }),
281
+ /* @__PURE__ */ jsx("div", { className: "ml-3", children: /* @__PURE__ */ jsx("p", { className: `text-sm font-medium ${textColor}`, children: message }) })
282
+ ] }) });
283
+ }
284
+ async function executeUpdateMutation(updateMutation, formData, model, id, idVariableName) {
285
+ try {
286
+ const cleanedInput = cleanFormInput(formData, model);
287
+ const result = await updateMutation({
288
+ variables: {
289
+ input: cleanedInput,
290
+ [idVariableName]: id
291
+ }
292
+ });
293
+ if (result.errors) {
294
+ return {
295
+ success: false,
296
+ message: result.errors.map((err) => err.message).join(", ")
297
+ };
298
+ }
299
+ return {
300
+ success: true,
301
+ message: `${toReadableText(model.name)} updated successfully!`
302
+ };
303
+ } catch (error) {
304
+ return {
305
+ success: false,
306
+ message: error instanceof Error ? error.message : "An unexpected error occurred"
307
+ };
308
+ }
309
+ }
310
+ async function executeDeleteMutation(deleteMutation, id, idVariableName, model) {
311
+ try {
312
+ const result = await deleteMutation({
313
+ variables: {
314
+ [idVariableName]: id
315
+ }
316
+ });
317
+ if (result.errors) {
318
+ return {
319
+ success: false,
320
+ message: result.errors.map((err) => err.message).join(", ")
321
+ };
322
+ }
323
+ return {
324
+ success: true,
325
+ message: `${toReadableText(model.name)} deleted successfully!`
326
+ };
327
+ } catch (error) {
328
+ return {
329
+ success: false,
330
+ message: error instanceof Error ? error.message : "An unexpected error occurred"
331
+ };
332
+ }
333
+ }
334
+ function AdminDataEditPageContent({ model, id, basePath, formTheme, displayFieldConfig }) {
105
335
  const navigate = useNavigate();
106
336
  const { sdk, databaseModels } = useAdminDataContext();
107
337
  const [submissionState, setSubmissionState] = useState({ status: "idle" });
@@ -195,216 +425,86 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme }) {
195
425
  }
196
426
  }, [deleteState.status]);
197
427
  if (!documents || !QUERY || !UPDATE_MUTATION || !DELETE_MUTATION) {
198
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "mt-8 mx-auto w-full max-w-2xl", children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 py-8 px-4 shadow sm:rounded-lg sm:px-10", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
199
- /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "mx-auto h-12 w-12 text-red-400" }),
200
- /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-medium text-gray-900 dark:text-gray-100", children: "GraphQL Schema Error" }),
201
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: "Unable to load GraphQL documents for this model. Please ensure the API server is running and the GraphQL schema is up to date." }),
202
- /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(
203
- Link,
204
- {
205
- to: basePath,
206
- className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-web hover:bg-green-web-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web",
207
- children: "Return to Data Browser"
208
- }
209
- ) })
210
- ] }) }) }) });
428
+ return /* @__PURE__ */ jsx(
429
+ AdminDataStateMessage,
430
+ {
431
+ type: "schema-error",
432
+ title: "GraphQL Schema Error",
433
+ message: "Unable to load GraphQL documents for this model. Please ensure the API server is running and the GraphQL schema is up to date.",
434
+ basePath
435
+ }
436
+ );
211
437
  }
212
438
  if (error) {
213
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "mt-8 mx-auto w-full max-w-md", children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 py-8 px-4 shadow sm:rounded-lg sm:px-10", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
214
- /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "mx-auto h-12 w-12 text-red-400" }),
215
- /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-medium text-gray-900 dark:text-gray-100", children: "Error Loading Data" }),
216
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: error.message }),
217
- /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-3", children: [
218
- /* @__PURE__ */ jsx(
219
- "button",
220
- {
221
- onClick: () => refetch(),
222
- className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-web hover:bg-green-web-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web",
223
- children: "Try Again"
224
- }
225
- ),
226
- /* @__PURE__ */ jsx(
227
- Link,
228
- {
229
- to: `${basePath}/${toKebabCase(model.pluralName)}`,
230
- className: "w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web",
231
- children: "Back to List"
232
- }
233
- )
234
- ] })
235
- ] }) }) }) });
439
+ return /* @__PURE__ */ jsx(
440
+ AdminDataStateMessage,
441
+ {
442
+ type: "error",
443
+ title: "Error Loading Data",
444
+ message: error.message,
445
+ basePath,
446
+ onRetry: () => refetch(),
447
+ backLinkText: "Back to List",
448
+ backLinkPath: `${basePath}/${toKebabCase(model.pluralName)}`
449
+ }
450
+ );
236
451
  }
237
452
  if (loading) {
238
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "mt-8 mx-auto w-full max-w-md", children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 py-8 px-4 shadow sm:rounded-lg sm:px-10", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
239
- /* @__PURE__ */ jsx("div", { className: "mx-auto h-12 w-12 border-4 border-green-web border-t-transparent rounded-full animate-spin" }),
240
- /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-medium text-gray-900 dark:text-gray-100", children: "Loading..." }),
241
- /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: [
242
- "Loading ",
243
- toReadableText(model.name),
244
- " data..."
245
- ] })
246
- ] }) }) }) });
453
+ return /* @__PURE__ */ jsx(
454
+ AdminDataStateMessage,
455
+ {
456
+ type: "loading",
457
+ title: "Loading...",
458
+ message: `Loading ${toReadableText(model.name)} data...`,
459
+ basePath
460
+ }
461
+ );
247
462
  }
248
463
  const item = data == null ? void 0 : data[responseFieldName];
249
464
  if (!item) {
250
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "mt-8 mx-auto w-full max-w-md", children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 py-8 px-4 shadow sm:rounded-lg sm:px-10", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
251
- /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "mx-auto h-12 w-12 text-yellow-400" }),
252
- /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-medium text-gray-900 dark:text-gray-100", children: "Not Found" }),
253
- /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: [
254
- "The ",
255
- toReadableText(model.name),
256
- " you're looking for doesn't exist or has been deleted."
257
- ] }),
258
- /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(
259
- Link,
260
- {
261
- to: `${basePath}/${toKebabCase(model.pluralName)}`,
262
- className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-web hover:bg-green-web-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web",
263
- children: "Back to List"
264
- }
265
- ) })
266
- ] }) }) }) });
267
- }
268
- const formFields = buildFormFields(sdk, model, "update", item, submissionState.status === "loading", basePath, databaseModels);
269
- const initialValues = {};
270
- if (item) {
271
- const editableFields = model.fields.filter((field) => {
272
- if (field.isId && false) ;
273
- if (field.isReadOnly || field.isGenerated) return false;
274
- if (field.isUpdatedAt || field.name === "createdAt") return false;
275
- if (field.relationName && field.isList) return false;
276
- return true;
277
- });
278
- editableFields.forEach((field) => {
279
- var _a;
280
- let value = item[field.name];
281
- if (field.relationName && !field.isList) {
282
- const relationFieldName = ((_a = field.relationFromFields) == null ? void 0 : _a[0]) || `${field.name}Id`;
283
- value = item[relationFieldName];
284
- if (value === void 0) {
285
- const relationObject = item[field.name];
286
- if (relationObject && typeof relationObject === "object" && relationObject.id) {
287
- value = relationObject.id;
288
- }
289
- }
290
- if (value && typeof value === "object") {
291
- value = value.id || "";
292
- }
293
- initialValues[relationFieldName] = value || "";
294
- } else {
295
- if (field.type.toLowerCase() === "datetime" || field.type.toLowerCase() === "date") {
296
- if (value !== null && value !== void 0 && value !== "") {
297
- try {
298
- const dateValue = value instanceof Date ? value : new Date(value);
299
- if (field.type.toLowerCase() === "date") {
300
- value = dateValue.toISOString().split("T")[0];
301
- } else {
302
- const isoString = dateValue.toISOString();
303
- value = isoString.substring(0, 16);
304
- }
305
- } catch (e) {
306
- value = "";
307
- }
308
- } else {
309
- value = "";
310
- }
311
- }
312
- if (value === null && field.type.toLowerCase() !== "boolean") {
313
- value = "";
314
- }
315
- if (field.type.toLowerCase() === "boolean") {
316
- value = Boolean(value);
317
- }
318
- if (value !== null && typeof value === "object") {
319
- if (typeof value === "object" && "id" in value) {
320
- value = value.id;
321
- } else {
322
- value = "";
323
- }
324
- }
325
- initialValues[field.name] = value;
326
- }
327
- });
328
- for (const [key, value] of Object.entries(initialValues)) {
329
- if (value === void 0) {
330
- initialValues[key] = "";
331
- continue;
332
- }
333
- if (value === null) {
334
- continue;
335
- }
336
- if (typeof value !== "object") {
337
- continue;
338
- }
339
- if ("id" in value && typeof value.id === "string") {
340
- initialValues[key] = value.id;
341
- } else if (value instanceof Date) {
342
- const field = model.fields.find((f) => f.name === key);
343
- initialValues[key] = (field == null ? void 0 : field.type.toLowerCase()) === "date" ? value.toISOString().split("T")[0] : value.toISOString();
344
- } else {
345
- initialValues[key] = "";
465
+ return /* @__PURE__ */ jsx(
466
+ AdminDataStateMessage,
467
+ {
468
+ type: "not-found",
469
+ title: "Not Found",
470
+ message: `The ${toReadableText(model.name)} you're looking for doesn't exist or has been deleted.`,
471
+ basePath,
472
+ backLinkText: "Back to List",
473
+ backLinkPath: `${basePath}/${toKebabCase(model.pluralName)}`
346
474
  }
347
- }
475
+ );
348
476
  }
477
+ const formFields = buildFormFields(sdk, model, "update", {
478
+ currentItem: item,
479
+ isSubmitting: submissionState.status === "loading",
480
+ basePath,
481
+ databaseModels,
482
+ displayFieldConfig
483
+ });
484
+ const initialValues = extractInitialValues(model, item);
349
485
  const handleSubmit = async (formData) => {
350
- try {
351
- setSubmissionState({ status: "loading" });
352
- const cleanedInput = cleanFormInput(formData, model);
353
- const result = await updateMutation({
354
- variables: {
355
- input: cleanedInput,
356
- [idVariableName]: id
357
- }
358
- });
359
- if (result.errors) {
360
- setSubmissionState({
361
- status: "error",
362
- message: result.errors.map((err) => err.message).join(", ")
363
- });
364
- window.scrollTo({ top: 0, behavior: "smooth" });
365
- return;
366
- }
367
- setSubmissionState({
368
- status: "success",
369
- message: `${toReadableText(model.name)} updated successfully!`
370
- });
371
- window.scrollTo({ top: 0, behavior: "smooth" });
486
+ setSubmissionState({ status: "loading" });
487
+ const result = await executeUpdateMutation(updateMutation, formData, model, id, idVariableName);
488
+ setSubmissionState({
489
+ status: result.success ? "success" : "error",
490
+ message: result.message
491
+ });
492
+ window.scrollTo({ top: 0, behavior: "smooth" });
493
+ if (result.success) {
372
494
  await refetch();
373
- } catch (error2) {
374
- setSubmissionState({
375
- status: "error",
376
- message: error2 instanceof Error ? error2.message : "An unexpected error occurred"
377
- });
378
- window.scrollTo({ top: 0, behavior: "smooth" });
379
495
  }
380
496
  };
381
497
  const handleDelete = async () => {
382
- try {
383
- setDeleteState({ status: "loading" });
384
- const result = await deleteMutation({
385
- variables: {
386
- [idVariableName]: id
387
- }
388
- });
389
- if (result.errors) {
390
- setDeleteState({
391
- status: "error",
392
- message: result.errors.map((err) => err.message).join(", ")
393
- });
394
- return;
395
- }
396
- setDeleteState({
397
- status: "success",
398
- message: `${toReadableText(model.name)} deleted successfully!`
399
- });
498
+ setDeleteState({ status: "loading" });
499
+ const result = await executeDeleteMutation(deleteMutation, id, idVariableName, model);
500
+ setDeleteState({
501
+ status: result.success ? "success" : "error",
502
+ message: result.message
503
+ });
504
+ if (result.success) {
400
505
  setTimeout(() => {
401
506
  navigate(`${basePath}/${toKebabCase(model.pluralName)}`);
402
507
  }, 1500);
403
- } catch (error2) {
404
- setDeleteState({
405
- status: "error",
406
- message: error2 instanceof Error ? error2.message : "An unexpected error occurred"
407
- });
408
508
  }
409
509
  };
410
510
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
@@ -476,57 +576,24 @@ function AdminDataEditPageContent({ model, id, basePath, formTheme }) {
476
576
  )
477
577
  ] })
478
578
  ] }),
479
- showDeleteConfirm && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-gray-600 dark:bg-gray-900 bg-opacity-50 dark:bg-opacity-75 overflow-y-auto h-full w-full z-50 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl max-w-md w-full mx-4", children: [
480
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
481
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "h-6 w-6 text-red-600" }) }),
482
- /* @__PURE__ */ jsxs("div", { className: "ml-3", children: [
483
- /* @__PURE__ */ jsxs("h3", { className: "text-lg font-medium text-gray-900 dark:text-gray-100", children: [
484
- "Delete ",
485
- toReadableText(model.name)
486
- ] }),
487
- /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
488
- "Are you sure you want to delete this",
489
- " ",
490
- toReadableText(model.name).toLowerCase(),
491
- "? This action cannot be undone."
492
- ] }) })
493
- ] })
494
- ] }),
495
- /* @__PURE__ */ jsxs("div", { className: "mt-5 sm:mt-4 sm:flex sm:flex-row-reverse", children: [
496
- /* @__PURE__ */ jsx(
497
- "button",
498
- {
499
- onClick: handleDelete,
500
- disabled: deleteState.status === "loading",
501
- className: "w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed",
502
- children: deleteState.status === "loading" ? "Deleting..." : "Delete"
503
- }
504
- ),
505
- /* @__PURE__ */ jsx(
506
- "button",
507
- {
508
- onClick: () => setShowDeleteConfirm(false),
509
- disabled: deleteState.status === "loading",
510
- className: "mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-web sm:mt-0 sm:w-auto sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed",
511
- children: "Cancel"
512
- }
513
- )
514
- ] })
515
- ] }) }),
516
- (submissionState.status !== "idle" || deleteState.status !== "idle") && /* @__PURE__ */ jsx(
517
- "div",
579
+ /* @__PURE__ */ jsx(
580
+ DeleteConfirmModal,
518
581
  {
519
- className: `mb-6 rounded-md p-4 ${submissionState.status === "success" || deleteState.status === "success" ? "bg-green-50 border border-green-200" : submissionState.status === "error" || deleteState.status === "error" ? "bg-red-50 border border-red-200" : "bg-blue-50 border border-blue-200"}`,
520
- children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
521
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: submissionState.status === "success" || deleteState.status === "success" ? /* @__PURE__ */ jsx(CheckCircleIcon, { className: "h-5 w-5 text-green-400" }) : submissionState.status === "error" || deleteState.status === "error" ? /* @__PURE__ */ jsx(ExclamationCircleIcon, { className: "h-5 w-5 text-red-400" }) : /* @__PURE__ */ jsx("div", { className: "h-5 w-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" }) }),
522
- /* @__PURE__ */ jsx("div", { className: "ml-3", children: /* @__PURE__ */ jsx(
523
- "p",
524
- {
525
- className: `text-sm font-medium ${submissionState.status === "success" || deleteState.status === "success" ? "text-green-800" : submissionState.status === "error" || deleteState.status === "error" ? "text-red-800" : "text-blue-800"}`,
526
- children: submissionState.status === "loading" ? `Updating ${toReadableText(model.name)}...` : deleteState.status === "loading" ? `Deleting ${toReadableText(model.name)}...` : submissionState.message || deleteState.message
527
- }
528
- ) })
529
- ] })
582
+ show: showDeleteConfirm,
583
+ modelName: model.name,
584
+ isDeleting: deleteState.status === "loading",
585
+ onConfirm: handleDelete,
586
+ onCancel: () => setShowDeleteConfirm(false)
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsx(
590
+ StatusMessage,
591
+ {
592
+ submissionStatus: submissionState.status,
593
+ deleteStatus: deleteState.status,
594
+ submissionMessage: submissionState.message,
595
+ deleteMessage: deleteState.message,
596
+ modelName: model.name
530
597
  }
531
598
  ),
532
599
  /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 shadow-sm rounded-lg", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(