@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.js CHANGED
@@ -83,6 +83,7 @@ __export(src_exports, {
83
83
  formatTimeInput: () => formatTimeInput,
84
84
  formatTimeIso: () => formatTimeIso,
85
85
  getDeterministicPastelBadge: () => getDeterministicPastelBadge,
86
+ getRolePermissions: () => getRolePermissions,
86
87
  isTextRepresentable: () => isTextRepresentable,
87
88
  measureTextWidth: () => measureTextWidth,
88
89
  normalizeRelationValue: () => normalizeRelationValue,
@@ -165,6 +166,30 @@ function getDeterministicPastelBadge(key) {
165
166
  return PASTEL_PALETTES[index];
166
167
  }
167
168
 
169
+ // src/utils/permissions.ts
170
+ function getRolePermissions(role) {
171
+ if (role === "VIEWER" || !role) {
172
+ return {
173
+ permissions: {
174
+ canCreate: false,
175
+ canRead: true,
176
+ canUpdate: false,
177
+ canDelete: false
178
+ },
179
+ readOnly: true
180
+ };
181
+ }
182
+ return {
183
+ permissions: {
184
+ canCreate: true,
185
+ canRead: true,
186
+ canUpdate: true,
187
+ canDelete: true
188
+ },
189
+ readOnly: false
190
+ };
191
+ }
192
+
168
193
  // ../shared/src/columns.ts
169
194
  var COLUMN_TYPES = [
170
195
  "STRING",
@@ -442,7 +467,7 @@ var import_react8 = __toESM(require("react"));
442
467
  var import_react3 = __toESM(require("react"));
443
468
  var import_lucide_react2 = require("lucide-react");
444
469
  var import_jsx_runtime2 = require("react/jsx-runtime");
445
- function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForDeletion, context }) {
470
+ function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForDeletion, context, readOnly = false }) {
446
471
  const [isOpening, setIsOpening] = (0, import_react3.useState)(false);
447
472
  const { showError } = useToast();
448
473
  const cellContextId = import_react3.default.useMemo(() => {
@@ -543,29 +568,33 @@ function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForD
543
568
  const updated = fileList.filter((_, idx) => idx !== indexToRemove);
544
569
  onChange(updated);
545
570
  };
546
- const renderUploadButton = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center w-full h-8 px-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
547
- "button",
548
- {
549
- type: "button",
550
- onClick: openUploadPopup,
551
- disabled: isOpening || !(context == null ? void 0 : context.executeAction),
552
- 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",
553
- children: [
554
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Upload, { className: "w-3.5 h-3.5" }),
555
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: isOpening ? "Loading..." : "Upload File" })
556
- ]
557
- }
558
- ) });
571
+ const renderUploadButton = (iconOnly) => {
572
+ if (readOnly) return null;
573
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
574
+ "button",
575
+ {
576
+ type: "button",
577
+ onClick: openUploadPopup,
578
+ disabled: isOpening || !(context == null ? void 0 : context.executeAction),
579
+ 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"}`,
580
+ title: "Upload File",
581
+ children: [
582
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Upload, { className: "w-3.5 h-3.5 shrink-0" }),
583
+ !iconOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: isOpening ? "Loading..." : "Upload File" })
584
+ ]
585
+ }
586
+ );
587
+ };
559
588
  if (fileList.length > 0) {
560
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col w-full py-1 gap-1", children: [
561
- fileList.map((file, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center w-full h-8 px-2 gap-1", children: [
589
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center w-full py-1 px-2 gap-1.5", children: [
590
+ fileList.map((file, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1 max-w-full", children: [
562
591
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
563
592
  "a",
564
593
  {
565
594
  href: file.fileUrl || file.url,
566
595
  target: "_blank",
567
596
  rel: "noopener noreferrer",
568
- 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",
597
+ 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)]"}`,
569
598
  title: file.fileName || "Open file",
570
599
  children: [
571
600
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.FileText, { className: "w-3.5 h-3.5 shrink-0" }),
@@ -573,21 +602,24 @@ function DrivePickerCell({ value, onChange, tableName, column, rowId, onMarkForD
573
602
  ]
574
603
  }
575
604
  ),
576
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
605
+ !readOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
577
606
  "button",
578
607
  {
579
608
  type: "button",
580
609
  onClick: () => handleRemoveFile(idx),
581
- className: "shrink-0 p-0.5 rounded hover:bg-rose-100 text-slate-400 hover:text-rose-500 transition-colors",
610
+ 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",
582
611
  title: "Remove file",
583
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.X, { className: "w-3.5 h-3.5" })
612
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.X, { className: "w-3.5 h-3.5 shrink-0" })
584
613
  }
585
614
  )
586
615
  ] }, file.fileId || idx)),
587
- renderUploadButton()
616
+ renderUploadButton(true)
588
617
  ] });
589
618
  }
590
- return renderUploadButton();
619
+ if (readOnly) {
620
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "px-2 py-1 flex items-center h-full text-slate-300 text-xs italic", children: "-" });
621
+ }
622
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center w-full h-8 px-2", children: renderUploadButton(false) });
591
623
  }
592
624
 
593
625
  // src/components/TableBlock/fields/DatePickerCell.tsx
