@open-mercato/ui 0.6.6-develop.6255.1.a6ee4a57c1 → 0.6.6-develop.6256.1.9fc16aedc4

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.
@@ -788,6 +788,7 @@ function DataTable({
788
788
  perspectives: [],
789
789
  defaultPerspectiveId: null,
790
790
  rolePerspectives: [],
791
+ manageableRolePerspectives: [],
791
792
  roles: [],
792
793
  canApplyToRoles: false
793
794
  };
@@ -1215,6 +1216,10 @@ function DataTable({
1215
1216
  initialPerspectiveAppliedRef.current = true;
1216
1217
  }, [perspectiveTableId, applyPerspectiveSettings, advancedFilter]);
1217
1218
  const perspectiveQueryKey = ["table-perspectives", perspectiveTableId];
1219
+ const rolePerspectivesForLocking = React.useMemo(
1220
+ () => perspectiveData?.manageableRolePerspectives ?? perspectiveData?.rolePerspectives ?? [],
1221
+ [perspectiveData]
1222
+ );
1218
1223
  const perspectiveMutationContextId = `data-table-perspectives:${perspectiveTableId ?? "unknown"}`;
1219
1224
  const { runMutation: runPerspectiveMutation, retryLastMutation: retryPerspectiveMutation } = useGuardedMutation({
1220
1225
  contextId: perspectiveMutationContextId,
@@ -1231,13 +1236,28 @@ function DataTable({
1231
1236
  const savePerspectiveMutation = useMutation({
1232
1237
  mutationFn: async (input) => {
1233
1238
  if (!perspectiveTableId) throw new Error("Missing table id");
1239
+ const roleExpectedUpdatedAtByRoleId = {};
1240
+ const roleExpectedUpdatedAtByPerspectiveId = {};
1241
+ for (const roleId of input.applyToRoles) {
1242
+ const rolePerspectives = rolePerspectivesForLocking.filter((p) => p.roleId === roleId);
1243
+ const matching = rolePerspectives.find((p) => p.name.trim() === input.name.trim()) ?? null;
1244
+ const defaultPerspective = input.setRoleDefault ? rolePerspectives.find((p) => p.isDefault) ?? null : null;
1245
+ for (const candidate of [matching, defaultPerspective]) {
1246
+ if (!candidate?.updatedAt) continue;
1247
+ roleExpectedUpdatedAtByPerspectiveId[candidate.id] = candidate.updatedAt;
1248
+ }
1249
+ const roleFallback = matching ?? defaultPerspective;
1250
+ if (roleFallback?.updatedAt) roleExpectedUpdatedAtByRoleId[roleId] = roleFallback.updatedAt;
1251
+ }
1234
1252
  const payload = {
1235
1253
  perspectiveId: input.perspectiveId ?? void 0,
1236
1254
  name: input.name,
1237
1255
  settings: input.settings ?? getCurrentSettings(),
1238
1256
  isDefault: input.isDefault,
1239
1257
  applyToRoles: input.applyToRoles,
1240
- setRoleDefault: input.setRoleDefault
1258
+ setRoleDefault: input.setRoleDefault,
1259
+ ...Object.keys(roleExpectedUpdatedAtByRoleId).length > 0 ? { roleExpectedUpdatedAtByRoleId } : {},
1260
+ ...Object.keys(roleExpectedUpdatedAtByPerspectiveId).length > 0 ? { roleExpectedUpdatedAtByPerspectiveId } : {}
1241
1261
  };
1242
1262
  if (process.env.NODE_ENV !== "production") {
1243
1263
  console.debug("[DataTable] perspective payload", payload);
@@ -1301,11 +1321,15 @@ function DataTable({
1301
1321
  const deletePerspectiveMutation = useMutation({
1302
1322
  mutationFn: async ({ perspectiveId }) => {
1303
1323
  if (!perspectiveTableId) throw new Error("Missing table id");
1324
+ const existing = perspectiveData?.perspectives.find((p) => p.id === perspectiveId) ?? null;
1304
1325
  await runPerspectiveMutation({
1305
1326
  operation: async () => {
1306
- const call = await apiCall(
1307
- `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/${encodeURIComponent(perspectiveId)}`,
1308
- { method: "DELETE" }
1327
+ const call = await withScopedApiRequestHeaders(
1328
+ buildOptimisticLockHeader(existing?.updatedAt ?? null),
1329
+ () => apiCall(
1330
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/${encodeURIComponent(perspectiveId)}`,
1331
+ { method: "DELETE" }
1332
+ )
1309
1333
  );
1310
1334
  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
1335
  if (!call.ok) {
@@ -1337,16 +1361,29 @@ function DataTable({
1337
1361
  setActivePerspectiveId(null);
1338
1362
  initialPerspectiveAppliedRef.current = false;
1339
1363
  }
1364
+ },
1365
+ onError: () => {
1366
+ if (perspectiveTableId) {
1367
+ void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey });
1368
+ }
1340
1369
  }
1341
1370
  });
1342
1371
  const clearRoleMutation = useMutation({
1343
- mutationFn: async ({ roleId }) => {
1372
+ mutationFn: async ({ roleId, updatedAt, expectedUpdatedAtByPerspectiveId }) => {
1344
1373
  if (!perspectiveTableId) throw new Error("Missing table id");
1374
+ const hasPerRowVersions = expectedUpdatedAtByPerspectiveId && Object.keys(expectedUpdatedAtByPerspectiveId).length > 0;
1345
1375
  await runPerspectiveMutation({
1346
1376
  operation: async () => {
1347
- const call = await apiCall(
1348
- `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/roles/${encodeURIComponent(roleId)}`,
1349
- { method: "DELETE" }
1377
+ const call = await withScopedApiRequestHeaders(
1378
+ hasPerRowVersions ? {} : buildOptimisticLockHeader(updatedAt ?? null),
1379
+ () => apiCall(
1380
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/roles/${encodeURIComponent(roleId)}`,
1381
+ hasPerRowVersions ? {
1382
+ method: "DELETE",
1383
+ headers: { "Content-Type": "application/json" },
1384
+ body: JSON.stringify({ roleExpectedUpdatedAtByPerspectiveId: expectedUpdatedAtByPerspectiveId })
1385
+ } : { method: "DELETE" }
1386
+ )
1350
1387
  );
1351
1388
  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
1389
  if (!call.ok) {
@@ -1378,6 +1415,11 @@ function DataTable({
1378
1415
  initialPerspectiveAppliedRef.current = false;
1379
1416
  }
1380
1417
  }
1418
+ },
1419
+ onError: () => {
1420
+ if (perspectiveTableId) {
1421
+ void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey });
1422
+ }
1381
1423
  }
1382
1424
  });
1383
1425
  const handlePerspectiveActivate = React.useCallback((item, _source) => {
@@ -1397,9 +1439,16 @@ function DataTable({
1397
1439
  const handlePerspectiveDelete = React.useCallback(async (perspectiveId) => {
1398
1440
  await deletePerspectiveMutation.mutateAsync({ perspectiveId });
1399
1441
  }, [deletePerspectiveMutation]);
1400
- const handleClearRole = React.useCallback(async (roleId) => {
1401
- await clearRoleMutation.mutateAsync({ roleId });
1402
- }, [clearRoleMutation]);
1442
+ const handleClearRole = React.useCallback(async (perspective2) => {
1443
+ const expectedUpdatedAtByPerspectiveId = Object.fromEntries(
1444
+ rolePerspectivesForLocking.filter((item) => item.roleId === perspective2.roleId && item.updatedAt).map((item) => [item.id, item.updatedAt])
1445
+ );
1446
+ await clearRoleMutation.mutateAsync({
1447
+ roleId: perspective2.roleId,
1448
+ updatedAt: perspective2.updatedAt ?? null,
1449
+ expectedUpdatedAtByPerspectiveId
1450
+ });
1451
+ }, [clearRoleMutation, rolePerspectivesForLocking]);
1403
1452
  const handleColumnChooserToggle = React.useCallback((key) => {
1404
1453
  const column = table.getColumn(key);
1405
1454
  if (!column) return;