@open-mercato/ui 0.6.6-develop.6229.1.ebe6706732 → 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.
- package/dist/backend/DataTable.js +68 -38
- package/dist/backend/DataTable.js.map +3 -3
- package/dist/primitives/icon-button.js +2 -0
- package/dist/primitives/icon-button.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/DataTable.tsx +79 -39
- package/src/backend/__tests__/DataTable.perspectiveGuardedMutation.test.tsx +263 -0
- package/src/primitives/__tests__/icon-button.test.tsx +40 -0
- package/src/primitives/icon-button.tsx +4 -0
|
@@ -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 {
|
|
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
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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: (
|
|
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
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
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
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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]);
|