@@ -2154,11 +2186,19 @@ function getCurrencySymbol(code) {
2154
2186
  return CURRENCY_SYMBOLS[code.toUpperCase()] || code;
2155
2187
  }
2156
2188
  var FORM_INPUTS = {
2157
- DATE: ({ value, onChange, column }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DatePickerCell, { value, onChange, column }),
2158
- CURRENCY: ({ value, column, onChange }) => {
2189
+ DATE: ({ value, onChange, column, readOnly }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DatePickerCell, { value, onChange, column, readOnly }),
2190
+ CURRENCY: ({ value, column, onChange, readOnly }) => {
2159
2191
  var _a;
2160
2192
  const symbol = getCurrencySymbol((_a = column.config) == null ? void 0 : _a.currencySymbol);
2161
2193
  const [isFocused, setIsFocused] = import_react8.default.useState(false);
2194
+ if (readOnly) {
2195
+ const num = Number(value);
2196
+ const formattedValue = value !== null && value !== void 0 && !isNaN(num) ? new Intl.NumberFormat("en-US").format(num) : "";
2197
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "px-2 py-1 flex items-center h-full text-sm select-text truncate", children: [
2198
+ symbol && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-slate-400 mr-1", children: symbol }),
2199
+ formattedValue
2200
+ ] });
2201
+ }
2162
2202
  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 : "";
2163
2203
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "relative w-full h-full", children: [
2164
2204
  symbol && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "absolute left-2 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none text-sm", children: symbol }),
@@ -2179,18 +2219,19 @@ var FORM_INPUTS = {
2179
2219
  )
2180
2220
  ] });
2181
2221
  },
2182
- TIME: ({ value, onChange, column }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TimePickerCell, { value, onChange, column }),
2183
- LINK: ({ value, onChange, column }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LinkPickerCell, { value, onChange, column }),
2184
- BOOLEAN: ({ value, onChange }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex items-center justify-center w-full h-full p-2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2222
+ TIME: ({ value, onChange, column, readOnly }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TimePickerCell, { value, onChange, column, readOnly }),
2223
+ LINK: ({ value, onChange, column, readOnly }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LinkPickerCell, { value, onChange, column, readOnly }),
2224
+ BOOLEAN: ({ value, onChange, readOnly }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex items-center justify-center w-full h-full p-2", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2185
2225
  "input",
2186
2226
  {
2187
2227
  type: "checkbox",
2188
2228
  checked: !!value,
2229
+ disabled: readOnly,
2189
2230
  onChange: (e) => onChange(e.target.checked),
2190
- className: "w-4 h-4 rounded border-slate-300 text-brand"
2231
+ className: `w-4 h-4 rounded border-slate-300 text-brand ${readOnly ? "cursor-not-allowed opacity-70" : "cursor-pointer"}`
2191
2232
  }
2192
2233
  ) }),
2193
- GOOGLE_DRIVE_FILE: ({ value, onChange, column, rowId, context }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2234
+ GOOGLE_DRIVE_FILE: ({ value, onChange, column, rowId, context, readOnly }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2194
2235
  DrivePickerCell,
2195
2236
  {
2196
2237
  value: value || [],
@@ -2199,34 +2240,60 @@ var FORM_INPUTS = {
2199
2240
  rowId,
2200
2241
  tableName: context == null ? void 0 : context.tableName,
2201
2242
  onMarkForDeletion: context == null ? void 0 : context.onMarkForDeletion,
2202
- context
2243
+ context,
2244
+ readOnly
2203
2245
  }
2204
2246
  ),
2205
- RELATION: ({ value, onChange, column, context, rowId }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2247
+ RELATION: ({ value, onChange, column, context, rowId, readOnly }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2206
2248
  RelationPickerCell,
2207
2249
  {
2208
2250
  value,
2209
2251
  onChange,
2210
2252
  column,
2211
2253
  context,
2212
- rowId
2254
+ rowId,
2255
+ readOnly
2213
2256
  }
2214
2257
  ),
2215
- NUMBER: ({ value, onChange }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2216
- "input",
2217
- {
2218
- type: "number",
2219
- className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2220
- value: value !== void 0 && value !== null ? value : "",
2221
- onChange: (e) => {
2222
- const val = e.target.value;
2223
- onChange(val === "" ? null : Number(val));
2224
- }
2258
+ NUMBER: ({ value, onChange, readOnly }) => {
2259
+ if (readOnly) {
2260
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "px-2 py-1 flex items-center h-full text-sm select-text truncate", children: value !== void 0 && value !== null ? value : "" });
2225
2261
  }
2226
- ),
2227
- PHONE: ({ value, onChange, column }) => {
2262
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2263
+ "input",
2264
+ {
2265
+ type: "number",
2266
+ className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2267
+ value: value !== void 0 && value !== null ? value : "",
2268
+ onChange: (e) => {
2269
+ const val = e.target.value;
2270
+ onChange(val === "" ? null : Number(val));
2271
+ }
2272
+ }
2273
+ );
2274
+ },
2275
+ PHONE: ({ value, onChange, column, readOnly }) => {
2228
2276
  var _a;
2229
2277
  const enableWhatsApp = (_a = column == null ? void 0 : column.config) == null ? void 0 : _a.enableWhatsApp;
2278
+ const formatted = value ? formatPhoneDisplay(String(value)) : "";
2279
+ if (readOnly) {
2280
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between w-full h-full px-2 gap-1.5 overflow-hidden", children: [
2281
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm", dir: "ltr", children: formatted }),
2282
+ enableWhatsApp && value && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2283
+ "button",
2284
+ {
2285
+ type: "button",
2286
+ className: "text-emerald-500 cursor-pointer hover:opacity-80 transition-opacity shrink-0 p-1 rounded hover:bg-emerald-50",
2287
+ title: "\u05E4\u05EA\u05D9\u05D7\u05EA \u05D5\u05D5\u05D0\u05D8\u05E1\u05D0\u05E4",
2288
+ onClick: (e) => {
2289
+ e.stopPropagation();
2290
+ openWhatsAppChat(String(value));
2291
+ },
2292
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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" }) })
2293
+ }
2294
+ )
2295
+ ] });
2296
+ }
2230
2297
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center w-full h-full px-2 gap-1.5 overflow-hidden", children: [
2231
2298
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2232
2299
  "input",
