@universe-platform/ui 0.1.0-beta.12 → 0.1.0-beta.15

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/index.mjs CHANGED
@@ -99,6 +99,30 @@ function getDeterministicPastelBadge(key) {
99
99
  return PASTEL_PALETTES[index];
100
100
  }
101
101
 
102
+ // src/utils/permissions.ts
103
+ function getRolePermissions(role) {
104
+ if (role === "VIEWER" || !role) {
105
+ return {
106
+ permissions: {
107
+ canCreate: false,
108
+ canRead: true,
109
+ canUpdate: false,
110
+ canDelete: false
111
+ },
112
+ readOnly: true
113
+ };
114
+ }
115
+ return {
116
+ permissions: {
117
+ canCreate: true,
118
+ canRead: true,
119
+ canUpdate: true,
120
+ canDelete: true
121
+ },
122
+ readOnly: false
123
+ };
124
+ }
125
+
102
126
  // ../shared/src/columns.ts
103
127
  var COLUMN_TYPES = [
104
128
  "STRING",
@@ -376,7 +400,7 @@ import React7 from "react";
376
400
  import React2, { useState as useState2, useEffect } from "react";
377
401
  import { FileText, Upload, X as X2 } from "lucide-react";
378
402
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
379
- function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForDeletion, context }) {
403
+ function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForDeletion, context, readOnly = false }) {
380
404
  const [isOpening, setIsOpening] = useState2(false);
381
405
  const { showError } = useToast();
382
406
  const cellContextId = React2.useMemo(() => {
@@ -477,29 +501,33 @@ function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForD
477
501
  const updated = fileList.filter((_, idx) => idx !== indexToRemove);
478
502
  onChange(updated);
479
503
  };
480
- const renderUploadButton = () => /* @__PURE__ */ jsx2("div", { className: "flex items-center w-full h-8 px-2", children: /* @__PURE__ */ jsxs2(
481
- "button",
482
- {
483
- type: "button",
484
- onClick: openUploadPopup,
485
- disabled: isOpening || !(context == null ? void 0 : context.executeAction),
486
- className: "inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium text-slate-500 bg-slate-100 hover:bg-slate-200 hover:text-slate-700 transition-colors disabled:opacity-50",
487
- children: [
488
- /* @__PURE__ */ jsx2(Upload, { className: "w-3.5 h-3.5" }),
489
- /* @__PURE__ */ jsx2("span", { children: isOpening ? "Loading..." : "Upload File" })
490
- ]
491
- }
492
- ) });
504
+ const renderUploadButton = (iconOnly) => {
505
+ if (readOnly) return null;
506
+ return /* @__PURE__ */ jsxs2(
507
+ "button",
508
+ {
509
+ type: "button",
510
+ onClick: openUploadPopup,
511
+ disabled: isOpening || !(context == null ? void 0 : context.executeAction),
512
+ className: `inline-flex items-center justify-center gap-1.5 rounded-md text-xs font-medium text-slate-500 bg-slate-100 hover:bg-slate-200 hover:text-slate-700 transition-colors disabled:opacity-50 ${iconOnly ? "w-7 h-7 shrink-0" : "px-2 py-1"}`,
513
+ title: "Upload File",
514
+ children: [
515
+ /* @__PURE__ */ jsx2(Upload, { className: "w-3.5 h-3.5 shrink-0" }),
516
+ !iconOnly && /* @__PURE__ */ jsx2("span", { className: "truncate", children: isOpening ? "Loading..." : "Upload File" })
517
+ ]
518
+ }
519
+ );
520
+ };
493
521
  if (fileList.length > 0) {
494
- return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col w-full py-1 gap-1", children: [
495
- fileList.map((file, idx) => /* @__PURE__ */ jsxs2("div", { className: "flex items-center w-full h-8 px-2 gap-1", children: [
522
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center w-full py-1 px-2 gap-1.5", children: [
523
+ fileList.map((file, idx) => /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 max-w-full", children: [
496
524
  /* @__PURE__ */ jsxs2(
497
525
  "a",
498
526
  {
499
527
  href: file.fileUrl || file.url,
500
528
  target: "_blank",
501
529
  rel: "noopener noreferrer",
502
- className: "inline-flex items-center gap-1.5 max-w-[calc(100%-24px)] px-2 py-1 rounded-md text-xs font-medium text-blue-700 bg-blue-50 hover:bg-blue-100 transition-colors truncate",
530
+ className: `inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium text-blue-700 bg-blue-50 hover:bg-blue-100 transition-colors truncate ${readOnly ? "max-w-full" : "max-w-[calc(100%-28px)]"}`,
503
531
  title: file.fileName || "Open file",
504
532
  children: [
505
533
  /* @__PURE__ */ jsx2(FileText, { className: "w-3.5 h-3.5 shrink-0" }),
@@ -507,21 +535,24 @@ function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForD
507
535
  ]
508
536
  }
509
537
  ),
510
- /* @__PURE__ */ jsx2(
538
+ !readOnly && /* @__PURE__ */ jsx2(
511
539
  "button",
512
540
  {
513
541
  type: "button",
514
542
  onClick: () => handleRemoveFile(idx),
515
- className: "shrink-0 p-0.5 rounded hover:bg-rose-100 text-slate-400 hover:text-rose-500 transition-colors",
543
+ className: "shrink-0 inline-flex items-center justify-center w-6 h-6 rounded hover:bg-rose-100 text-slate-400 hover:text-rose-500 transition-colors",
516
544
  title: "Remove file",
517
- children: /* @__PURE__ */ jsx2(X2, { className: "w-3.5 h-3.5" })
545
+ children: /* @__PURE__ */ jsx2(X2, { className: "w-3.5 h-3.5 shrink-0" })
518
546
  }
519
547
  )
520
548
  ] }, file.fileId || idx)),
521
- renderUploadButton()
549
+ renderUploadButton(true)
522
550
  ] });
523
551
  }
524
- return renderUploadButton();
552
+ if (readOnly) {
553
+ return /* @__PURE__ */ jsx2("span", { className: "px-2 py-1 flex items-center h-full text-slate-300 text-xs italic", children: "-" });
554
+ }
555
+ return /* @__PURE__ */ jsx2("div", { className: "flex items-center w-full h-8 px-2", children: renderUploadButton(false) });
525
556
  }
526
557
 
527
558
  // src/components/TableBlock/fields/DatePickerCell.tsx
@@ -2088,11 +2119,19 @@ function getCurrencySymbol(code) {
2088
2119
  return CURRENCY_SYMBOLS[code.toUpperCase()] || code;
2089
2120
  }
