@open-mercato/ui 0.6.6-develop.6228.1.cf483c68b5 → 0.6.6-develop.6241.1.47c01ad4ed

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.
@@ -34,7 +34,7 @@ import { resolveInjectedIcon } from "./injection/resolveInjectedIcon.js";
34
34
  import { serializeExport, defaultExportFilename } from "@open-mercato/shared/lib/crud/exporters";
35
35
  import { apiCall, withScopedApiRequestHeaders } from "./utils/apiCall.js";
36
36
  import { buildOptimisticLockHeader } from "./utils/optimisticLock.js";
37
- import { surfaceRecordConflict } from "./conflicts/index.js";
37
+ import { useGuardedMutation } from "./injection/useGuardedMutation.js";
38
38
  import { raiseCrudError } from "./utils/serverErrors.js";
39
39
  import { PerspectiveSidebar } from "./PerspectiveSidebar.js";
40
40
  import { Popover, PopoverTrigger, PopoverContent } from "../primitives/popover.js";
@@ -1215,6 +1215,19 @@ function DataTable({
1215
1215
  initialPerspectiveAppliedRef.current = true;
1216
1216
  }, [perspectiveTableId, applyPerspectiveSettings, advancedFilter]);
1217
1217
  const perspectiveQueryKey = ["table-perspectives", perspectiveTableId];