@@ -2253,85 +2320,9 @@ var FORM_INPUTS = {
2253
2320
  )
2254
2321
  ] });
2255
2322
  },
2256
- GOOGLE_CONTACT: ({ value, column, rowId, onChange, context }) => {
2323
+ GOOGLE_CONTACT: ({ value, column, rowId, onChange, context, readOnly }) => {
2257
2324
  const [isSyncing, setIsSyncing] = import_react8.default.useState(false);
2258
2325
  const { addToast } = useToast();
2259
- const googleClientIdRef = import_react8.default.useRef(null);
2260
- const ensureGisScript = import_react8.default.useCallback(() => {
2261
- return new Promise((resolve, reject) => {
2262
- var _a, _b, _c;
2263
- if ((_c = (_b = (_a = window.google) == null ? void 0 : _a.accounts) == null ? void 0 : _b.oauth2) == null ? void 0 : _c.initTokenClient) {
2264
- resolve();
2265
- return;
2266
- }
2267
- if (document.querySelector('script[src*="accounts.google.com/gsi/client"]')) {
2268
- const waitForLoad = setInterval(() => {
2269
- var _a2, _b2, _c2;
2270
- if ((_c2 = (_b2 = (_a2 = window.google) == null ? void 0 : _a2.accounts) == null ? void 0 : _b2.oauth2) == null ? void 0 : _c2.initTokenClient) {
2271
- clearInterval(waitForLoad);
2272
- resolve();
2273
- }
2274
- }, 100);
2275
- setTimeout(() => {
2276
- clearInterval(waitForLoad);
2277
- reject(new Error("GIS script load timeout"));
2278
- }, 1e4);
2279
- return;
2280
- }
2281
- const script = document.createElement("script");
2282
- script.src = "https://accounts.google.com/gsi/client";
2283
- script.async = true;
2284
- script.onload = () => resolve();
2285
- script.onerror = () => reject(new Error("Failed to load Google Identity Services"));
2286
- document.head.appendChild(script);
2287
- });
2288
- }, []);
2289
- const requestGisToken = import_react8.default.useCallback(async (hint) => {
2290
- await ensureGisScript();
2291
- if (!googleClientIdRef.current && (context == null ? void 0 : context.executeAction)) {
2292
- try {
2293
- const config = await context.executeAction("GET_GOOGLE_CONFIG", {});
2294
- googleClientIdRef.current = (config == null ? void 0 : config.clientId) || "";
2295
- } catch (e) {
2296
- throw new Error("Failed to fetch Google configuration");
2297
- }
2298
- }
2299
- const clientId = googleClientIdRef.current;
2300
- if (!clientId) {
2301
- throw new Error("Google Client ID is not configured");
2302
- }
2303
- const accessToken = await new Promise((resolve, reject) => {
2304
- const tokenClient = window.google.accounts.oauth2.initTokenClient({
2305
- client_id: clientId,
2306
- scope: "https://www.googleapis.com/auth/contacts https://www.googleapis.com/auth/userinfo.email",
2307
- prompt: hint ? "" : "select_account",
2308
- hint: hint || void 0,
2309
- callback: (tokenResponse) => {
2310
- if (tokenResponse == null ? void 0 : tokenResponse.access_token) {
2311
- resolve(tokenResponse.access_token);
2312
- } else {
2313
- reject(new Error("Google authorization was cancelled or failed"));
2314
- }
2315
- },
2316
- error_callback: (err) => {
2317
- reject(new Error((err == null ? void 0 : err.message) || "Google authorization error"));
2318
- }
2319
- });
2320
- tokenClient.requestAccessToken({ hint: hint || void 0 });
2321
- });
2322
- let email = "";
2323
- try {
2324
- const res = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
2325
- headers: { Authorization: `Bearer ${accessToken}` }
2326
- });
2327
- if (res.ok) {
2328
- const info = await res.json();
2329
- email = info.email || "";
2330
- }
2331
- } catch (e) {
2332
- }
2333
- return { accessToken, email };
2334
- }, [ensureGisScript, context]);
2335
2326
  const handleSync = async () => {
2336
2327
  var _a, _b, _c, _d, _e, _f;
2337
2328
  if (!(context == null ? void 0 : context.executeAction)) return;
@@ -2366,17 +2357,6 @@ var FORM_INPUTS = {
2366
2357
  setIsSyncing(false);
2367
2358
  return;
2368
2359
  }
2369
- let accessToken;
2370
- let userEmail = "";
2371
- try {
2372
- const gisResult = await requestGisToken();
2373
- accessToken = gisResult.accessToken;
2374
- userEmail = gisResult.email;
2375
- } catch (gisError) {
2376
- addToast({ type: "warning", message: gisError.message || "\u05D4\u05D4\u05E8\u05E9\u05D0\u05D4 \u05DC\u05D2\u05D5\u05D2\u05DC \u05E0\u05DB\u05E9\u05DC\u05D4" });
2377
- setIsSyncing(false);
2378
- return;
2379
- }
2380
2360
  const payload = {
2381
2361
  nameParts: mapping.nameParts || [],
2382
2362
  phoneCol: mapping.phoneCol,
@@ -2386,9 +2366,7 @@ var FORM_INPUTS = {
2386
2366
  tableMetaId: targetTableMetaId,
2387
2367
  tableId: targetTableMetaId,
2388
2368
  recordId: rowId,
2389
- orgId: targetOrgId,
2390
- accessToken,
2391
- userEmail
2369
+ orgId: targetOrgId
2392
2370
  };
2393
2371
  const res = await context.executeAction("SYNC_GOOGLE_CONTACT", payload);
2394
2372
  if (res && res.synced) {
@@ -2408,39 +2386,44 @@ var FORM_INPUTS = {
2408
2386
  }
2409
2387
  }
2410
2388
  } catch (error) {
2411
- console.error("Failed to sync contact:", error);
2412
- addToast({ type: "warning", message: "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05E1\u05E0\u05DB\u05E8\u05D5\u05DF \u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8: " + error.message });
2389
+ const errorMessage = (error == null ? void 0 : error.message) || (error == null ? void 0 : error.error) || (typeof error === "string" ? error : "");
2390
+ if (errorMessage.includes("OAUTH_REQUIRED") || errorMessage.includes("Access Denied") || errorMessage.includes("No refresh token")) {
2391
+ addToast({
2392
+ type: "warning",
2393
+ 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."
2394
+ });
2395
+ } else {
2396
+ console.error("Failed to sync contact:", error);
2397
+ addToast({
2398
+ type: "warning",
2399
+ message: "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05E1\u05E0\u05DB\u05E8\u05D5\u05DF \u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8: " + error.message
2400
+ });
2401
+ }
2413
2402
  } finally {
2414
2403
  setIsSyncing(false);
2415
2404
  }
2416
2405
  };