2090
2121
  var FORM_INPUTS = {
2091
- DATE: ({ value, onChange, column }) => /* @__PURE__ */ jsx7(DatePickerCell, { value, onChange, column }),
2092
- CURRENCY: ({ value, column, onChange }) => {
2122
+ DATE: ({ value, onChange, column, readOnly }) => /* @__PURE__ */ jsx7(DatePickerCell, { value, onChange, column, readOnly }),
2123
+ CURRENCY: ({ value, column, onChange, readOnly }) => {
2093
2124
  var _a;
2094
2125
  const symbol = getCurrencySymbol((_a = column.config) == null ? void 0 : _a.currencySymbol);
2095
2126
  const [isFocused, setIsFocused] = React7.useState(false);
2127
+ if (readOnly) {
2128
+ const num = Number(value);
2129
+ const formattedValue = value !== null && value !== void 0 && !isNaN(num) ? new Intl.NumberFormat("en-US").format(num) : "";
2130
+ return /* @__PURE__ */ jsxs7("span", { className: "px-2 py-1 flex items-center h-full text-sm select-text truncate", children: [
2131
+ symbol && /* @__PURE__ */ jsx7("span", { className: "text-slate-400 mr-1", children: symbol }),
2132
+ formattedValue
2133
+ ] });
2134
+ }
2096
2135
  const displayValue = !isFocused && value !== void 0 && value !== null && !isNaN(Number(value)) ? new Intl.NumberFormat("en-US").format(Number(value)) : value !== void 0 && value !== null ? value : "";
2097
2136
  return /* @__PURE__ */ jsxs7("div", { className: "relative w-full h-full", children: [
2098
2137
  symbol && /* @__PURE__ */ jsx7("span", { className: "absolute left-2 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none text-sm", children: symbol }),
@@ -2113,18 +2152,19 @@ var FORM_INPUTS = {
2113
2152
  )
2114
2153
  ] });
2115
2154
  },
2116
- TIME: ({ value, onChange, column }) => /* @__PURE__ */ jsx7(TimePickerCell, { value, onChange, column }),
2117
- LINK: ({ value, onChange, column }) => /* @__PURE__ */ jsx7(LinkPickerCell, { value, onChange, column }),
2118
- BOOLEAN: ({ value, onChange }) => /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center w-full h-full p-2", children: /* @__PURE__ */ jsx7(
2155
+ TIME: ({ value, onChange, column, readOnly }) => /* @__PURE__ */ jsx7(TimePickerCell, { value, onChange, column, readOnly }),
2156
+ LINK: ({ value, onChange, column, readOnly }) => /* @__PURE__ */ jsx7(LinkPickerCell, { value, onChange, column, readOnly }),
2157
+ BOOLEAN: ({ value, onChange, readOnly }) => /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center w-full h-full p-2", children: /* @__PURE__ */ jsx7(
2119
2158
  "input",
2120
2159
  {
2121
2160
  type: "checkbox",
2122
2161
  checked: !!value,
2162
+ disabled: readOnly,
2123
2163
  onChange: (e) => onChange(e.target.checked),
2124
- className: "w-4 h-4 rounded border-slate-300 text-brand"
2164
+ className: `w-4 h-4 rounded border-slate-300 text-brand ${readOnly ? "cursor-not-allowed opacity-70" : "cursor-pointer"}`
2125
2165
  }
2126
2166
  ) }),
2127
- GOOGLE_DRIVE_FILE: ({ value, onChange, column, rowId, context }) => /* @__PURE__ */ jsx7(
2167
+ GOOGLE_DRIVE_FILE: ({ value, onChange, column, rowId, context, readOnly }) => /* @__PURE__ */ jsx7(
2128
2168
  DrivePickerCell,
2129
2169
  {
2130
2170
  value: value || [],
@@ -2133,34 +2173,60 @@ var FORM_INPUTS = {
2133
2173
  rowId,
2134
2174
  tableName: context == null ? void 0 : context.tableName,
2135
2175
  onMarkForDeletion: context == null ? void 0 : context.onMarkForDeletion,
2136
- context
2176
+ context,
2177
+ readOnly
2137
2178
  }
2138
2179
  ),
2139
- RELATION: ({ value, onChange, column, context, rowId }) => /* @__PURE__ */ jsx7(
2180
+ RELATION: ({ value, onChange, column, context, rowId, readOnly }) => /* @__PURE__ */ jsx7(
2140
2181
  RelationPickerCell,
2141
2182
  {
2142
2183
  value,
2143
2184
  onChange,
2144
2185
  column,
2145
2186
  context,
2146
- rowId
2187
+ rowId,
2188
+ readOnly
2147
2189
  }
2148
2190
  ),
2149
- NUMBER: ({ value, onChange }) => /* @__PURE__ */ jsx7(
2150
- "input",
2151
- {
2152
- type: "number",
2153
- className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2154
- value: value !== void 0 && value !== null ? value : "",
2155
- onChange: (e) => {
2156
- const val = e.target.value;
2157
- onChange(val === "" ? null : Number(val));
2158
- }
2191
+ NUMBER: ({ value, onChange, readOnly }) => {
2192
+ if (readOnly) {
2193
+ return /* @__PURE__ */ jsx7("span", { className: "px-2 py-1 flex items-center h-full text-sm select-text truncate", children: value !== void 0 && value !== null ? value : "" });
2159
2194
  }
2160
- ),
2161
- PHONE: ({ value, onChange, column }) => {
2195
+ return /* @__PURE__ */ jsx7(
2196
+ "input",
2197
+ {
2198
+ type: "number",
2199
+ className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2200
+ value: value !== void 0 && value !== null ? value : "",
2201
+ onChange: (e) => {
2202
+ const val = e.target.value;
2203
+ onChange(val === "" ? null : Number(val));
2204
+ }
2205
+ }
2206
+ );
2207
+ },
2208
+ PHONE: ({ value, onChange, column, readOnly }) => {
2162
2209
  var _a;
2163
2210
  const enableWhatsApp = (_a = column == null ? void 0 : column.config) == null ? void 0 : _a.enableWhatsApp;
2211
+ const formatted = value ? formatPhoneDisplay(String(value)) : "";
2212
+ if (readOnly) {
2213
+ return /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between w-full h-full px-2 gap-1.5 overflow-hidden", children: [
2214
+ /* @__PURE__ */ jsx7("span", { className: "truncate text-sm", dir: "ltr", children: formatted }),
2215
+ enableWhatsApp && value && /* @__PURE__ */ jsx7(
2216
+ "button",
2217
+ {
2218
+ type: "button",
2219
+ className: "text-emerald-500 cursor-pointer hover:opacity-80 transition-opacity shrink-0 p-1 rounded hover:bg-emerald-50",
2220
+ title: "\u05E4\u05EA\u05D9\u05D7\u05EA \u05D5\u05D5\u05D0\u05D8\u05E1\u05D0\u05E4",
2221
+ onClick: (e) => {
2222
+ e.stopPropagation();
2223
+ openWhatsAppChat(String(value));
2224
+ },
2225
+ children: /* @__PURE__ */ jsx7("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx7("path", { d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a5.816 5.816 0 00-.571-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" }) })
2226
+ }
2227
+ )
2228
+ ] });
2229
+ }
2164
2230
  return /* @__PURE__ */ jsxs7("div", { className: "flex items-center w-full h-full px-2 gap-1.5 overflow-hidden", children: [
2165
2231
  /* @__PURE__ */ jsx7(
2166
2232
  "input",
@@ -2187,85 +2253,9 @@ var FORM_INPUTS = {
2187
2253
  )
2188
2254
  ] });
2189
2255
  },