1218
+ const perspectiveMutationContextId = `data-table-perspectives:${perspectiveTableId ?? "unknown"}`;
1219
+ const { runMutation: runPerspectiveMutation, retryLastMutation: retryPerspectiveMutation } = useGuardedMutation({
1220
+ contextId: perspectiveMutationContextId,
1221
+ blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
1222
+ });
1223
+ const perspectiveMutationContext = React.useMemo(
1224
+ () => ({
1225
+ formId: perspectiveMutationContextId,
1226
+ resourceKind: "perspective",
1227
+ retryLastMutation: retryPerspectiveMutation
1228
+ }),
1229
+ [perspectiveMutationContextId, retryPerspectiveMutation]
1230
+ );
1218
1231
  const savePerspectiveMutation = useMutation({
1219
1232
  mutationFn: async (input) => {
1220
1233
  if (!perspectiveTableId) throw new Error("Missing table id");
@@ -1230,26 +1243,32 @@ function DataTable({
1230
1243
  console.debug("[DataTable] perspective payload", payload);
1231
1244
  }
1232
1245
  const existing = input.perspectiveId ? perspectiveData?.perspectives.find((p) => p.id === input.perspectiveId) ?? null : null;
1233
- const call = await withScopedApiRequestHeaders(
1234
- buildOptimisticLockHeader(existing?.updatedAt ?? null),
1235
- () => apiCall(
1236
- `/api/perspectives/${encodeURIComponent(perspectiveTableId)}`,
1237
- {
1238
- method: "POST",
1239
- headers: { "Content-Type": "application/json" },
1240
- body: JSON.stringify(payload)
1246
+ return runPerspectiveMutation({
1247
+ operation: async () => {
1248
+ const call = await withScopedApiRequestHeaders(
1249
+ buildOptimisticLockHeader(existing?.updatedAt ?? null),
1250
+ () => apiCall(
1251
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}`,
1252
+ {
1253
+ method: "POST",
1254
+ headers: { "Content-Type": "application/json" },
1255
+ body: JSON.stringify(payload)
1256
+ }
1257
+ )
1258
+ );
1259
+ if (call.status === 404) {
1260
+ throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` to regenerate module routes and restart the dev server."));
1241
1261
  }
1242
- )
1243
- );
1244
- if (call.status === 404) {
1245
- throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` to regenerate module routes and restart the dev server."));
1246
- }
1247
- if (!call.ok) {
1248
- await raiseCrudError(call.response, t("ui.dataTable.perspectives.error.save", "Failed to save perspective"));
1249
- }
1250
- const result = call.result;
1251
- if (!result) throw new Error(t("ui.dataTable.perspectives.error.save", "Failed to save perspective"));
1252
- return result;
1262
+ if (!call.ok) {
1263
+ await raiseCrudError(call.response, t("ui.dataTable.perspectives.error.save", "Failed to save perspective"));
1264
+ }
1265
+ const result = call.result;
1266
+ if (!result) throw new Error(t("ui.dataTable.perspectives.error.save", "Failed to save perspective"));
1267
+ return result;
1268
+ },
1269
+ context: perspectiveMutationContext,
1270
+ mutationPayload: payload
1271
+ });
1253
1272
  },
1254
1273
  onSuccess: (data2) => {
1255
1274
  if (perspectiveTableId) {
@@ -1259,11 +1278,10 @@ function DataTable({
1259
1278
  applyPerspectiveSettings(data2.perspective.settings, data2.perspective.id);
1260
1279
  }
1261
1280
  },
1262
- onError: (error2) => {
1281
+ onError: () => {
1263
1282
  if (perspectiveTableId) {
1264
1283
  void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey });
1265
1284
  }
1266
- surfaceRecordConflict(error2, t);
1267
1285
  }
1268
1286
  });
1269
1287
  const resolveColumnLabel = React.useCallback((column) => {
@@ -1283,14 +1301,20 @@ function DataTable({
1283
1301
  const deletePerspectiveMutation = useMutation({
1284
1302
  mutationFn: async ({ perspectiveId }) => {
1285
1303
  if (!perspectiveTableId) throw new Error("Missing table id");
1286
- const call = await apiCall(
1287
- `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/${encodeURIComponent(perspectiveId)}`,
1288
- { method: "DELETE" }
1289
- );
1290
- if (call.status === 404) throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` and restart the dev server."));
1291
- if (!call.ok) {
1292
- await raiseCrudError(call.response, t("ui.dataTable.perspectives.error.delete", "Failed to delete perspective"));
1293
- }
1304
+ await runPerspectiveMutation({
1305
+ operation: async () => {
1306
+ const call = await apiCall(
1307
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/${encodeURIComponent(perspectiveId)}`,
1308
+ { method: "DELETE" }
1309
+ );
1310
+ if (call.status === 404) throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` and restart the dev server."));
1311
+ if (!call.ok) {
1312
+ await raiseCrudError(call.response, t("ui.dataTable.perspectives.error.delete", "Failed to delete perspective"));
1313
+ }
1314
+ },
1315
+ context: perspectiveMutationContext,
1316
+ mutationPayload: { perspectiveId }
1317
+ });
1294
1318
  },
1295
1319
  onMutate: ({ perspectiveId }) => {
1296
1320
  setDeletingIds((prev) => prev.includes(perspectiveId) ? prev : [...prev, perspectiveId]);
@@ -1318,14 +1342,20 @@ function DataTable({
1318
1342
  const clearRoleMutation = useMutation({
1319
1343
  mutationFn: async ({ roleId }) => {
1320
1344
  if (!perspectiveTableId) throw new Error("Missing table id");
1321
- const call = await apiCall(
1322
- `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/roles/${encodeURIComponent(roleId)}`,
1323
- { method: "DELETE" }
1324
- );
1325
- if (call.status === 404) throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` and restart the dev server."));
1326
- if (!call.ok) {
1327
- await raiseCrudError(call.response, t("ui.dataTable.perspectives.error.clearRoles", "Failed to clear role perspectives"));
1328
- }
1345
+ await runPerspectiveMutation({
1346
+ operation: async () => {
1347
+ const call = await apiCall(
1348
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/roles/${encodeURIComponent(roleId)}`,
1349
+ { method: "DELETE" }
1350
+ );
1351
+ if (call.status === 404) throw new Error(t("ui.dataTable.perspectives.error.apiUnavailable", "Perspectives API is not available. Run `yarn generate` and restart the dev server."));
1352
+ if (!call.ok) {
1353
+ await raiseCrudError(call.response, t("ui.dataTable.perspectives.error.clearRoles", "Failed to clear role perspectives"));
1354
+ }
1355
+ },
1356
+ context: perspectiveMutationContext,
1357
+ mutationPayload: { roleId }
1358
+ });
1329
1359
  },
1330
1360
  onMutate: ({ roleId }) => {
1331
1361
  setRoleClearingIds((prev) => prev.includes(roleId) ? prev : [...prev, roleId]);