2417
2406
  const handleUnlink = async () => {
2418
- let accessToken;
2419
2407
  const targetTableMetaId = (context == null ? void 0 : context.tableMetaId) || (context == null ? void 0 : context.tableId);
2420
2408
  const targetOrgId = context == null ? void 0 : context.orgId;
2421
2409
  try {
2422
- let emailHint = value == null ? void 0 : value.userEmail;
2423
- if (!emailHint && (value == null ? void 0 : value.url)) {
2424
- try {
2425
- const parsed = new URL(value.url);
2426
- emailHint = parsed.searchParams.get("authuser") || void 0;
2427
- } catch (e) {
2428
- }
2410
+ if ((value == null ? void 0 : value.resourceName) && (context == null ? void 0 : context.executeAction)) {
2411
+ await context.executeAction("DELETE_GOOGLE_CONTACT", {
2412
+ resourceName: value.resourceName,
2413
+ tableMetaId: targetTableMetaId,
2414
+ tableId: targetTableMetaId,
2415
+ recordId: rowId,
2416
+ orgId: targetOrgId
2417
+ });
2429
2418
  }
2430
- const auth = await requestGisToken(emailHint);
2431
- accessToken = auth.accessToken;
2432
- } catch (err) {
2433
- console.warn("[handleUnlink] Could not obtain token via GIS, falling back to server default token:", err);
2434
- }
2435
- if ((value == null ? void 0 : value.resourceName) && (context == null ? void 0 : context.executeAction)) {
2436
- await context.executeAction("DELETE_GOOGLE_CONTACT", {
2437
- resourceName: value.resourceName,
2438
- accessToken,
2439
- tableMetaId: targetTableMetaId,
2440
- tableId: targetTableMetaId,
2441
- recordId: rowId,
2442
- orgId: targetOrgId
2443
- });
2419
+ } catch (error) {
2420
+ if (error.message === "OAUTH_REQUIRED" || error.error === "OAUTH_REQUIRED") {
2421
+ 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." });
2422
+ return;
2423
+ }
2424
+ console.error("Failed to unlink contact:", error);
2425
+ addToast({ type: "warning", message: "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05D4\u05E1\u05E8\u05EA \u05D0\u05D9\u05E9 \u05E7\u05E9\u05E8: " + error.message });
2426
+ return;
2444
2427
  }
2445
2428
  if (context == null ? void 0 : context.updateQuietly) {
2446
2429
  context.updateQuietly(rowId, column.name, null);
@@ -2460,7 +2443,7 @@ var FORM_INPUTS = {
2460
2443
  if (value && value.synced && value.url) {
2461
2444
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5 h-8 px-2 group whitespace-nowrap overflow-hidden max-w-full", children: [
2462
2445
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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" }),
2463
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2446
+ !readOnly && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2464
2447
  "button",
2465
2448
  {
2466
2449
  type: "button",
@@ -2472,6 +2455,9 @@ var FORM_INPUTS = {
2472
2455
  )
2473
2456
  ] });
2474
2457
  }
2458
+ if (readOnly) {
2459
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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" });
2460
+ }
2475
2461
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex items-center h-8 px-2 whitespace-nowrap overflow-hidden max-w-full", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2476
2462
  "button",
2477
2463
  {
@@ -2489,15 +2475,20 @@ var FORM_INPUTS = {
2489
2475
  }
2490
2476
  ) });