2190
- GOOGLE_CONTACT: ({ value, column, rowId, onChange, context }) => {
2256
+ GOOGLE_CONTACT: ({ value, column, rowId, onChange, context, readOnly }) => {
2191
2257
  const [isSyncing, setIsSyncing] = React7.useState(false);
2192
2258
  const { addToast } = useToast();
2193
- const googleClientIdRef = React7.useRef(null);
2194
- const ensureGisScript = React7.useCallback(() => {
2195
- return new Promise((resolve, reject) => {
2196
- var _a, _b, _c;
2197
- if ((_c = (_b = (_a = window.google) == null ? void 0 : _a.accounts) == null ? void 0 : _b.oauth2) == null ? void 0 : _c.initTokenClient) {
2198
- resolve();
2199
- return;
2200
- }
2201
- if (document.querySelector('script[src*="accounts.google.com/gsi/client"]')) {
2202
- const waitForLoad = setInterval(() => {
2203
- var _a2, _b2, _c2;
2204
- if ((_c2 = (_b2 = (_a2 = window.google) == null ? void 0 : _a2.accounts) == null ? void 0 : _b2.oauth2) == null ? void 0 : _c2.initTokenClient) {
2205
- clearInterval(waitForLoad);
2206
- resolve();
2207
- }
2208
- }, 100);
2209
- setTimeout(() => {
2210
- clearInterval(waitForLoad);
2211
- reject(new Error("GIS script load timeout"));
2212
- }, 1e4);
2213
- return;
2214
- }
2215
- const script = document.createElement("script");
2216
- script.src = "https://accounts.google.com/gsi/client";
2217
- script.async = true;
2218
- script.onload = () => resolve();
2219
- script.onerror = () => reject(new Error("Failed to load Google Identity Services"));
2220
- document.head.appendChild(script);
2221
- });
2222
- }, []);
2223
- const requestGisToken = React7.useCallback(async (hint) => {
2224
- await ensureGisScript();
2225
- if (!googleClientIdRef.current && (context == null ? void 0 : context.executeAction)) {
2226
- try {
2227
- const config = await context.executeAction("GET_GOOGLE_CONFIG", {});
2228
- googleClientIdRef.current = (config == null ? void 0 : config.clientId) || "";
2229
- } catch (e) {
2230
- throw new Error("Failed to fetch Google configuration");
2231
- }
2232
- }
2233
- const clientId = googleClientIdRef.current;
2234
- if (!clientId) {
2235
- throw new Error("Google Client ID is not configured");
2236
- }
2237
- const accessToken = await new Promise((resolve, reject) => {
2238
- const tokenClient = window.google.accounts.oauth2.initTokenClient({
2239
- client_id: clientId,
2240
- scope: "https://www.googleapis.com/auth/contacts https://www.googleapis.com/auth/userinfo.email",
2241
- prompt: hint ? "" : "select_account",
2242
- hint: hint || void 0,
2243
- callback: (tokenResponse) => {
2244
- if (tokenResponse == null ? void 0 : tokenResponse.access_token) {
2245
- resolve(tokenResponse.access_token);
2246
- } else {
2247
- reject(new Error("Google authorization was cancelled or failed"));
2248
- }
2249
- },
2250
- error_callback: (err) => {
2251
- reject(new Error((err == null ? void 0 : err.message) || "Google authorization error"));
2252
- }
2253
- });
2254
- tokenClient.requestAccessToken({ hint: hint || void 0 });
2255
- });
2256
- let email = "";
2257
- try {
2258
- const res = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
2259
- headers: { Authorization: `Bearer ${accessToken}` }
2260
- });
2261
- if (res.ok) {
2262
- const info = await res.json();
2263
- email = info.email || "";
2264
- }
2265
- } catch (e) {
2266
- }
2267
- return { accessToken, email };
2268
- }, [ensureGisScript, context]);
2269
2259
  const handleSync = async () => {
2270
2260
  var _a, _b, _c, _d, _e, _f;
2271
2261
  if (!(context == null ? void 0 : context.executeAction)) return;
@@ -2300,17 +2290,6 @@ var FORM_INPUTS = {
2300
2290
  setIsSyncing(false);
2301
2291
  return;
2302
2292
  }
2303
- let accessToken;
2304
- let userEmail = "";
2305
- try {
2306
- const gisResult = await requestGisToken();
2307
- accessToken = gisResult.accessToken;
2308
- userEmail = gisResult.email;
2309
- } catch (gisError) {
2310
- addToast({ type: "warning", message: gisError.message || "\u05D4\u05D4\u05E8\u05E9\u05D0\u05D4 \u05DC\u05D2\u05D5\u05D2\u05DC \u05E0\u05DB\u05E9\u05DC\u05D4" });
2311
- setIsSyncing(false);
2312
- return;
2313
- }
2314
2293
  const payload = {
2315
2294
  nameParts: mapping.nameParts || [],
2316
2295
  phoneCol: mapping.phoneCol,
@@ -2320,9 +2299,7 @@ var FORM_INPUTS = {
2320
2299
  tableMetaId: targetTableMetaId,
2321
2300
  tableId: targetTableMetaId,
2322
2301
  recordId: rowId,
2323
- orgId: targetOrgId,
2324
- accessToken,
2325
- userEmail
2302
+ orgId: targetOrgId
2326
2303
  };
2327
2304
  const res = await context.executeAction("SYNC_GOOGLE_CONTACT", payload);
2328
2305
  if (res && res.synced) {
@@ -2342,39 +2319,44 @@ var FORM_INPUTS = {
2342
2319
  }
2343
2320
  }
2344
2321
  } catch (error) {
2345
- console.error("Failed to sync contact:", error);
2346
- addToast({ type: "warning", message: "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05E1\u05E0\u05DB\u05E8\u05D5\u05DF \u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8: " + error.message });
2322
+ const errorMessage = (error == null ? void 0 : error.message) || (error == null ? void 0 : error.error) || (typeof error === "string" ? error : "");
2323
+ if (errorMessage.includes("OAUTH_REQUIRED") || errorMessage.includes("Access Denied") || errorMessage.includes("No refresh token")) {
2324
+ addToast({
2325
+ type: "warning",
2326
+ message: "\u05D8\u05E8\u05DD \u05D4\u05D5\u05D2\u05D3\u05E8 \u05D7\u05E9\u05D1\u05D5\u05DF \u05D2\u05D5\u05D2\u05DC \u05D0\u05E8\u05D2\u05D5\u05E0\u05D9. \u05D9\u05E9 \u05DC\u05E4\u05E0\u05D5\u05EA \u05DC\u05DE\u05E0\u05D4\u05DC \u05D4\u05DE\u05E2\u05E8\u05DB\u05EA \u05DC\u05D7\u05D9\u05D1\u05D5\u05E8 \u05D4\u05D7\u05E9\u05D1\u05D5\u05DF \u05D1\u05D4\u05D2\u05D3\u05E8\u05D5\u05EA \u05D4\u05D0\u05E8\u05D2\u05D5\u05DF."
2327
+ });
2328
+ } else {
2329
+ console.error("Failed to sync contact:", error);
2330
+ addToast({
2331
+ type: "warning",
2332
+ message: "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05E1\u05E0\u05DB\u05E8\u05D5\u05DF \u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8: " + error.message
2333
+ });
2334
+ }
2347
2335
  } finally {
2348
2336
  setIsSyncing(false);
2349
2337
  }
2350
2338
  };