2491
2477
  },
2492
- DEFAULT: ({ value, onChange }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2493
- "input",
2494
- {
2495
- type: "text",
2496
- className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2497
- value: value !== void 0 && value !== null ? String(value) : "",
2498
- onChange: (e) => onChange(e.target.value)
2478
+ DEFAULT: ({ value, onChange, readOnly }) => {
2479
+ if (readOnly) {
2480
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "px-2 py-1 flex items-center h-full text-sm select-text truncate", children: value !== void 0 && value !== null ? String(value) : "" });
2499
2481
  }
2500
- ),
2482
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2483
+ "input",
2484
+ {
2485
+ type: "text",
2486
+ className: "w-full h-8 px-2 bg-transparent focus:bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 text-sm",
2487
+ value: value !== void 0 && value !== null ? String(value) : "",
2488
+ onChange: (e) => onChange(e.target.value)
2489
+ }
2490
+ );
2491
+ },
2501
2492
  SUB_TABLE: ({ value, column }) => {
2502
2493
  var _a, _b, _c;
2503
2494
  if (!Array.isArray(value)) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "px-2 py-1 flex items-center h-full text-slate-300 text-xs italic", children: "\u05E8\u05D9\u05E7" });
@@ -2907,7 +2898,8 @@ function SubTableCell({
2907
2898
  refetchRelation,
2908
2899
  onCommit,
2909
2900
  onQuietUpdate,
2910
- width
2901
+ width,
2902
+ readOnly
2911
2903
  }) {
2912
2904
  var _a, _b, _c, _d, _e;
2913
2905
  const colType = ((_a = column.type) == null ? void 0 : _a.toUpperCase()) || "DEFAULT";
@@ -2942,6 +2934,7 @@ function SubTableCell({
2942
2934
  }
2943
2935
  }, [serverValue]);
2944
2936
  const commit = (0, import_react12.useCallback)(async (valToCommit) => {
2937
+ if (readOnly) return true;
2945
2938
  if (isCommittingRef.current) return false;
2946
2939
  if (!isDirtyRef.current) return true;
2947
2940
  if (valToCommit === serverValueRef.current) {
@@ -3058,7 +3051,9 @@ function SubTableCell({
3058
3051
  column,
3059
3052
  rowId: record.id,
3060
3053
  onChange: handleChange,
3054
+ readOnly,
3061
3055
  context: __spreadProps(__spreadValues({}, context), {
3056
+ readOnly,
3062
3057
  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",
3063
3058
  relationOptions,
3064
3059
  updateRelationOptions: (name, opts) => setRelationOptions((prev) => __spreadProps(__spreadValues({}, prev), { [name]: opts })),
@@ -3088,8 +3083,9 @@ function SubTableCell({
3088
3083
  }
3089
3084
  );
3090
3085
  }
3091
- function SubTableViewer({ rowId, column, value, onChange, context }) {
3086
+ function SubTableViewer({ rowId, column, value, onChange, context, readOnly }) {
3092
3087
  var _a, _b, _c, _d, _e, _f;
3088
+ const isReadOnly = readOnly != null ? readOnly : Boolean(context == null ? void 0 : context.readOnly);
3093
3089
  const { addToast } = useToast();
3094
3090
  const entries = Array.isArray(value) ? value : [];
3095
3091
  const [linkedRecords, setLinkedRecords] = (0, import_react12.useState)(() => {
@@ -3616,7 +3612,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3616
3612
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "p-3 animate-in fade-in slide-in-from-top-2 duration-200", dir: "rtl", children: [
3617
3613
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-4 mb-2.5", children: [
3618
3614
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("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 }) }),
3619
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1.5", children: [
3615
+ !isReadOnly && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1.5", children: [
3620
3616
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "relative", ref: searchRef, children: [
3621
3617
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3622
3618
  "button",
@@ -3681,13 +3677,13 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3681
3677
  )
3682
3678
  ] })
3683
3679
  ] }),
3684
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-5 h-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" }) }) : entries.length === 0 && !isCreating ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("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__ */ (0, import_jsx_runtime9.jsx)("div", { className: "overflow-x-auto pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { className: "w-full text-xs border-collapse table-auto", children: [
3680
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex items-center justify-center py-6", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-5 h-5 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" }) }) : entries.length === 0 && !isCreating ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("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__ */ (0, import_jsx_runtime9.jsx)("div", { className: "overflow-x-auto pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { className: "w-full text-xs border-collapse table-auto", children: [
3685
3681
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { className: "bg-slate-100/80 h-12 min-h-[48px]", children: [
3686
3682
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("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" }),
3687
3683
  targetColumns.map((col) => {
3688
- const dragProps = getHeaderDragProps(col.name);
3689
- const isDragging = draggedColName === col.name;
3690
- const isDragOver = dragOverColName === col.name;
3684
+ const dragProps = isReadOnly ? {} : getHeaderDragProps(col.name);
3685
+ const isDragging = !isReadOnly && draggedColName === col.name;
3686
+ const isDragOver = !isReadOnly && dragOverColName === col.name;
3691
3687
  const colMinWidth = calculateMinColumnWidth(col.name, true, col.type);
3692
3688
  const currentWidth = Math.max(colMinWidth, columnWidths[col.name] || 150);
3693
3689
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
@@ -3696,7 +3692,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3696
3692
  style: { width: currentWidth, minWidth: currentWidth, maxWidth: currentWidth },
3697
3693
  title: col.name
3698
3694
  }, dragProps), {
3699
- 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"}`,
3695
+ 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"}`,
3700
3696
  children: [
3701
3697
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3702
3698
  "div",
@@ -3712,7 +3708,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3712
3708
  ),
3713
3709
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between gap-1.5 group overflow-hidden w-full", title: col.name, children: [
3714
3710
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ColumnHeaderTitle, { name: col.name, width: currentWidth, isSubTable: true }),
3715
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3711
+ !isReadOnly && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3716
3712
  "svg",
3717
3713
  {
3718
3714
  className: "w-3.5 h-3.5 text-slate-400 opacity-40 group-hover:opacity-100 transition-opacity shrink-0 cursor-grab",
@@ -3741,8 +3737,9 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3741
3737
  {
3742
3738
  type: "checkbox",
3743
3739
  checked: entry.isSelected,
3740
+ disabled: isReadOnly,
3744
3741
  onChange: () => handleToggleSelected(entry.recordId),
3745
- className: "w-3.5 h-3.5 rounded border-slate-300 text-blue-600 cursor-pointer"
3742
+ className: `w-3.5 h-3.5 rounded border-slate-300 text-blue-600 ${isReadOnly ? "cursor-not-allowed opacity-60" : "cursor-pointer"}`
3746
3743
  }
3747
3744
  ) }),
3748
3745
  targetColumns.map((col) => {
@@ -3772,7 +3769,8 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3772
3769
  refetchRelation,
3773
3770
  onCommit: handleCommitRecordCell,
3774
3771
  onQuietUpdate: handleQuietSubTableCellChange,
3775
- width: currentWidth
3772
+ width: currentWidth,
3773
+ readOnly: isReadOnly
3776
3774
  },
3777
3775
  `${record.id}_${col.name}`
3778
3776
  );
@@ -3799,7 +3797,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3799
3797
  }
3800
3798
  return null;
3801
3799
  })(),
3802
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3800
+ !isReadOnly && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3803
3801
  "button",
3804
3802
  {
3805
3803
  type: "button",
@@ -3816,7 +3814,7 @@ function SubTableViewer({ rowId, column, value, onChange, context }) {
3816
3814
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { className: "border border-slate-200 bg-white pointer-events-none" })
3817
3815
  ] }, entry.recordId);
3818
3816
  }),