2351
2339
  const handleUnlink = async () => {
2352
- let accessToken;
2353
2340
  const targetTableMetaId = (context == null ? void 0 : context.tableMetaId) || (context == null ? void 0 : context.tableId);
2354
2341
  const targetOrgId = context == null ? void 0 : context.orgId;
2355
2342
  try {
2356
- let emailHint = value == null ? void 0 : value.userEmail;
2357
- if (!emailHint && (value == null ? void 0 : value.url)) {
2358
- try {
2359
- const parsed = new URL(value.url);
2360
- emailHint = parsed.searchParams.get("authuser") || void 0;
2361
- } catch (e) {
2362
- }
2343
+ if ((value == null ? void 0 : value.resourceName) && (context == null ? void 0 : context.executeAction)) {
2344
+ await context.executeAction("DELETE_GOOGLE_CONTACT", {
2345
+ resourceName: value.resourceName,
2346
+ tableMetaId: targetTableMetaId,
2347
+ tableId: targetTableMetaId,
2348
+ recordId: rowId,
2349
+ orgId: targetOrgId
2350
+ });
2363
2351
  }
2364
- const auth = await requestGisToken(emailHint);
2365
- accessToken = auth.accessToken;
2366
- } catch (err) {
2367
- console.warn("[handleUnlink] Could not obtain token via GIS, falling back to server default token:", err);
2368
- }
2369
- if ((value == null ? void 0 : value.resourceName) && (context == null ? void 0 : context.executeAction)) {
2370
- await context.executeAction("DELETE_GOOGLE_CONTACT", {
2371
- resourceName: value.resourceName,
2372
- accessToken,
2373
- tableMetaId: targetTableMetaId,
2374
- tableId: targetTableMetaId,
2375
- recordId: rowId,
2376
- orgId: targetOrgId
2377
- });
2352
+ } catch (error) {
2353
+ if (error.message === "OAUTH_REQUIRED" || error.error === "OAUTH_REQUIRED") {
2354
+ addToast({ type: "warning", message: "\u05D8\u05E8\u05DD \u05D4\u05D5\u05D2\u05D3\u05E8 \u05D7\u05E9\u05D1\u05D5\u05DF \u05D2\u05D5\u05D2\u05DC \u05D0\u05E8\u05D2\u05D5\u05E0\u05D9. \u05D9\u05E9 \u05DC\u05E4\u05E0\u05D5\u05EA \u05DC\u05DE\u05E0\u05D4\u05DC \u05D4\u05DE\u05E2\u05E8\u05DB\u05EA \u05DC\u05D7\u05D9\u05D1\u05D5\u05E8 \u05D4\u05D7\u05E9\u05D1\u05D5\u05DF \u05D1\u05D4\u05D2\u05D3\u05E8\u05D5\u05EA \u05D4\u05D0\u05E8\u05D2\u05D5\u05DF." });
2355
+ return;
2356
+ }
2357
+ console.error("Failed to unlink contact:", error);
2358
+ addToast({ type: "warning", message: "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05D4\u05E1\u05E8\u05EA \u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8: " + error.message });
2359
+ return;
2378
2360
  }
2379
2361
  if (context == null ? void 0 : context.updateQuietly) {
2380
2362
  context.updateQuietly(rowId, column.name, null);
@@ -2394,7 +2376,7 @@ var FORM_INPUTS = {
2394
2376
  if (value && value.synced && value.url) {
2395
2377
  return /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5 h-8 px-2 group whitespace-nowrap overflow-hidden max-w-full", children: [
2396
2378
  /* @__PURE__ */ jsx7("a", { href: value.url, target: "_blank", rel: "noopener noreferrer", className: "text-blue-600 hover:underline flex-1 truncate text-xs whitespace-nowrap", children: "\u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8 \u05D2\u05D5\u05D2\u05DC" }),
2397
- /* @__PURE__ */ jsx7(
2379
+ !readOnly && /* @__PURE__ */ jsx7(
2398
2380
  "button",
2399
2381
  {
2400
2382
  type: "button",
@@ -2406,6 +2388,9 @@ var FORM_INPUTS = {
2406
2388
  )
2407
2389
  ] });
2408
2390
  }
2391
+ if (readOnly) {
2392
+ return /* @__PURE__ */ jsx7("span", { className: "px-2 py-1 flex items-center h-full text-slate-300 text-xs italic", children: "\u05DC\u05D0 \u05DE\u05E1\u05D5\u05E0\u05DB\u05E8\u05DF" });
2393
+ }
2409
2394
  return /* @__PURE__ */ jsx7("div", { className: "flex items-center h-8 px-2 whitespace-nowrap overflow-hidden max-w-full", children: /* @__PURE__ */ jsxs7(
2410
2395
  "button",
2411
2396
  {
@@ -2423,15 +2408,20 @@ var FORM_INPUTS = {
2423
2408
  }
2424
2409
  ) });
2425
2410
  },
2426
- DEFAULT: ({ value, onChange }) => /* @__PURE__ */ jsx7(
2427
- "input",
2428
- {
2429
- type: "text",
2430
- className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2431
- value: value !== void 0 && value !== null ? String(value) : "",
2432
- onChange: (e) => onChange(e.target.value)
2411
+ DEFAULT: ({ value, onChange, readOnly }) => {
2412
+ if (readOnly) {
2413
+ return /* @__PURE__ */ jsx7("span", { className: "px-2 py-1 flex items-center h-full text-sm select-text truncate", children: value !== void 0 && value !== null ? String(value) : "" });
2433
2414
  }
2434
- ),
2415
+ return /* @__PURE__ */ jsx7(
2416
+ "input",
2417
+ {
2418
+ type: "text",
2419
+ className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2420
+ value: value !== void 0 && value !== null ? String(value) : "",
2421
+ onChange: (e) => onChange(e.target.value)
2422
+ }
2423
+ );
2424
+ },
2435
2425
  SUB_TABLE: ({ value, column }) => {
2436
2426
  var _a, _b, _c;
2437
2427
  if (!Array.isArray(value)) return /* @__PURE__ */ jsx7("span", { className: "px-2 py-1 flex items-center h-full text-slate-300 text-xs italic", children: "\u05E8\u05D9\u05E7" });
@@ -2841,7 +2831,8 @@ function SubTableCell({
2841
2831
  refetchRelation,
2842
2832
  onCommit,
2843
2833
  onQuietUpdate,
2844
- width
2834
+ width,
2835
+ readOnly
2845
2836
  }) {
2846
2837
  var _a, _b, _c, _d, _e;
2847
2838
  const colType = ((_a = column.type) == null ? void 0 : _a.toUpperCase()) || "DEFAULT";
@@ -2876,6 +2867,7 @@ function SubTableCell({
2876
2867
  }
2877
2868
  }, [serverValue]);
2878
2869
  const commit = useCallback8(async (valToCommit) => {
2870
+ if (readOnly) return true;
2879
2871
  if (isCommittingRef.current) return false;
2880
2872
  if (!isDirtyRef.current) return true;
2881
2873
  if (valToCommit === serverValueRef.current) {
@@ -2992,7 +2984,9 @@ function SubTableCell({
2992
2984
  column,
2993
2985
  rowId: record.id,
2994
2986
  onChange: handleChange,
2987
+ readOnly,
2995
2988
  context: __spreadProps(__spreadValues({}, context), {
2989
+ readOnly,
2996
2990
  tableName: (targetSchema == null ? void 0 : targetSchema.name) || (targetSchema == null ? void 0 : targetSchema.tableName) || (targetSchema == null ? void 0 : targetSchema.title) || (targetSchema == null ? void 0 : targetSchema.id) || "Table",
2997
2991
  relationOptions,
2998
2992
  updateRelationOptions: (name, opts) => setRelationOptions((prev) => __spreadProps(__spreadValues({}, prev), { [name]: opts })),
@@ -3022,8 +3016,9 @@ function SubTableCell({
3022
3016
  }
3023
3017
  );
3024
3018
  }
3025
- function SubTableViewer({ rowId, column, value, onChange, context }) {
3019
+ function SubTableViewer({ rowId, column, value, onChange, context, readOnly }) {
3026
3020
  var _a, _b, _c, _d, _e, _f;
3021
+ const isReadOnly = readOnly != null ? readOnly : Boolean(context == null ? void 0 : context.readOnly);
3027
3022
  const { addToast } = useToast();
3028
3023
  const entries = Array.isArray(value) ? value : [];
3029
3024
  const [linkedRecords, setLinkedRecords] = useState10(() => {
@@ -3550,7 +3545,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3550
3545
  return /* @__PURE__ */ jsxs9("div", { className: "p-3 animate-in fade-in slide-in-from-top-2 duration-200", dir: "rtl", children: [
3551
3546
  /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-4 mb-2.5", children: [
3552
3547
  /* @__PURE__ */ jsx9("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx9("span", { className: "text-xs font-bold text-slate-600", children: (_e = targetSchema == null ? void 0 : targetSchema.name) != null ? _e : (_d = column.config) == null ? void 0 : _d.referencedTableName }) }),
3553
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
3548
+ !isReadOnly && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
3554
3549
  /* @__PURE__ */ jsxs9("div", { className: "relative", ref: searchRef, children: [
3555
3550
  /* @__PURE__ */ jsxs9(
3556
3551
  "button",
@@ -3615,13 +3610,13 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3615
3610
  )
3616
3611
  ] })
3617
3612
  ] }),
3618
- isLoading ? /* @__PURE__ */ jsx9("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ jsx9("div", { className: "w-5 h-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" }) }) : entries.length === 0 && !isCreating ? /* @__PURE__ */ jsx9("div", { className: "text-center py-6 text-xs text-slate-400", children: "\u05D0\u05D9\u05DF \u05E8\u05E9\u05D5\u05DE\u05D5\u05EA \u05DE\u05E7\u05D5\u05E9\u05E8\u05D5\u05EA. \u05D4\u05E9\u05EA\u05DE\u05E9 \u05D1\u05D7\u05D9\u05E4\u05D5\u05E9 \u05DB\u05D3\u05D9 \u05DC\u05E7\u05E9\u05E8 \u05E8\u05E9\u05D5\u05DE\u05D5\u05EA \u05E7\u05D9\u05D9\u05DE\u05D5\u05EA, \u05D0\u05D5 \u05D4\u05D5\u05E1\u05E3 \u05E8\u05E9\u05D5\u05DE\u05D4 \u05D7\u05D3\u05E9\u05D4." }) : /* @__PURE__ */ jsx9("div", { className: "overflow-x-auto pb-1", children: /* @__PURE__ */ jsxs9("table", { className: "w-full text-xs border-collapse table-auto", children: [
3613
+ isLoading ? /* @__PURE__ */ jsx9("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ jsx9("div", { className: "w-5 h-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" }) }) : entries.length === 0 && !isCreating ? /* @__PURE__ */ jsx9("div", { className: "text-center py-6 text-xs text-slate-400", children: isReadOnly ? "\u05D0\u05D9\u05DF \u05E8\u05E9\u05D5\u05DE\u05D5\u05EA \u05DE\u05E7\u05D5\u05E9\u05E8\u05D5\u05EA \u05DC\u05D4\u05E6\u05D2\u05D4." : "\u05D0\u05D9\u05DF \u05E8\u05E9\u05D5\u05DE\u05D5\u05EA \u05DE\u05E7\u05D5\u05E9\u05E8\u05D5\u05EA. \u05D4\u05E9\u05EA\u05DE\u05E9 \u05D1\u05D7\u05D9\u05E4\u05D5\u05E9 \u05DB\u05D3\u05D9 \u05DC\u05E7\u05E9\u05E8 \u05E8\u05E9\u05D5\u05DE\u05D5\u05EA \u05E7\u05D9\u05D9\u05DE\u05D5\u05EA, \u05D0\u05D5 \u05D4\u05D5\u05E1\u05E3 \u05E8\u05E9\u05D5\u05DE\u05D4 \u05D7\u05D3\u05E9\u05D4." }) : /* @__PURE__ */ jsx9("div", { className: "overflow-x-auto pb-1", children: /* @__PURE__ */ jsxs9("table", { className: "w-full text-xs border-collapse table-auto", children: [
3619
3614
  /* @__PURE__ */ jsx9("thead", { children: /* @__PURE__ */ jsxs9("tr", { className: "bg-slate-100/80 h-12 min-h-[48px]", children: [
3620
3615
  /* @__PURE__ */ jsx9("th", { className: "border border-slate-200 px-2 py-1.5 text-center w-10 font-semibold text-slate-500 h-12 min-h-[48px] align-middle", style: { width: 40, minWidth: 40, maxWidth: 40 }, children: "\u05E0\u05D1\u05D7\u05E8" }),
3621
3616
  targetColumns.map((col) => {
3622
- const dragProps = getHeaderDragProps(col.name);
3623
- const isDragging = draggedColName === col.name;
3624
- const isDragOver = dragOverColName === col.name;
3617
+ const dragProps = isReadOnly ? {} : getHeaderDragProps(col.name);
3618
+ const isDragging = !isReadOnly && draggedColName === col.name;
3619
+ const isDragOver = !isReadOnly && dragOverColName === col.name;
3625
3620
  const colMinWidth = calculateMinColumnWidth(col.name, true, col.type);
3626
3621
  const currentWidth = Math.max(colMinWidth, columnWidths[col.name] || 150);
3627
3622
  return /* @__PURE__ */ jsxs9(
@@ -3630,7 +3625,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3630
3625
  style: { width: currentWidth, minWidth: currentWidth, maxWidth: currentWidth },
3631
3626
  title: col.name
3632
3627
  }, dragProps), {
3633
- className: `relative border border-slate-200 px-3 py-1.5 font-semibold text-slate-600 select-none cursor-grab active:cursor-grabbing transition-colors h-12 min-h-[48px] align-middle ${isDragging ? "opacity-40 bg-slate-200 border-dashed border-2 border-brand" : ""} ${isDragOver ? "bg-blue-50 border-r-4 border-r-brand text-brand font-bold" : "hover:bg-slate-200/60"}`,
3628
+ className: `relative border border-slate-200 px-3 py-1.5 font-semibold text-slate-600 select-none ${isReadOnly ? "cursor-default" : "cursor-grab active:cursor-grabbing"} transition-colors h-12 min-h-[48px] align-middle ${isDragging ? "opacity-40 bg-slate-200 border-dashed border-2 border-brand" : ""} ${isDragOver ? "bg-blue-50 border-r-4 border-r-brand text-brand font-bold" : isReadOnly ? "" : "hover:bg-slate-200/60"}`,
3634
3629
  children: [
3635
3630
  /* @__PURE__ */ jsx9(
3636
3631
  "div",
@@ -3646,7 +3641,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3646
3641
  ),
3647
3642
  /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-1.5 group overflow-hidden w-full", title: col.name, children: [
3648
3643
  /* @__PURE__ */ jsx9(ColumnHeaderTitle, { name: col.name, width: currentWidth, isSubTable: true }),
3649
- /* @__PURE__ */ jsx9(
3644
+ !isReadOnly && /* @__PURE__ */ jsx9(
3650
3645
  "svg",
3651
3646
  {
3652
3647
  className: "w-3.5 h-3.5 text-slate-400 opacity-40 group-hover:opacity-100 transition-opacity shrink-0 cursor-grab",
@@ -3675,8 +3670,9 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3675
3670
  {
3676
3671
  type: "checkbox",
3677
3672
  checked: entry.isSelected,
3673
+ disabled: isReadOnly,
3678
3674
  onChange: () => handleToggleSelected(entry.recordId),
3679
- className: "w-3.5 h-3.5 rounded border-slate-300 text-blue-600 cursor-pointer"
3675
+ className: `w-3.5 h-3.5 rounded border-slate-300 text-blue-600 ${isReadOnly ? "cursor-not-allowed opacity-60" : "cursor-pointer"}`
3680
3676
  }
3681
3677
  ) }),
3682
3678
  targetColumns.map((col) => {
@@ -3706,7 +3702,8 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3706
3702
  refetchRelation,
3707
3703
  onCommit: handleCommitRecordCell,
3708
3704
  onQuietUpdate: handleQuietSubTableCellChange,
3709
- width: currentWidth
3705
+ width: currentWidth,
3706
+ readOnly: isReadOnly
3710
3707
  },
3711
3708
  `${record.id}_${col.name}`
3712
3709
  );
@@ -3733,7 +3730,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3733
3730
  }
3734
3731
  return null;
3735
3732
  })(),
3736
- /* @__PURE__ */ jsx9(
3733
+ !isReadOnly && /* @__PURE__ */ jsx9(
3737
3734
  "button",
3738
3735
  {
3739
3736
  type: "button",
@@ -3750,7 +3747,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3750
3747
  /* @__PURE__ */ jsx9("td", { className: "border border-slate-200 bg-white pointer-events-none" })
3751
3748
  ] }, entry.recordId);
3752
3749
  }),
3753
- isCreating && /* @__PURE__ */ jsxs9("tr", { className: "bg-emerald-50/40", children: [
3750
+ isCreating && !isReadOnly && /* @__PURE__ */ jsxs9("tr", { className: "bg-emerald-50/40", children: [
3754
3751
  /* @__PURE__ */ jsx9("td", { className: "border border-emerald-200 px-2 py-1 text-center", style: { width: 40, minWidth: 40, maxWidth: 40 }, children: /* @__PURE__ */ jsx9("span", { className: "text-emerald-500 font-bold text-[10px]", children: "\u05D7\u05D3\u05E9" }) }),
3755
3752
  targetColumns.map((col) => {
3756
3753
  var _a2, _b2, _c2, _d2, _e2;
@@ -4265,10 +4262,21 @@ function TableBlockClient({
4265
4262
  componentId,
4266
4263
  orgId,
4267
4264
  className = "",
4268
- "data-testid": dataTestId
4265
+ "data-testid": dataTestId,
4266
+ role,
4267
+ permissions,
4268
+ readOnly
4269
4269
  }) {
4270
4270
  const { showError, addToast } = useToast();
4271
4271
  const [isExporting, setIsExporting] = useState12(false);
4272
+ const resolvedRole = role ? getRolePermissions(role) : void 0;
4273
+ const effectivePermissions = permissions || (resolvedRole == null ? void 0 : resolvedRole.permissions) || {
4274
+ canCreate: !readOnly,
4275
+ canRead: true,
4276
+ canUpdate: !readOnly,
4277
+ canDelete: !readOnly
4278
+ };
4279
+ const effectiveReadOnly = readOnly != null ? readOnly : resolvedRole ? resolvedRole.readOnly : !effectivePermissions.canUpdate && !effectivePermissions.canCreate && !effectivePermissions.canDelete;
4272
4280
  const tableKey = (schema == null ? void 0 : schema.tableMetaId) || (schema == null ? void 0 : schema.tableId) || (schema == null ? void 0 : schema.id) || (schema == null ? void 0 : schema.name) || "default";
4273
4281
  const { columnWidths, startResize } = useColumnResize({
4274
4282
  storageKey: `table_widths_${tableKey}`,
@@ -5397,14 +5405,29 @@ function TableBlockClient({
5397
5405
  }
5398
5406
  const result = (res == null ? void 0 : res.data) || res;
5399
5407
  if ((result == null ? void 0 : result.success) === false) {
5400
- throw new Error(result.error || "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05D9\u05D9\u05E6\u05D5\u05D0 \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD");
5408
+ throw new Error(result.error || "Failed to export data");
5401
5409
  }
5410
+ const base64Data = result == null ? void 0 : result.base64Data;
5402
5411
  const csvString = (result == null ? void 0 : result.csvString) || (result == null ? void 0 : result.csv);
5403
- if (!csvString) {
5404
- throw new Error("\u05DC\u05D0 \u05D4\u05EA\u05E7\u05D1\u05DC\u05D5 \u05E0\u05EA\u05D5\u05E0\u05D9 CSV \u05DE\u05D4\u05E9\u05E8\u05EA");
5412
+ if (!base64Data && !csvString) {
5413
+ throw new Error("No export data received from server");
5414
+ }
5415
+ let blob;
5416
+ const defaultFileName = `${((schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.name) || "table").trim()}_export.xlsx`;
5417
+ let fileName = (result == null ? void 0 : result.fileName) || defaultFileName;
5418
+ if (base64Data) {
5419
+ const byteCharacters = atob(base64Data);
5420
+ const byteNumbers = new Uint8Array(byteCharacters.length);
5421
+ for (let i = 0; i < byteCharacters.length; i++) {
5422
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
5423
+ }
5424
+ blob = new Blob([byteNumbers], {
5425
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
5426
+ });
5427
+ } else {
5428
+ fileName = fileName.replace(/\.xlsx$/i, ".csv");
5429
+ blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });
5405
5430
  }
5406
- const fileName = (result == null ? void 0 : result.fileName) || `${((schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.name) || "table").trim()}_export.csv`;
5407
- const blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });
5408
5431
  const url = URL.createObjectURL(blob);
5409
5432
  const link = document.createElement("a");
5410
5433
  link.href = url;
@@ -5413,6 +5436,13 @@ function TableBlockClient({
5413
5436
  link.click();
5414
5437
  document.body.removeChild(link);
5415
5438
  URL.revokeObjectURL(url);
5439
+ if (result == null ? void 0 : result.isTruncated) {
5440
+ addToast({
5441
+ message: "\u05D9\u05D5\u05E6\u05D0\u05D5 10,000 \u05D4\u05E9\u05D5\u05E8\u05D5\u05EA \u05D4\u05E8\u05D0\u05E9\u05D5\u05E0\u05D5\u05EA. \u05DE\u05D5\u05DE\u05DC\u05E5 \u05DC\u05E1\u05E0\u05DF \u05D0\u05EA \u05D4\u05D8\u05D1\u05DC\u05D4 \u05DB\u05D3\u05D9 \u05DC\u05E6\u05DE\u05E6\u05DD \u05D0\u05EA \u05D4\u05DB\u05DE\u05D5\u05EA",
5442
+ type: "warning",
5443
+ duration: 5e3
5444
+ });
5445
+ }
5416
5446
  } catch (err) {
5417
5447
  console.error("[TableBlock] Export to CSV failed:", err);
5418
5448
  showError((err == null ? void 0 : err.message) || String(err), "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05D9\u05D9\u05E6\u05D5\u05D0 \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD");
@@ -5435,7 +5465,7 @@ function TableBlockClient({
5435
5465
  /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center justify-between gap-3 p-3.5 sm:p-4 bg-slate-50 border-b border-slate-200 shrink-0", children: [
5436
5466
  /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-2.5", children: [
5437
5467
  ((schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.name)) && /* @__PURE__ */ jsx11("div", { className: "font-bold text-slate-800 text-sm sm:text-base border-l border-slate-300 pl-3 ml-1", children: /* @__PURE__ */ jsx11("span", { children: schema.title || schema.name }) }),
5438
- /* @__PURE__ */ jsx11(
5468
+ !effectiveReadOnly && effectivePermissions.canCreate && /* @__PURE__ */ jsx11(
5439
5469
  "button",
5440
5470
  {
5441
5471
  onClick: handleAddRow,
@@ -5445,7 +5475,7 @@ function TableBlockClient({
5445
5475
  children: /* @__PURE__ */ jsx11("svg", { className: "w-4.5 h-4.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2.5", d: "M12 4v16m8-8H4" }) })
5446
5476
  }
5447
5477
  ),
5448
- /* @__PURE__ */ jsx11(
5478
+ !effectiveReadOnly && effectivePermissions.canCreate && /* @__PURE__ */ jsx11(
5449
5479
  "button",
5450
5480
  {
5451
5481
  onClick: handleDuplicateRow,
@@ -5455,7 +5485,7 @@ function TableBlockClient({
5455
5485
  children: /* @__PURE__ */ jsx11(Copy2, { className: "w-4 h-4 text-blue-600 shrink-0" })
5456
5486
  }
5457
5487
  ),
5458
- /* @__PURE__ */ jsxs11(
5488
+ !effectiveReadOnly && effectivePermissions.canDelete && /* @__PURE__ */ jsxs11(
5459
5489
  "button",
5460
5490
  {
5461
5491
  onClick: handleDeleteSelected,
@@ -5481,7 +5511,7 @@ function TableBlockClient({
5481
5511
  ]
5482
5512
  }
5483
5513
  ),
5484
- /* @__PURE__ */ jsxs11("div", { className: "relative", ref: dropdownRef, children: [
5514
+ !effectiveReadOnly && effectivePermissions.canUpdate && /* @__PURE__ */ jsxs11("div", { className: "relative", ref: dropdownRef, children: [
5485
5515
  /* @__PURE__ */ jsxs11(
5486
5516
  "button",
5487
5517
  {
@@ -5619,7 +5649,7 @@ function TableBlockClient({
5619
5649
  ] }) : isSaveSuccess ? /* @__PURE__ */ jsxs11("span", { className: "text-xs text-emerald-700 font-semibold flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 px-3 py-1.5 rounded-lg animate-in fade-in duration-200", children: [
5620
5650
  /* @__PURE__ */ jsx11("svg", { className: "w-4 h-4 text-emerald-600 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2.5", d: "M5 13l4 4L19 7" }) }),
5621
5651
  "\u05E2\u05D5\u05D3\u05DB\u05DF \u05D1\u05D4\u05E6\u05DC\u05D7\u05D4"
5622
- ] }) : hasChanges ? /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 animate-in fade-in", children: [
5652
+ ] }) : !effectiveReadOnly && effectivePermissions.canUpdate && hasChanges ? /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 animate-in fade-in", children: [
5623
5653
  /* @__PURE__ */ jsx11("span", { className: "text-xs font-semibold text-amber-700", children: "\u05E9\u05D9\u05E0\u05D5\u05D9\u05D9\u05DD \u05E9\u05DC\u05D0 \u05E0\u05E9\u05DE\u05E8\u05D5" }),
5624
5654
  /* @__PURE__ */ jsx11(
5625
5655
  "button",
@@ -5634,7 +5664,7 @@ function TableBlockClient({
5634
5664
  }
5635
5665
  )
5636
5666
  ] }) : null,
5637
- /* @__PURE__ */ jsx11(
5667
+ !effectiveReadOnly && effectivePermissions.canUpdate && /* @__PURE__ */ jsx11(
5638
5668
  "button",
5639
5669
  {
5640
5670
  onClick: handleSave,
@@ -5672,9 +5702,10 @@ function TableBlockClient({
5672
5702
  filteredColumns.map((col) => {
5673
5703
  var _a, _b;
5674
5704
  const isSearching = activeSearchCol === col.name;
5675
- const dragProps = isSearching ? {} : getHeaderDragProps(col.name);
5676
- const isDragging = !isSearching && draggedColName === col.name;
5677
- const isDragOver = !isSearching && dragOverColName === col.name;
5705
+ const canReorderColumns = !effectiveReadOnly && effectivePermissions.canUpdate && !isSearching;
5706
+ const dragProps = canReorderColumns ? getHeaderDragProps(col.name) : {};
5707
+ const isDragging = canReorderColumns && draggedColName === col.name;
5708
+ const isDragOver = canReorderColumns && dragOverColName === col.name;
5678
5709
  const colMinWidth = calculateMinColumnWidth(col.name, false, col.type);
5679
5710
  const currentWidth = Math.max(colMinWidth, columnWidths[col.name] || 200);
5680
5711
  const isRelation = ((_a = col.type) == null ? void 0 : _a.toUpperCase()) === "RELATION";
@@ -5694,7 +5725,7 @@ function TableBlockClient({
5694
5725
  style: { width: currentWidth, minWidth: currentWidth, maxWidth: currentWidth },
5695
5726
  title: isSearching ? void 0 : col.name
5696
5727
  }, dragProps), {
5697
- className: `group relative border-b border-l border-slate-200 px-2 py-1 font-semibold text-slate-700 select-none transition-colors h-12 min-h-[48px] align-middle bg-white ${isSearching ? "cursor-default bg-blue-50/40" : "cursor-grab active:cursor-grabbing hover:bg-slate-100"} ${isDragging ? "opacity-40 bg-slate-200 border-dashed border-2 border-brand" : ""} ${isDragOver ? "bg-blue-50 border-r-4 border-r-brand text-brand font-bold" : ""}`,
5728
+ className: `group relative border-b border-l border-slate-200 px-2 py-1 font-semibold text-slate-700 select-none transition-colors h-12 min-h-[48px] align-middle bg-white ${isSearching ? "cursor-default bg-blue-50/40" : canReorderColumns ? "cursor-grab active:cursor-grabbing hover:bg-slate-100" : "cursor-default"} ${isDragging ? "opacity-40 bg-slate-200 border-dashed border-2 border-brand" : ""} ${isDragOver ? "bg-blue-50 border-r-4 border-r-brand text-brand font-bold" : ""}`,
5698
5729
  children: [
5699
5730
  /* @__PURE__ */ jsx11(
5700
5731
  "div",
@@ -5835,7 +5866,9 @@ function TableBlockClient({
5835
5866
  relationDictionaryRef,
5836
5867
  refetchRelation,
5837
5868
  executeAction,
5838
- emitEvent
5869
+ emitEvent,
5870
+ readOnly: effectiveReadOnly || !effectivePermissions.canUpdate,
5871
+ permissions: effectivePermissions
5839
5872
  },
5840
5873
  row.id
5841
5874
  );
@@ -5986,7 +6019,8 @@ function ExpandedSubTables({
5986
6019
  column: activeCol,
5987
6020
  value: (rowData == null ? void 0 : rowData[activeCol.name]) || [],
5988
6021
  onChange: (newVal) => handleCellChange(rowId, activeCol.name, newVal),
5989
- context
6022
+ context,
6023
+ readOnly: context == null ? void 0 : context.readOnly
5990
6024
  },
5991
6025
  activeCol.name
5992
6026
  ) })
@@ -6017,7 +6051,9 @@ var TableRow = React11.memo(function TableRow2({
6017
6051
  relationDictionaryRef,
6018
6052
  refetchRelation,
6019
6053
  executeAction,
6020
- emitEvent
6054
+ emitEvent,
6055
+ readOnly,
6056
+ permissions
6021
6057
  }) {
6022
6058
  return /* @__PURE__ */ jsxs11(React11.Fragment, { children: [
6023
6059
  /* @__PURE__ */ jsxs11("tr", { className: `hover:bg-blue-50/30 transition-colors group ${isSelected ? "bg-blue-50/40" : ""}`, children: [
@@ -6069,6 +6105,7 @@ var TableRow = React11.memo(function TableRow2({
6069
6105
  column: col,
6070
6106
  rowId: row.id,
6071
6107
  onChange: (v) => handleCellChange(row.id, col.name, v),
6108
+ readOnly,
6072
6109
  context: {
6073
6110
  tableName: (schema == null ? void 0 : schema.name) || (schema == null ? void 0 : schema.tableName) || (schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.id) || "Table",
6074
6111
  tableId: (schema == null ? void 0 : schema.tableMetaId) || (schema == null ? void 0 : schema.tableId) || (schema == null ? void 0 : schema.id),
@@ -6083,7 +6120,9 @@ var TableRow = React11.memo(function TableRow2({
6083
6120
  orgId,
6084
6121
  updateQuietly: handleQuietCellChange,
6085
6122
  triggerSaveSuccess,
6086
- rowData: row.data
6123
+ rowData: row.data,
6124
+ readOnly,
6125
+ permissions
6087
6126
  }
6088
6127
  }
6089
6128
  )
@@ -6109,7 +6148,9 @@ var TableRow = React11.memo(function TableRow2({
6109
6148
  rowData: row.data,
6110
6149
  updateQuietly: handleQuietCellChange,
6111
6150
  triggerSaveSuccess,
6112
- onUpdateColumnConfig: handleUpdateColumnConfig
6151
+ onUpdateColumnConfig: handleUpdateColumnConfig,
6152
+ readOnly,
6153
+ permissions
6113
6154
  }
6114
6155
  }
6115
6156
  )
@@ -6150,6 +6191,7 @@ export {
6150
6191
  formatTimeInput,
6151
6192
  formatTimeIso,
6152
6193
  getDeterministicPastelBadge,
6194
+ getRolePermissions,
6153
6195
  isTextRepresentable,
6154
6196
  measureTextWidth,
6155
6197
  normalizeRelationValue,