3819
- isCreating && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { className: "bg-emerald-50/40", children: [
3817
+ isCreating && !isReadOnly && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { className: "bg-emerald-50/40", children: [
3820
3818
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { className: "border border-emerald-200 px-2 py-1 text-center", style: { width: 40, minWidth: 40, maxWidth: 40 }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-emerald-500 font-bold text-[10px]", children: "\u05D7\u05D3\u05E9" }) }),
3821
3819
  targetColumns.map((col) => {
3822
3820
  var _a2, _b2, _c2, _d2, _e2;
@@ -4331,10 +4329,21 @@ function TableBlockClient({
4331
4329
  componentId,
4332
4330
  orgId,
4333
4331
  className = "",
4334
- "data-testid": dataTestId
4332
+ "data-testid": dataTestId,
4333
+ role,
4334
+ permissions,
4335
+ readOnly
4335
4336
  }) {
4336
4337
  const { showError, addToast } = useToast();
4337
4338
  const [isExporting, setIsExporting] = (0, import_react14.useState)(false);
4339
+ const resolvedRole = role ? getRolePermissions(role) : void 0;
4340
+ const effectivePermissions = permissions || (resolvedRole == null ? void 0 : resolvedRole.permissions) || {
4341
+ canCreate: !readOnly,
4342
+ canRead: true,
4343
+ canUpdate: !readOnly,
4344
+ canDelete: !readOnly
4345
+ };
4346
+ const effectiveReadOnly = readOnly != null ? readOnly : resolvedRole ? resolvedRole.readOnly : !effectivePermissions.canUpdate && !effectivePermissions.canCreate && !effectivePermissions.canDelete;
4338
4347
  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";
4339
4348
  const { columnWidths, startResize } = useColumnResize({
4340
4349
  storageKey: `table_widths_${tableKey}`,
@@ -5463,14 +5472,29 @@ function TableBlockClient({
5463
5472
  }
5464
5473
  const result = (res == null ? void 0 : res.data) || res;
5465
5474
  if ((result == null ? void 0 : result.success) === false) {
5466
- throw new Error(result.error || "\u05E9\u05D2\u05D9\u05D0\u05D4 \u05D1\u05D9\u05D9\u05E6\u05D5\u05D0 \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD");
5475
+ throw new Error(result.error || "Failed to export data");
5467
5476
  }
5477
+ const base64Data = result == null ? void 0 : result.base64Data;
5468
5478
  const csvString = (result == null ? void 0 : result.csvString) || (result == null ? void 0 : result.csv);
5469
- if (!csvString) {
5470
- throw new Error("\u05DC\u05D0 \u05D4\u05EA\u05E7\u05D1\u05DC\u05D5 \u05E0\u05EA\u05D5\u05E0\u05D9 CSV \u05DE\u05D4\u05E9\u05E8\u05EA");
5479
+ if (!base64Data && !csvString) {
5480
+ throw new Error("No export data received from server");
5481
+ }
5482
+ let blob;
5483
+ const defaultFileName = `${((schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.name) || "table").trim()}_export.xlsx`;
5484
+ let fileName = (result == null ? void 0 : result.fileName) || defaultFileName;
5485
+ if (base64Data) {
5486
+ const byteCharacters = atob(base64Data);
5487
+ const byteNumbers = new Uint8Array(byteCharacters.length);
5488
+ for (let i = 0; i < byteCharacters.length; i++) {
5489
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
5490
+ }
5491
+ blob = new Blob([byteNumbers], {
5492
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
5493
+ });
5494
+ } else {
5495
+ fileName = fileName.replace(/\.xlsx$/i, ".csv");
5496
+ blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });
5471
5497
  }
5472
- const fileName = (result == null ? void 0 : result.fileName) || `${((schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.name) || "table").trim()}_export.csv`;
5473
- const blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });
5474
5498
  const url = URL.createObjectURL(blob);
5475
5499
  const link = document.createElement("a");
5476
5500
  link.href = url;
@@ -5479,6 +5503,13 @@ function TableBlockClient({
5479
5503
  link.click();
5480
5504
  document.body.removeChild(link);
5481
5505
  URL.revokeObjectURL(url);
5506
+ if (result == null ? void 0 : result.isTruncated) {
5507
+ addToast({
5508
+ 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",
5509
+ type: "warning",
5510
+ duration: 5e3
5511
+ });
5512
+ }
5482
5513
  } catch (err) {
5483
5514
  console.error("[TableBlock] Export to CSV failed:", err);
5484
5515
  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");
@@ -5501,7 +5532,7 @@ function TableBlockClient({
5501
5532
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
5502
5533
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center gap-2.5", children: [
5503
5534
  ((schema == null ? void 0 : schema.title) || (schema == null ? void 0 : schema.name)) && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-bold text-slate-800 text-sm sm:text-base border-l border-slate-300 pl-3 ml-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: schema.title || schema.name }) }),
5504
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5535
+ !effectiveReadOnly && effectivePermissions.canCreate && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5505
5536
  "button",
5506
5537
  {
5507
5538
  onClick: handleAddRow,
@@ -5511,7 +5542,7 @@ function TableBlockClient({
5511
5542
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4.5 h-4.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2.5", d: "M12 4v16m8-8H4" }) })
5512
5543
  }
5513
5544
  ),
5514
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5545
+ !effectiveReadOnly && effectivePermissions.canCreate && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5515
5546
  "button",
5516
5547
  {
5517
5548
  onClick: handleDuplicateRow,
@@ -5521,7 +5552,7 @@ function TableBlockClient({
5521
5552
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react8.Copy, { className: "w-4 h-4 text-blue-600 shrink-0" })
5522
5553
  }
5523
5554
  ),
5524
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5555
+ !effectiveReadOnly && effectivePermissions.canDelete && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5525
5556
  "button",
5526
5557
  {
5527
5558
  onClick: handleDeleteSelected,
@@ -5547,7 +5578,7 @@ function TableBlockClient({
5547
5578
  ]
5548
5579
  }
5549
5580
  ),
5550
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative", ref: dropdownRef, children: [
5581
+ !effectiveReadOnly && effectivePermissions.canUpdate && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative", ref: dropdownRef, children: [
5551
5582
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5552
5583
  "button",
5553
5584
  {
@@ -5685,7 +5716,7 @@ function TableBlockClient({
5685
5716
  ] }) : isSaveSuccess ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
5686
5717
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4 text-emerald-600 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2.5", d: "M5 13l4 4L19 7" }) }),
5687
5718
  "\u05E2\u05D5\u05D3\u05DB\u05DF \u05D1\u05D4\u05E6\u05DC\u05D7\u05D4"
5688
- ] }) : hasChanges ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1.5 animate-in fade-in", children: [
5719
+ ] }) : !effectiveReadOnly && effectivePermissions.canUpdate && hasChanges ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1.5 animate-in fade-in", children: [
5689
5720
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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" }),
5690
5721
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5691
5722
  "button",
@@ -5700,7 +5731,7 @@ function TableBlockClient({
5700
5731
  }
5701
5732
  )
5702
5733
  ] }) : null,
5703
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5734
+ !effectiveReadOnly && effectivePermissions.canUpdate && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5704
5735
  "button",
5705
5736
  {
5706
5737
  onClick: handleSave,
@@ -5738,9 +5769,10 @@ function TableBlockClient({
5738
5769
  filteredColumns.map((col) => {
5739
5770
  var _a, _b;
5740
5771
  const isSearching = activeSearchCol === col.name;
5741
- const dragProps = isSearching ? {} : getHeaderDragProps(col.name);
5742
- const isDragging = !isSearching && draggedColName === col.name;
5743
- const isDragOver = !isSearching && dragOverColName === col.name;
5772
+ const canReorderColumns = !effectiveReadOnly && effectivePermissions.canUpdate && !isSearching;
5773
+ const dragProps = canReorderColumns ? getHeaderDragProps(col.name) : {};
5774
+ const isDragging = canReorderColumns && draggedColName === col.name;
5775
+ const isDragOver = canReorderColumns && dragOverColName === col.name;
5744
5776
  const colMinWidth = calculateMinColumnWidth(col.name, false, col.type);
5745
5777
  const currentWidth = Math.max(colMinWidth, columnWidths[col.name] || 200);
5746
5778
  const isRelation = ((_a = col.type) == null ? void 0 : _a.toUpperCase()) === "RELATION";
@@ -5760,7 +5792,7 @@ function TableBlockClient({
5760
5792
  style: { width: currentWidth, minWidth: currentWidth, maxWidth: currentWidth },
5761
5793
  title: isSearching ? void 0 : col.name
5762
5794
  }, dragProps), {
5763
- 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" : ""}`,
5795
+ 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" : ""}`,
5764
5796
  children: [
5765
5797
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5766
5798
  "div",
@@ -5901,7 +5933,9 @@ function TableBlockClient({
5901
5933
  relationDictionaryRef,
5902
5934
  refetchRelation,
5903
5935
  executeAction,
5904
- emitEvent
5936
+ emitEvent,
5937
+ readOnly: effectiveReadOnly || !effectivePermissions.canUpdate,
5938
+ permissions: effectivePermissions
5905
5939
  },
5906
5940
  row.id
5907
5941
  );
@@ -6052,7 +6086,8 @@ function ExpandedSubTables({
6052
6086
  column: activeCol,
6053
6087
  value: (rowData == null ? void 0 : rowData[activeCol.name]) || [],
6054
6088
  onChange: (newVal) => handleCellChange(rowId, activeCol.name, newVal),
6055
- context
6089
+ context,
6090
+ readOnly: context == null ? void 0 : context.readOnly
6056
6091
  },
6057
6092
  activeCol.name
6058
6093
  ) })
@@ -6083,7 +6118,9 @@ var TableRow = import_react14.default.memo(function TableRow2({
6083
6118
  relationDictionaryRef,
6084
6119
  refetchRelation,
6085
6120
  executeAction,
6086
- emitEvent
6121
+ emitEvent,
6122
+ readOnly,
6123
+ permissions
6087
6124
  }) {
6088
6125
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react14.default.Fragment, { children: [
6089
6126
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { className: `hover:bg-blue-50/30 transition-colors group ${isSelected ? "bg-blue-50/40" : ""}`, children: [
@@ -6135,6 +6172,7 @@ var TableRow = import_react14.default.memo(function TableRow2({
6135
6172
  column: col,
6136
6173
  rowId: row.id,
6137
6174
  onChange: (v) => handleCellChange(row.id, col.name, v),
6175
+ readOnly,
6138
6176
  context: {
6139
6177
  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",
6140
6178
  tableId: (schema == null ? void 0 : schema.tableMetaId) || (schema == null ? void 0 : schema.tableId) || (schema == null ? void 0 : schema.id),
@@ -6149,7 +6187,9 @@ var TableRow = import_react14.default.memo(function TableRow2({
6149
6187
  orgId,
6150
6188
  updateQuietly: handleQuietCellChange,
6151
6189
  triggerSaveSuccess,
6152
- rowData: row.data
6190
+ rowData: row.data,
6191
+ readOnly,
6192
+ permissions
6153
6193
  }
6154
6194
  }
6155
6195
  )
@@ -6175,7 +6215,9 @@ var TableRow = import_react14.default.memo(function TableRow2({
6175
6215
  rowData: row.data,
6176
6216
  updateQuietly: handleQuietCellChange,
6177
6217
  triggerSaveSuccess,
6178
- onUpdateColumnConfig: handleUpdateColumnConfig
6218
+ onUpdateColumnConfig: handleUpdateColumnConfig,
6219
+ readOnly,
6220
+ permissions
6179
6221
  }
6180
6222
  }
6181
6223
  )
@@ -6217,6 +6259,7 @@ var UNIVERSE_COMPONENTS = {
6217
6259
  formatTimeInput,
6218
6260
  formatTimeIso,
6219
6261
  getDeterministicPastelBadge,
6262
+ getRolePermissions,
6220
6263
  isTextRepresentable,
6221
6264
  measureTextWidth,
6222
6265
  normalizeRelationValue,