@fayz-ai/admin 0.43.4 → 0.44.1
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/ScheduleEditor-MNHZCQIG.js +6 -0
- package/dist/{ScheduleEditor-ZEG6PG3Z.js.map → ScheduleEditor-MNHZCQIG.js.map} +1 -1
- package/dist/SubscriptionPage-JWWS44SE.js +9 -0
- package/dist/{SubscriptionPage-VXZ37WZZ.js.map → SubscriptionPage-JWWS44SE.js.map} +1 -1
- package/dist/app/AdminShell.d.ts.map +1 -1
- package/dist/{chunk-A5UZL52H.js → chunk-OINP2UYL.js} +3 -3
- package/dist/{chunk-A5UZL52H.js.map → chunk-OINP2UYL.js.map} +1 -1
- package/dist/{chunk-KP3LNCNP.js → chunk-V2IBAF4O.js} +954 -23
- package/dist/chunk-V2IBAF4O.js.map +1 -0
- package/dist/{chunk-HOMD2MAG.js → chunk-VH4LLIMH.js} +3 -3
- package/dist/{chunk-HOMD2MAG.js.map → chunk-VH4LLIMH.js.map} +1 -1
- package/dist/components/settings/ConnectedCompanySettings.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderDeliveryBlock.d.ts +32 -0
- package/dist/crud/archetypes/OrderDeliveryBlock.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderDetailPage.d.ts +4 -2
- package/dist/crud/archetypes/OrderDetailPage.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderDetailRail.d.ts +2 -0
- package/dist/crud/archetypes/OrderDetailRail.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderDetailView.d.ts +11 -1
- package/dist/crud/archetypes/OrderDetailView.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderFormView.d.ts +10 -2
- package/dist/crud/archetypes/OrderFormView.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderListView.d.ts +20 -1
- package/dist/crud/archetypes/OrderListView.d.ts.map +1 -1
- package/dist/crud/archetypes/orderStage.d.ts +16 -0
- package/dist/crud/archetypes/orderStage.d.ts.map +1 -0
- package/dist/crud/archetypes/orderVocabulary.d.ts.map +1 -1
- package/dist/crud/archetypes/useOrderKindTransitions.d.ts +10 -0
- package/dist/crud/archetypes/useOrderKindTransitions.d.ts.map +1 -0
- package/dist/crud/relation-field.d.ts.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +751 -927
- package/dist/index.js.map +1 -1
- package/dist/lib/currency.d.ts +33 -5
- package/dist/lib/currency.d.ts.map +1 -1
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/index.js +1 -2
- package/dist/plugins/createPluginContext.d.ts.map +1 -1
- package/dist/theme/utils.d.ts +9 -0
- package/dist/theme/utils.d.ts.map +1 -1
- package/dist/units/useHouseCurrencySync.d.ts +9 -0
- package/dist/units/useHouseCurrencySync.d.ts.map +1 -0
- package/dist/units/useUnitBootstrap.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/ScheduleEditor-ZEG6PG3Z.js +0 -7
- package/dist/SubscriptionPage-VXZ37WZZ.js +0 -9
- package/dist/chunk-3C5Q4OGJ.js +0 -375
- package/dist/chunk-3C5Q4OGJ.js.map +0 -1
- package/dist/chunk-KP3LNCNP.js.map +0 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { persistOrgId, cn, getPersistedOrgId } from './chunk-QOS6UAQ4.js';
|
|
1
|
+
import { usePermissionsStore } from './chunk-UQ2NTFQC.js';
|
|
2
|
+
import { persistOrgId, cn, useOrganizationStore, getPersistedOrgId } from './chunk-QOS6UAQ4.js';
|
|
3
3
|
import { createClient } from '@supabase/supabase-js';
|
|
4
|
-
import { EMPTY_APP_CONFIG, getActiveUnitId, getActiveTenantId, setGlobalSupabaseClient, useTranslation,
|
|
4
|
+
import { EMPTY_APP_CONFIG, resetActiveUnit, setUnitMode, setActiveUnitId, setScopedResources, getSupabaseClientOptional, isKnownMissing, rememberIfMissing, getActiveUnitId, getActiveTenantId, setGlobalSupabaseClient, useTranslation, isImpersonating, errorMessage, emitDomainEvent, setHouseCurrency } from '@fayz-ai/core';
|
|
5
5
|
import { createDedupedFetch } from '@fayz-ai/sdk';
|
|
6
|
-
import * as
|
|
6
|
+
import * as React3 from 'react';
|
|
7
7
|
import { useState, useEffect } from 'react';
|
|
8
8
|
import { Building2, Check, ShieldCheck } from 'lucide-react';
|
|
9
9
|
import { create } from 'zustand';
|
|
10
10
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
11
11
|
import { cn as cn$1, Checkbox, Button } from '@fayz-ai/ui';
|
|
12
|
+
import { useAuthStore, readAppHandoff } from '@fayz-ai/auth';
|
|
12
13
|
|
|
13
14
|
var _client = null;
|
|
14
15
|
function requestUrl(input) {
|
|
@@ -981,6 +982,61 @@ function createMockOrgAdapter(defaultProfiles) {
|
|
|
981
982
|
}
|
|
982
983
|
};
|
|
983
984
|
}
|
|
985
|
+
|
|
986
|
+
// src/org/choice.ts
|
|
987
|
+
var AUTOPICK_KEY = "fayz:workspace-autopick";
|
|
988
|
+
var SWITCHING_KEY = "fayz:workspace-switching";
|
|
989
|
+
function markWorkspaceSwitch(orgId) {
|
|
990
|
+
try {
|
|
991
|
+
if (typeof sessionStorage === "undefined") return;
|
|
992
|
+
sessionStorage.setItem(SWITCHING_KEY, orgId);
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function takeWorkspaceSwitch() {
|
|
997
|
+
try {
|
|
998
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
999
|
+
const v = sessionStorage.getItem(SWITCHING_KEY);
|
|
1000
|
+
if (v) sessionStorage.removeItem(SWITCHING_KEY);
|
|
1001
|
+
return v;
|
|
1002
|
+
} catch {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function getAutoPick() {
|
|
1007
|
+
try {
|
|
1008
|
+
if (typeof localStorage === "undefined") return false;
|
|
1009
|
+
return localStorage.getItem(AUTOPICK_KEY) === "1";
|
|
1010
|
+
} catch {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function setAutoPick(on) {
|
|
1015
|
+
try {
|
|
1016
|
+
if (typeof localStorage === "undefined") return;
|
|
1017
|
+
if (on) localStorage.setItem(AUTOPICK_KEY, "1");
|
|
1018
|
+
else localStorage.removeItem(AUTOPICK_KEY);
|
|
1019
|
+
} catch {
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function decideWorkspaceChoice({
|
|
1023
|
+
memberships,
|
|
1024
|
+
switchingToId = null,
|
|
1025
|
+
handoffId,
|
|
1026
|
+
pinnedId,
|
|
1027
|
+
autoPick
|
|
1028
|
+
}) {
|
|
1029
|
+
if (memberships.length === 0) return { kind: "none" };
|
|
1030
|
+
const has = (id) => id && memberships.some((m) => m.orgId === id) ? id : null;
|
|
1031
|
+
const switching = has(switchingToId);
|
|
1032
|
+
if (switching) return { kind: "resolved", orgId: switching };
|
|
1033
|
+
const handoff = has(handoffId);
|
|
1034
|
+
if (handoff) return { kind: "resolved", orgId: handoff };
|
|
1035
|
+
if (memberships.length === 1) return { kind: "resolved", orgId: memberships[0].orgId };
|
|
1036
|
+
const pinned = has(pinnedId);
|
|
1037
|
+
if (pinned && autoPick) return { kind: "resolved", orgId: pinned };
|
|
1038
|
+
return { kind: "ask", suggestedId: pinned };
|
|
1039
|
+
}
|
|
984
1040
|
var initialState = {
|
|
985
1041
|
config: EMPTY_APP_CONFIG,
|
|
986
1042
|
tenantId: null,
|
|
@@ -1023,9 +1079,9 @@ function OrgMark({ name, logoUrl, className, tone = "sidebar", current = false }
|
|
|
1023
1079
|
const identity = useAppConfigStore((state) => state.config.branding?.identity);
|
|
1024
1080
|
const pick = (key) => typeof identity?.[key] === "string" ? identity[key] : void 0;
|
|
1025
1081
|
const src = (current ? pick("markUrl") : void 0) ?? logoUrl;
|
|
1026
|
-
const [status, setStatus] =
|
|
1027
|
-
const imgRef =
|
|
1028
|
-
|
|
1082
|
+
const [status, setStatus] = React3.useState("loading");
|
|
1083
|
+
const imgRef = React3.useRef(null);
|
|
1084
|
+
React3.useLayoutEffect(() => {
|
|
1029
1085
|
const el = imgRef.current;
|
|
1030
1086
|
setStatus(el?.complete ? el.naturalWidth > 0 ? "ok" : "error" : "loading");
|
|
1031
1087
|
}, [src]);
|
|
@@ -1075,6 +1131,881 @@ function OrgMark({ name, logoUrl, className, tone = "sidebar", current = false }
|
|
|
1075
1131
|
}
|
|
1076
1132
|
);
|
|
1077
1133
|
}
|
|
1134
|
+
var UNIT_BOOTSTRAP_OBJECT = "rpc:unit_bootstrap";
|
|
1135
|
+
var UNIT_MODE_OBJECT = "tenants.unit_scoping_enabled";
|
|
1136
|
+
var SCOPED_RESOURCES_OBJECT = "scoped_resources";
|
|
1137
|
+
var UNIT_ACCESS_CHANGED = "core.unit-access.changed";
|
|
1138
|
+
function emitUnitAccessChanged(userId, unitId) {
|
|
1139
|
+
emitDomainEvent(
|
|
1140
|
+
UNIT_ACCESS_CHANGED,
|
|
1141
|
+
{ user_id: userId, unit_id: unitId },
|
|
1142
|
+
{ tenantId: getActiveTenantId() ?? null, subject: { type: "location", id: unitId } }
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
function client() {
|
|
1146
|
+
return getSupabaseClientOptional();
|
|
1147
|
+
}
|
|
1148
|
+
function mapUnit(row) {
|
|
1149
|
+
return {
|
|
1150
|
+
id: row.id,
|
|
1151
|
+
tenantId: row.tenant_id,
|
|
1152
|
+
kind: row.kind ?? "branch",
|
|
1153
|
+
name: row.name,
|
|
1154
|
+
email: row.email ?? void 0,
|
|
1155
|
+
phone: row.phone ?? void 0,
|
|
1156
|
+
address: row.address ?? void 0,
|
|
1157
|
+
city: row.city ?? void 0,
|
|
1158
|
+
state: row.state ?? void 0,
|
|
1159
|
+
country: row.country ?? "BR",
|
|
1160
|
+
postalCode: row.postal_code ?? void 0,
|
|
1161
|
+
isHeadquarters: Boolean(row.is_headquarters),
|
|
1162
|
+
isActive: row.is_active !== false,
|
|
1163
|
+
tags: row.tags ?? [],
|
|
1164
|
+
notes: row.notes ?? void 0,
|
|
1165
|
+
metadata: row.metadata ?? {},
|
|
1166
|
+
createdAt: row.created_at,
|
|
1167
|
+
updatedAt: row.updated_at,
|
|
1168
|
+
parentId: row.parent_id ?? null,
|
|
1169
|
+
code: row.code ?? null,
|
|
1170
|
+
// numeric do Postgres chega como string no PostgREST; a aritmética de
|
|
1171
|
+
// distância precisa de número, e `Number(null)` é 0, que é uma coordenada
|
|
1172
|
+
// real no golfo da Guiné.
|
|
1173
|
+
latitude: row.latitude == null ? null : Number(row.latitude),
|
|
1174
|
+
longitude: row.longitude == null ? null : Number(row.longitude)
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
async function fetchUnits(tenantId) {
|
|
1178
|
+
const db = client();
|
|
1179
|
+
if (!db) return [];
|
|
1180
|
+
const { data, error } = await db.from("v_units").select("*").eq("tenant_id", tenantId).order("is_headquarters", { ascending: false }).order("name");
|
|
1181
|
+
if (error) return [];
|
|
1182
|
+
return (data ?? []).map(mapUnit);
|
|
1183
|
+
}
|
|
1184
|
+
async function fetchMyBindings(userId, tenantId) {
|
|
1185
|
+
const db = client();
|
|
1186
|
+
if (!db) return [];
|
|
1187
|
+
const { data, error } = await db.from("v_unit_members").select("location_id, role").eq("user_id", userId).eq("tenant_id", tenantId);
|
|
1188
|
+
if (error) return [];
|
|
1189
|
+
return (data ?? []).map((r) => ({ locationId: r.location_id, role: r.role ?? null }));
|
|
1190
|
+
}
|
|
1191
|
+
async function fetchUnitBootstrap(tenantId, deviceId) {
|
|
1192
|
+
const db = client();
|
|
1193
|
+
if (!db) return null;
|
|
1194
|
+
if (isKnownMissing(UNIT_BOOTSTRAP_OBJECT)) return null;
|
|
1195
|
+
const { data, error } = await db.rpc("unit_bootstrap", {
|
|
1196
|
+
p_tenant_id: tenantId,
|
|
1197
|
+
p_device_id: deviceId ?? null
|
|
1198
|
+
});
|
|
1199
|
+
if (error) {
|
|
1200
|
+
rememberIfMissing(UNIT_BOOTSTRAP_OBJECT, error);
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
if (!data) return null;
|
|
1204
|
+
const raw = data;
|
|
1205
|
+
return {
|
|
1206
|
+
mode: Boolean(raw.mode),
|
|
1207
|
+
units: (raw.units ?? []).map(mapUnit),
|
|
1208
|
+
bindings: (raw.bindings ?? []).map((r) => ({
|
|
1209
|
+
locationId: r.location_id,
|
|
1210
|
+
role: r.role ?? null
|
|
1211
|
+
})),
|
|
1212
|
+
scoped: Object.fromEntries(
|
|
1213
|
+
Object.entries(raw.scoped ?? {}).map(([table, v]) => [
|
|
1214
|
+
table,
|
|
1215
|
+
{ unitColumn: v?.unit_column ?? "unit_id", shareable: v?.shareable !== false }
|
|
1216
|
+
])
|
|
1217
|
+
),
|
|
1218
|
+
// user_unit_ids_in walks the tree with UNION over (id, depth): a branch
|
|
1219
|
+
// reached directly AND through its HQ comes back twice.
|
|
1220
|
+
reachable: Array.isArray(raw.reachable) ? [...new Set(raw.reachable)] : void 0,
|
|
1221
|
+
devicePin: { set: Boolean(raw.device_pin_set), unitId: raw.device_pin ?? null },
|
|
1222
|
+
accountPin: { set: Boolean(raw.account_pin_set), unitId: raw.account_pin ?? null }
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
async function saveEntryUnit(tenantId, unitId, deviceId) {
|
|
1226
|
+
const db = client();
|
|
1227
|
+
if (!db || !deviceId) return;
|
|
1228
|
+
await db.rpc("set_entry_unit", {
|
|
1229
|
+
p_tenant_id: tenantId,
|
|
1230
|
+
p_unit_id: unitId ?? null,
|
|
1231
|
+
p_device_id: deviceId
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
async function fetchUnitMembers(unitId) {
|
|
1235
|
+
const db = client();
|
|
1236
|
+
if (!db) return [];
|
|
1237
|
+
const { data, error } = await db.from("v_unit_members").select("id, user_id, role, created_at").eq("location_id", unitId).order("created_at");
|
|
1238
|
+
if (error) return [];
|
|
1239
|
+
return (data ?? []).map((r) => ({
|
|
1240
|
+
id: r.id,
|
|
1241
|
+
userId: r.user_id,
|
|
1242
|
+
role: r.role ?? null,
|
|
1243
|
+
since: r.created_at ?? null
|
|
1244
|
+
}));
|
|
1245
|
+
}
|
|
1246
|
+
async function fetchMemberActivity(tenantId) {
|
|
1247
|
+
const db = client();
|
|
1248
|
+
if (!db) return {};
|
|
1249
|
+
const { data, error } = await db.rpc("tenant_member_activity", { p_tenant_id: tenantId });
|
|
1250
|
+
if (error) return {};
|
|
1251
|
+
const out = {};
|
|
1252
|
+
for (const r of data ?? []) {
|
|
1253
|
+
out[r.user_id] = {
|
|
1254
|
+
lastSignInAt: r.last_sign_in_at ?? null,
|
|
1255
|
+
signedUpAt: r.signed_up_at ?? null
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
return out;
|
|
1259
|
+
}
|
|
1260
|
+
async function fetchMemberUnits(userId, tenantId) {
|
|
1261
|
+
const tenant = tenantId ?? getActiveTenantId();
|
|
1262
|
+
if (!tenant) return [];
|
|
1263
|
+
return fetchMyBindings(userId, tenant);
|
|
1264
|
+
}
|
|
1265
|
+
async function bindUserToUnit(unitId, userId, role) {
|
|
1266
|
+
const db = client();
|
|
1267
|
+
if (!db) throw new Error("sem conex\xE3o");
|
|
1268
|
+
const { error } = await db.rpc("unit_member_set", { p_unit: unitId, p_user: userId, p_role: role });
|
|
1269
|
+
if (error) throw error;
|
|
1270
|
+
emitUnitAccessChanged(userId, unitId);
|
|
1271
|
+
}
|
|
1272
|
+
async function unbindUserFromUnit(unitId, userId) {
|
|
1273
|
+
const db = client();
|
|
1274
|
+
if (!db) throw new Error("sem conex\xE3o");
|
|
1275
|
+
const { error } = await db.rpc("unit_member_remove", { p_unit: unitId, p_user: userId });
|
|
1276
|
+
if (error) throw error;
|
|
1277
|
+
emitUnitAccessChanged(userId, unitId);
|
|
1278
|
+
}
|
|
1279
|
+
async function fetchUserIdForPerson(personId) {
|
|
1280
|
+
const db = client();
|
|
1281
|
+
if (!db) return null;
|
|
1282
|
+
const { data, error } = await db.from("v_team_members").select("user_id").eq("person_id", personId).maybeSingle();
|
|
1283
|
+
if (error || !data) return null;
|
|
1284
|
+
return data.user_id ?? null;
|
|
1285
|
+
}
|
|
1286
|
+
async function fetchScopedResources() {
|
|
1287
|
+
const db = client();
|
|
1288
|
+
if (!db) return {};
|
|
1289
|
+
if (isKnownMissing(SCOPED_RESOURCES_OBJECT)) return {};
|
|
1290
|
+
const { data, error } = await db.from("scoped_resources").select("resource_table, unit_column, shareable");
|
|
1291
|
+
if (error) {
|
|
1292
|
+
rememberIfMissing(SCOPED_RESOURCES_OBJECT, error);
|
|
1293
|
+
return {};
|
|
1294
|
+
}
|
|
1295
|
+
const out = {};
|
|
1296
|
+
for (const r of data ?? []) {
|
|
1297
|
+
out[r.resource_table] = {
|
|
1298
|
+
unitColumn: r.unit_column ?? "unit_id",
|
|
1299
|
+
shareable: r.shareable !== false
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
return out;
|
|
1303
|
+
}
|
|
1304
|
+
async function fetchUnitMode(tenantId) {
|
|
1305
|
+
const db = client();
|
|
1306
|
+
if (!db) return { installed: false, on: false };
|
|
1307
|
+
if (isKnownMissing(UNIT_MODE_OBJECT)) return { installed: false, on: false };
|
|
1308
|
+
const { data, error } = await db.from("tenants").select("unit_scoping_enabled").eq("id", tenantId).maybeSingle();
|
|
1309
|
+
if (error) {
|
|
1310
|
+
rememberIfMissing(UNIT_MODE_OBJECT, error);
|
|
1311
|
+
return { installed: false, on: false };
|
|
1312
|
+
}
|
|
1313
|
+
return { installed: true, on: Boolean(data?.unit_scoping_enabled) };
|
|
1314
|
+
}
|
|
1315
|
+
async function setUnitModeRemote(tenantId, on) {
|
|
1316
|
+
const db = client();
|
|
1317
|
+
if (!db) throw new Error("sem conex\xE3o");
|
|
1318
|
+
const { error } = await db.from("tenants").update({ unit_scoping_enabled: on }).eq("id", tenantId);
|
|
1319
|
+
if (error) throw error;
|
|
1320
|
+
}
|
|
1321
|
+
async function bindAllMembersTo(tenantId, unitId) {
|
|
1322
|
+
const db = client();
|
|
1323
|
+
if (!db) return 0;
|
|
1324
|
+
const { data } = await db.from("v_team_members").select("user_id").eq("tenant_id", tenantId);
|
|
1325
|
+
const userIds = (data ?? []).map((m) => m.user_id);
|
|
1326
|
+
if (userIds.length === 0) return 0;
|
|
1327
|
+
for (const userId of userIds) {
|
|
1328
|
+
const { error } = await db.rpc("unit_member_set", { p_unit: unitId, p_user: userId, p_role: null });
|
|
1329
|
+
if (error) throw error;
|
|
1330
|
+
}
|
|
1331
|
+
emitUnitAccessChanged(null, unitId);
|
|
1332
|
+
return userIds.length;
|
|
1333
|
+
}
|
|
1334
|
+
async function countUnboundMembers(tenantId) {
|
|
1335
|
+
const db = client();
|
|
1336
|
+
if (!db) return 0;
|
|
1337
|
+
const { data: members } = await db.from("v_team_members").select("user_id").eq("tenant_id", tenantId);
|
|
1338
|
+
const { data: units } = await db.from("v_units").select("id").eq("tenant_id", tenantId);
|
|
1339
|
+
const unitIds = (units ?? []).map((u) => u.id);
|
|
1340
|
+
if (unitIds.length === 0) return (members ?? []).length;
|
|
1341
|
+
const { data: bound } = await db.from("v_unit_members").select("user_id").in("location_id", unitIds);
|
|
1342
|
+
const boundSet = new Set((bound ?? []).map((b) => b.user_id));
|
|
1343
|
+
return (members ?? []).filter((m) => !boundSet.has(m.user_id)).length;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/units/cache.ts
|
|
1347
|
+
var STORAGE_KEY2 = "fayz:units-snapshot";
|
|
1348
|
+
var VERSION = 1;
|
|
1349
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1350
|
+
var MAX_BYTES = 256 * 1024;
|
|
1351
|
+
function storage() {
|
|
1352
|
+
try {
|
|
1353
|
+
if (typeof localStorage === "undefined") return null;
|
|
1354
|
+
return localStorage;
|
|
1355
|
+
} catch {
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
function readUnitSnapshot() {
|
|
1360
|
+
const store = storage();
|
|
1361
|
+
if (!store) return null;
|
|
1362
|
+
try {
|
|
1363
|
+
const raw = store.getItem(STORAGE_KEY2);
|
|
1364
|
+
if (!raw) return null;
|
|
1365
|
+
const snapshot = JSON.parse(raw);
|
|
1366
|
+
if (snapshot?.version !== VERSION || !snapshot.tenantId || !snapshot.userId) return null;
|
|
1367
|
+
const age = Date.now() - snapshot.savedAt;
|
|
1368
|
+
if (age < 0 || age > MAX_AGE_MS) {
|
|
1369
|
+
store.removeItem(STORAGE_KEY2);
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
return snapshot;
|
|
1373
|
+
} catch {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
function saveUnitSnapshot(input) {
|
|
1378
|
+
const store = storage();
|
|
1379
|
+
if (!store) return;
|
|
1380
|
+
try {
|
|
1381
|
+
const raw = JSON.stringify({ ...input, version: VERSION, savedAt: Date.now() });
|
|
1382
|
+
if (raw.length > MAX_BYTES) return;
|
|
1383
|
+
store.setItem(STORAGE_KEY2, raw);
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function patchUnitSnapshotActive(activeUnitId) {
|
|
1388
|
+
const store = storage();
|
|
1389
|
+
const current = readUnitSnapshot();
|
|
1390
|
+
if (!store || !current) return;
|
|
1391
|
+
try {
|
|
1392
|
+
store.setItem(STORAGE_KEY2, JSON.stringify({ ...current, activeUnitId }));
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function clearUnitSnapshot() {
|
|
1397
|
+
try {
|
|
1398
|
+
storage()?.removeItem(STORAGE_KEY2);
|
|
1399
|
+
} catch {
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/units/device.ts
|
|
1404
|
+
var STORAGE_KEY3 = "fayz:device-id";
|
|
1405
|
+
var cached = null;
|
|
1406
|
+
function randomId() {
|
|
1407
|
+
try {
|
|
1408
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1409
|
+
return crypto.randomUUID();
|
|
1410
|
+
}
|
|
1411
|
+
} catch {
|
|
1412
|
+
}
|
|
1413
|
+
return `d-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
1414
|
+
}
|
|
1415
|
+
function getDeviceId() {
|
|
1416
|
+
if (cached) return cached;
|
|
1417
|
+
try {
|
|
1418
|
+
if (typeof localStorage === "undefined") return void 0;
|
|
1419
|
+
const existing = localStorage.getItem(STORAGE_KEY3);
|
|
1420
|
+
if (existing) {
|
|
1421
|
+
cached = existing;
|
|
1422
|
+
return existing;
|
|
1423
|
+
}
|
|
1424
|
+
const fresh = randomId();
|
|
1425
|
+
localStorage.setItem(STORAGE_KEY3, fresh);
|
|
1426
|
+
cached = fresh;
|
|
1427
|
+
return fresh;
|
|
1428
|
+
} catch {
|
|
1429
|
+
return void 0;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/lib/dedup.ts
|
|
1434
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
1435
|
+
function dedup2(key, fn) {
|
|
1436
|
+
const existing = inflight.get(key);
|
|
1437
|
+
if (existing) return existing;
|
|
1438
|
+
const promise = fn().finally(() => {
|
|
1439
|
+
inflight.delete(key);
|
|
1440
|
+
});
|
|
1441
|
+
inflight.set(key, promise);
|
|
1442
|
+
return promise;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/units/entry.ts
|
|
1446
|
+
function resolveEntry(reachable, pins) {
|
|
1447
|
+
const canStand = (id) => id != null && reachable.includes(id);
|
|
1448
|
+
const ladder = [
|
|
1449
|
+
[pins.device, "device"],
|
|
1450
|
+
[pins.account, "account"]
|
|
1451
|
+
];
|
|
1452
|
+
for (const [pin, reason] of ladder) {
|
|
1453
|
+
if (!pin?.set) continue;
|
|
1454
|
+
if (pin.unitId === null) return { unitId: void 0, reason: "all", needsChoice: false };
|
|
1455
|
+
if (canStand(pin.unitId)) return { unitId: pin.unitId, reason, needsChoice: false };
|
|
1456
|
+
}
|
|
1457
|
+
if (canStand(pins.legacy)) return { unitId: pins.legacy, reason: "device", needsChoice: false };
|
|
1458
|
+
if (reachable.length === 1) return { unitId: reachable[0], reason: "only", needsChoice: false };
|
|
1459
|
+
return { unitId: void 0, reason: "all", needsChoice: reachable.length > 1 };
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/units/store.ts
|
|
1463
|
+
var LEGACY_KEY = "saas-core:current-location";
|
|
1464
|
+
function legacyPin() {
|
|
1465
|
+
try {
|
|
1466
|
+
return localStorage.getItem(LEGACY_KEY) ?? void 0;
|
|
1467
|
+
} catch {
|
|
1468
|
+
return void 0;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function buildTree(units) {
|
|
1472
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1473
|
+
for (const u of units) nodes.set(u.id, { ...u, children: [] });
|
|
1474
|
+
const roots = [];
|
|
1475
|
+
for (const node of nodes.values()) {
|
|
1476
|
+
const parent = node.parentId ? nodes.get(node.parentId) : void 0;
|
|
1477
|
+
if (parent && parent.id !== node.id) parent.children.push(node);
|
|
1478
|
+
else roots.push(node);
|
|
1479
|
+
}
|
|
1480
|
+
return roots;
|
|
1481
|
+
}
|
|
1482
|
+
function expandDown(units, seeds) {
|
|
1483
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
1484
|
+
for (const u of units) {
|
|
1485
|
+
if (!u.parentId) continue;
|
|
1486
|
+
const list = childrenOf.get(u.parentId) ?? [];
|
|
1487
|
+
list.push(u.id);
|
|
1488
|
+
childrenOf.set(u.parentId, list);
|
|
1489
|
+
}
|
|
1490
|
+
const out = /* @__PURE__ */ new Set();
|
|
1491
|
+
const queue = [...seeds];
|
|
1492
|
+
while (queue.length > 0) {
|
|
1493
|
+
const id = queue.shift();
|
|
1494
|
+
if (out.has(id)) continue;
|
|
1495
|
+
out.add(id);
|
|
1496
|
+
for (const child of childrenOf.get(id) ?? []) queue.push(child);
|
|
1497
|
+
}
|
|
1498
|
+
return [...out];
|
|
1499
|
+
}
|
|
1500
|
+
function branchesOnly(ids, byId) {
|
|
1501
|
+
return ids.filter((id) => byId[id]?.kind !== "region");
|
|
1502
|
+
}
|
|
1503
|
+
var useUnitStore = create((set, get) => ({
|
|
1504
|
+
units: [],
|
|
1505
|
+
byId: {},
|
|
1506
|
+
tree: [],
|
|
1507
|
+
myUnitIds: [],
|
|
1508
|
+
myBindings: [],
|
|
1509
|
+
activeUnitId: void 0,
|
|
1510
|
+
entryReason: "all",
|
|
1511
|
+
unitMode: false,
|
|
1512
|
+
loading: false,
|
|
1513
|
+
resolved: false,
|
|
1514
|
+
needsChoice: false,
|
|
1515
|
+
loadedFor: null,
|
|
1516
|
+
// Uma chamada por conta, por mais telas que peçam.
|
|
1517
|
+
//
|
|
1518
|
+
// Dez componentes chamam `useUnitBootstrap` — o seletor, a linha de
|
|
1519
|
+
// identidade, o rodapé do registro, a aba de unidades de uma pessoa — e todos
|
|
1520
|
+
// montam no mesmo quadro. A guarda anterior era `loadedFor === tenantId &&
|
|
1521
|
+
// !loading`, que deixa passar exatamente enquanto a primeira chamada está no
|
|
1522
|
+
// ar: o segundo componente via a conta já marcada, via `loading` ligado, e a
|
|
1523
|
+
// condição inteira dava falso. Resultado: uma requisição por tela, todas
|
|
1524
|
+
// idênticas. Juntar cinco viagens numa e depois disparar essa uma cinco vezes
|
|
1525
|
+
// não economiza nada.
|
|
1526
|
+
//
|
|
1527
|
+
// `dedup` é o mesmo utilitário que a tela de equipe já usa: quem chegar
|
|
1528
|
+
// enquanto a primeira está no ar recebe a MESMA promessa em vez de abrir
|
|
1529
|
+
// outra.
|
|
1530
|
+
async load(tenantId, userId) {
|
|
1531
|
+
if (!tenantId) return;
|
|
1532
|
+
if (get().loadedFor === tenantId && !get().loading) return;
|
|
1533
|
+
return dedup2(`units:${tenantId}:${userId ?? ""}`, () => loadOnce(tenantId, userId, set, get));
|
|
1534
|
+
},
|
|
1535
|
+
setActive(unitId, reason = "manual") {
|
|
1536
|
+
setActiveUnitId(unitId);
|
|
1537
|
+
set({ activeUnitId: unitId, entryReason: reason, needsChoice: false });
|
|
1538
|
+
patchUnitSnapshotActive(unitId ?? null);
|
|
1539
|
+
const tenantId = get().loadedFor;
|
|
1540
|
+
if (tenantId) void saveEntryUnit(tenantId, unitId, getDeviceId());
|
|
1541
|
+
},
|
|
1542
|
+
setMode(on) {
|
|
1543
|
+
setUnitMode(on);
|
|
1544
|
+
set({ unitMode: on });
|
|
1545
|
+
if (!on) {
|
|
1546
|
+
setActiveUnitId(void 0);
|
|
1547
|
+
set({ activeUnitId: void 0, entryReason: "all", needsChoice: false });
|
|
1548
|
+
patchUnitSnapshotActive(null);
|
|
1549
|
+
}
|
|
1550
|
+
},
|
|
1551
|
+
reset() {
|
|
1552
|
+
clearUnitSnapshot();
|
|
1553
|
+
resetActiveUnit();
|
|
1554
|
+
set({
|
|
1555
|
+
units: [],
|
|
1556
|
+
byId: {},
|
|
1557
|
+
tree: [],
|
|
1558
|
+
myUnitIds: [],
|
|
1559
|
+
myBindings: [],
|
|
1560
|
+
activeUnitId: void 0,
|
|
1561
|
+
entryReason: "all",
|
|
1562
|
+
unitMode: false,
|
|
1563
|
+
loading: false,
|
|
1564
|
+
resolved: false,
|
|
1565
|
+
needsChoice: false,
|
|
1566
|
+
loadedFor: null
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}));
|
|
1570
|
+
async function loadOnce(tenantId, userId, set, get) {
|
|
1571
|
+
set({ loading: true, loadedFor: tenantId });
|
|
1572
|
+
try {
|
|
1573
|
+
const deviceId = getDeviceId();
|
|
1574
|
+
const boot = await fetchUnitBootstrap(tenantId, deviceId);
|
|
1575
|
+
let mode;
|
|
1576
|
+
let units;
|
|
1577
|
+
let bindings;
|
|
1578
|
+
let scoped;
|
|
1579
|
+
let reachableFromServer;
|
|
1580
|
+
if (boot) {
|
|
1581
|
+
;
|
|
1582
|
+
[mode, units, bindings, scoped] = [boot.mode, boot.units, boot.bindings, boot.scoped];
|
|
1583
|
+
reachableFromServer = boot.reachable;
|
|
1584
|
+
} else {
|
|
1585
|
+
const modeResult = await fetchUnitMode(tenantId);
|
|
1586
|
+
mode = modeResult.on;
|
|
1587
|
+
if (!modeResult.installed) {
|
|
1588
|
+
units = [];
|
|
1589
|
+
bindings = [];
|
|
1590
|
+
scoped = {};
|
|
1591
|
+
} else {
|
|
1592
|
+
;
|
|
1593
|
+
[units, bindings, scoped] = await Promise.all([
|
|
1594
|
+
fetchUnits(tenantId),
|
|
1595
|
+
userId ? fetchMyBindings(userId, tenantId) : Promise.resolve([]),
|
|
1596
|
+
mode ? fetchScopedResources() : Promise.resolve({})
|
|
1597
|
+
]);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
setScopedResources(scoped);
|
|
1601
|
+
const byId = {};
|
|
1602
|
+
for (const u of units) byId[u.id] = u;
|
|
1603
|
+
const reachable = reachableFromServer ? branchesOnly(reachableFromServer.filter((id) => byId[id]), byId) : branchesOnly(expandDown(units, bindings.map((b) => b.locationId)), byId);
|
|
1604
|
+
const entry = resolveEntry(reachable, {
|
|
1605
|
+
device: boot?.devicePin,
|
|
1606
|
+
account: boot?.accountPin,
|
|
1607
|
+
legacy: legacyPin()
|
|
1608
|
+
});
|
|
1609
|
+
setUnitMode(mode);
|
|
1610
|
+
setActiveUnitId(entry.unitId);
|
|
1611
|
+
set({
|
|
1612
|
+
units,
|
|
1613
|
+
byId,
|
|
1614
|
+
tree: buildTree(units),
|
|
1615
|
+
myUnitIds: reachable,
|
|
1616
|
+
myBindings: bindings,
|
|
1617
|
+
unitMode: mode,
|
|
1618
|
+
activeUnitId: entry.unitId,
|
|
1619
|
+
entryReason: entry.reason,
|
|
1620
|
+
needsChoice: entry.needsChoice,
|
|
1621
|
+
loading: false,
|
|
1622
|
+
resolved: true
|
|
1623
|
+
});
|
|
1624
|
+
if (userId) {
|
|
1625
|
+
saveUnitSnapshot({
|
|
1626
|
+
userId,
|
|
1627
|
+
tenantId,
|
|
1628
|
+
mode,
|
|
1629
|
+
units,
|
|
1630
|
+
bindings,
|
|
1631
|
+
myUnitIds: reachable,
|
|
1632
|
+
activeUnitId: entry.unitId ?? null,
|
|
1633
|
+
scoped
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
} finally {
|
|
1637
|
+
if (get().loading) set({ loading: false });
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
(function hydrate() {
|
|
1641
|
+
const snapshot = readUnitSnapshot();
|
|
1642
|
+
if (!snapshot) return;
|
|
1643
|
+
const org = readOrgSnapshot();
|
|
1644
|
+
if (!org || org.org?.id !== snapshot.tenantId || org.userId !== snapshot.userId) {
|
|
1645
|
+
clearUnitSnapshot();
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const byId = {};
|
|
1649
|
+
for (const u of snapshot.units) byId[u.id] = u;
|
|
1650
|
+
const active = snapshot.activeUnitId ?? void 0;
|
|
1651
|
+
const standable = active && snapshot.myUnitIds.includes(active) ? active : void 0;
|
|
1652
|
+
setScopedResources(snapshot.scoped);
|
|
1653
|
+
setUnitMode(snapshot.mode);
|
|
1654
|
+
setActiveUnitId(standable);
|
|
1655
|
+
useUnitStore.setState({
|
|
1656
|
+
units: snapshot.units,
|
|
1657
|
+
byId,
|
|
1658
|
+
tree: buildTree(snapshot.units),
|
|
1659
|
+
myUnitIds: snapshot.myUnitIds,
|
|
1660
|
+
myBindings: snapshot.bindings,
|
|
1661
|
+
unitMode: snapshot.mode,
|
|
1662
|
+
activeUnitId: standable,
|
|
1663
|
+
entryReason: standable ? "device" : "all",
|
|
1664
|
+
resolved: true
|
|
1665
|
+
});
|
|
1666
|
+
})();
|
|
1667
|
+
|
|
1668
|
+
// src/units/useHouseCurrencySync.ts
|
|
1669
|
+
function useHouseCurrencySync(orgId, tenantCurrency) {
|
|
1670
|
+
const activeUnitId = useUnitStore((s) => s.activeUnitId);
|
|
1671
|
+
React3.useEffect(() => {
|
|
1672
|
+
if (!orgId) {
|
|
1673
|
+
setHouseCurrency(null);
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
setHouseCurrency(tenantCurrency ?? null);
|
|
1677
|
+
if (!activeUnitId) return;
|
|
1678
|
+
let alive = true;
|
|
1679
|
+
void unitCurrency(orgId, activeUnitId).then((code) => {
|
|
1680
|
+
if (alive && code) setHouseCurrency(code);
|
|
1681
|
+
});
|
|
1682
|
+
return () => {
|
|
1683
|
+
alive = false;
|
|
1684
|
+
};
|
|
1685
|
+
}, [orgId, activeUnitId, tenantCurrency]);
|
|
1686
|
+
}
|
|
1687
|
+
var unitCurrencyCache = /* @__PURE__ */ new Map();
|
|
1688
|
+
function unitCurrency(orgId, unitId) {
|
|
1689
|
+
const key = `${orgId}:${unitId}`;
|
|
1690
|
+
let pending = unitCurrencyCache.get(key);
|
|
1691
|
+
if (!pending) {
|
|
1692
|
+
const client2 = getSupabaseClientOptional();
|
|
1693
|
+
pending = client2 ? Promise.resolve(client2.rpc("my_unit_currency", { p_unit: unitId })).then((res) => res.error || typeof res.data !== "string" ? null : res.data).catch(() => null) : Promise.resolve(null);
|
|
1694
|
+
unitCurrencyCache.set(key, pending);
|
|
1695
|
+
}
|
|
1696
|
+
return pending;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/org/cache.ts
|
|
1700
|
+
var STORAGE_KEY4 = "fayz:org-snapshot";
|
|
1701
|
+
var VERSION2 = 1;
|
|
1702
|
+
var ORG_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1703
|
+
var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
|
|
1704
|
+
var MAX_BYTES2 = 512 * 1024;
|
|
1705
|
+
function storage2() {
|
|
1706
|
+
try {
|
|
1707
|
+
if (typeof localStorage === "undefined") return null;
|
|
1708
|
+
return localStorage;
|
|
1709
|
+
} catch {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
function readOrgSnapshot() {
|
|
1714
|
+
const store = storage2();
|
|
1715
|
+
if (!store) return null;
|
|
1716
|
+
try {
|
|
1717
|
+
const raw = store.getItem(STORAGE_KEY4);
|
|
1718
|
+
if (!raw) return null;
|
|
1719
|
+
const snapshot = JSON.parse(raw);
|
|
1720
|
+
if (snapshot?.version !== VERSION2 || !snapshot.org?.id || !snapshot.userId) return null;
|
|
1721
|
+
if (snapshotAge(snapshot) > MAX_AGE_MS2) {
|
|
1722
|
+
store.removeItem(STORAGE_KEY4);
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
return snapshot;
|
|
1726
|
+
} catch {
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
function snapshotAge(snapshot) {
|
|
1731
|
+
const age = Date.now() - snapshot.savedAt;
|
|
1732
|
+
return age < 0 ? Number.POSITIVE_INFINITY : age;
|
|
1733
|
+
}
|
|
1734
|
+
function isSnapshotFresh(snapshot, ttlMs) {
|
|
1735
|
+
return snapshotAge(snapshot) < ttlMs;
|
|
1736
|
+
}
|
|
1737
|
+
function saveOrgSnapshot(input) {
|
|
1738
|
+
write({ ...input, version: VERSION2, savedAt: Date.now() });
|
|
1739
|
+
}
|
|
1740
|
+
function patchOrgSnapshot(input) {
|
|
1741
|
+
const current = readOrgSnapshot();
|
|
1742
|
+
if (!current || current.userId !== input.userId) return;
|
|
1743
|
+
write({ ...input, version: VERSION2, savedAt: current.savedAt });
|
|
1744
|
+
}
|
|
1745
|
+
function clearOrgSnapshot() {
|
|
1746
|
+
try {
|
|
1747
|
+
storage2()?.removeItem(STORAGE_KEY4);
|
|
1748
|
+
} catch {
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function write(snapshot) {
|
|
1752
|
+
const store = storage2();
|
|
1753
|
+
if (!store) return;
|
|
1754
|
+
try {
|
|
1755
|
+
const raw = JSON.stringify(snapshot);
|
|
1756
|
+
if (raw.length > MAX_BYTES2) return;
|
|
1757
|
+
store.setItem(STORAGE_KEY4, raw);
|
|
1758
|
+
} catch {
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
var OrgAdapterContext = React3.createContext(null);
|
|
1762
|
+
function useOrgAdapter() {
|
|
1763
|
+
const adapter = React3.useContext(OrgAdapterContext);
|
|
1764
|
+
if (!adapter) {
|
|
1765
|
+
throw new Error("useOrgAdapter must be used within an <OrgProvider>");
|
|
1766
|
+
}
|
|
1767
|
+
return adapter;
|
|
1768
|
+
}
|
|
1769
|
+
function useOrgAdapterOptional() {
|
|
1770
|
+
return React3.useContext(OrgAdapterContext);
|
|
1771
|
+
}
|
|
1772
|
+
var TenantContext = React3.createContext(null);
|
|
1773
|
+
function useTenant() {
|
|
1774
|
+
const ctx = React3.useContext(TenantContext);
|
|
1775
|
+
if (!ctx) {
|
|
1776
|
+
throw new Error("useTenant must be used inside <OrgProvider>");
|
|
1777
|
+
}
|
|
1778
|
+
return ctx;
|
|
1779
|
+
}
|
|
1780
|
+
function useTenantOptional() {
|
|
1781
|
+
return React3.useContext(TenantContext);
|
|
1782
|
+
}
|
|
1783
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React3.useLayoutEffect : React3.useEffect;
|
|
1784
|
+
function OrgProvider({
|
|
1785
|
+
adapter,
|
|
1786
|
+
autoCreate = false,
|
|
1787
|
+
cacheTtlMs = ORG_CACHE_TTL_MS,
|
|
1788
|
+
children
|
|
1789
|
+
}) {
|
|
1790
|
+
const { user, isLoading: authLoading } = useAuthStore();
|
|
1791
|
+
const { currentOrg, userOrgs, loading, setCurrentOrg, paintCurrentOrg, setUserOrgs, setMembers, setLoading, reset } = useOrganizationStore();
|
|
1792
|
+
const setProfiles = usePermissionsStore((s) => s.setProfiles);
|
|
1793
|
+
const setCurrentProfile = usePermissionsStore((s) => s.setCurrentProfile);
|
|
1794
|
+
const setProfileKnownFor = usePermissionsStore((s) => s.setProfileKnownFor);
|
|
1795
|
+
const adapterRef = React3.useRef(adapter);
|
|
1796
|
+
adapterRef.current = adapter;
|
|
1797
|
+
const [snapshot] = React3.useState(
|
|
1798
|
+
() => cacheTtlMs > 0 ? readOrgSnapshot() : null
|
|
1799
|
+
);
|
|
1800
|
+
const loadedForRef = React3.useRef(null);
|
|
1801
|
+
const [settledFor, setSettledFor] = React3.useState(
|
|
1802
|
+
() => snapshot?.org ? snapshot.userId : null
|
|
1803
|
+
);
|
|
1804
|
+
const [orgsReadFor, setOrgsReadFor] = React3.useState(
|
|
1805
|
+
() => snapshot?.org ? snapshot.userId : null
|
|
1806
|
+
);
|
|
1807
|
+
const [loadError, setLoadError] = React3.useState(null);
|
|
1808
|
+
const [retryTick, setRetryTick] = React3.useState(0);
|
|
1809
|
+
const retryOrgs = React3.useCallback(() => {
|
|
1810
|
+
loadedForRef.current = null;
|
|
1811
|
+
setLoadError(null);
|
|
1812
|
+
setRetryTick((n) => n + 1);
|
|
1813
|
+
}, []);
|
|
1814
|
+
const settled = user ? settledFor === user.id : !authLoading;
|
|
1815
|
+
const orgsKnown = !!user && orgsReadFor === user.id;
|
|
1816
|
+
useIsomorphicLayoutEffect(() => {
|
|
1817
|
+
if (!snapshot) return;
|
|
1818
|
+
setUserOrgs(snapshot.userOrgs);
|
|
1819
|
+
paintCurrentOrg(snapshot.org);
|
|
1820
|
+
setMembers(snapshot.members);
|
|
1821
|
+
setProfiles(snapshot.profiles);
|
|
1822
|
+
setCurrentProfile(snapshot.profiles.find((p) => p.id === snapshot.currentProfileId) ?? null);
|
|
1823
|
+
setProfileKnownFor(snapshot.org.id);
|
|
1824
|
+
}, []);
|
|
1825
|
+
React3.useEffect(() => {
|
|
1826
|
+
if (cacheTtlMs <= 0 || !user) return;
|
|
1827
|
+
const uid = user.id;
|
|
1828
|
+
const sync = () => {
|
|
1829
|
+
const org = useOrganizationStore.getState();
|
|
1830
|
+
if (!org.currentOrg) return;
|
|
1831
|
+
const perms = usePermissionsStore.getState();
|
|
1832
|
+
patchOrgSnapshot({
|
|
1833
|
+
userId: uid,
|
|
1834
|
+
org: org.currentOrg,
|
|
1835
|
+
userOrgs: org.userOrgs,
|
|
1836
|
+
members: org.members,
|
|
1837
|
+
profiles: perms.profiles,
|
|
1838
|
+
// realProfile, not currentProfile: an impersonation preview is not
|
|
1839
|
+
// something to cache back as the user's own role.
|
|
1840
|
+
currentProfileId: perms.realProfile?.id ?? null
|
|
1841
|
+
});
|
|
1842
|
+
};
|
|
1843
|
+
const unsubscribeOrg = useOrganizationStore.subscribe(sync);
|
|
1844
|
+
const unsubscribePerms = usePermissionsStore.subscribe(sync);
|
|
1845
|
+
return () => {
|
|
1846
|
+
unsubscribeOrg();
|
|
1847
|
+
unsubscribePerms();
|
|
1848
|
+
};
|
|
1849
|
+
}, [user?.id, cacheTtlMs]);
|
|
1850
|
+
const hydrateOrg = React3.useCallback(
|
|
1851
|
+
async (org, memberships, uid) => {
|
|
1852
|
+
try {
|
|
1853
|
+
const [members, profiles] = await Promise.all([
|
|
1854
|
+
adapterRef.current.listMembers(org.id),
|
|
1855
|
+
adapterRef.current.listProfiles(org.id)
|
|
1856
|
+
]);
|
|
1857
|
+
setMembers(members);
|
|
1858
|
+
setProfiles(profiles);
|
|
1859
|
+
const myMembership = uid ? members.find((m) => m.userId === uid) : void 0;
|
|
1860
|
+
const profile = profiles.find((p) => p.id === myMembership?.profileId) ?? null;
|
|
1861
|
+
setCurrentProfile(profile);
|
|
1862
|
+
if (cacheTtlMs > 0 && uid) {
|
|
1863
|
+
saveOrgSnapshot({
|
|
1864
|
+
userId: uid,
|
|
1865
|
+
org,
|
|
1866
|
+
userOrgs: memberships,
|
|
1867
|
+
members,
|
|
1868
|
+
profiles,
|
|
1869
|
+
currentProfileId: profile?.id ?? null
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
} catch {
|
|
1873
|
+
} finally {
|
|
1874
|
+
setProfileKnownFor(org.id);
|
|
1875
|
+
}
|
|
1876
|
+
},
|
|
1877
|
+
[setMembers, setProfiles, setCurrentProfile, setProfileKnownFor, cacheTtlMs]
|
|
1878
|
+
);
|
|
1879
|
+
React3.useEffect(() => {
|
|
1880
|
+
if (!user) {
|
|
1881
|
+
if (authLoading) return;
|
|
1882
|
+
loadedForRef.current = null;
|
|
1883
|
+
reset();
|
|
1884
|
+
clearOrgSnapshot();
|
|
1885
|
+
setSettledFor(null);
|
|
1886
|
+
setOrgsReadFor(null);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
const cached2 = snapshot?.userId === user.id ? snapshot : null;
|
|
1890
|
+
if (cached2 && isSnapshotFresh(cached2, cacheTtlMs)) {
|
|
1891
|
+
setSettledFor(user.id);
|
|
1892
|
+
setOrgsReadFor(user.id);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (snapshot && snapshot.userId !== user.id && loadedForRef.current === null) {
|
|
1896
|
+
reset();
|
|
1897
|
+
clearOrgSnapshot();
|
|
1898
|
+
}
|
|
1899
|
+
if (loadedForRef.current === user.id) return;
|
|
1900
|
+
loadedForRef.current = user.id;
|
|
1901
|
+
const silent = !!cached2;
|
|
1902
|
+
let cancelled = false;
|
|
1903
|
+
async function loadOrgs() {
|
|
1904
|
+
if (!silent) setLoading(true);
|
|
1905
|
+
try {
|
|
1906
|
+
let accepted = 0;
|
|
1907
|
+
try {
|
|
1908
|
+
if (!isImpersonating()) accepted = await adapterRef.current.acceptPendingInvites?.() ?? 0;
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
if (cancelled) return;
|
|
1912
|
+
let memberships = await adapterRef.current.listUserOrgs(user.id);
|
|
1913
|
+
if (memberships.length === 0 && accepted > 0) {
|
|
1914
|
+
memberships = await adapterRef.current.listUserOrgs(user.id);
|
|
1915
|
+
}
|
|
1916
|
+
for (let attempt = 0; memberships.length === 0 && attempt < 2; attempt++) {
|
|
1917
|
+
await new Promise((resolve) => setTimeout(resolve, 250 * (attempt + 1)));
|
|
1918
|
+
if (cancelled) return;
|
|
1919
|
+
memberships = await adapterRef.current.listUserOrgs(user.id);
|
|
1920
|
+
}
|
|
1921
|
+
if (cancelled) return;
|
|
1922
|
+
setUserOrgs(memberships);
|
|
1923
|
+
setOrgsReadFor(user.id);
|
|
1924
|
+
if (memberships.length === 0 && accepted === 0 && autoCreate) {
|
|
1925
|
+
const newOrg = await adapterRef.current.createOrg(
|
|
1926
|
+
user.fullName ? `${user.fullName}'s Organization` : "My Organization",
|
|
1927
|
+
user.id
|
|
1928
|
+
);
|
|
1929
|
+
if (!cancelled) {
|
|
1930
|
+
const ownMembership = [
|
|
1931
|
+
{
|
|
1932
|
+
orgId: newOrg.id,
|
|
1933
|
+
orgName: newOrg.name,
|
|
1934
|
+
orgSlug: newOrg.slug,
|
|
1935
|
+
profileId: "owner",
|
|
1936
|
+
profileName: "Owner"
|
|
1937
|
+
}
|
|
1938
|
+
];
|
|
1939
|
+
setUserOrgs(ownMembership);
|
|
1940
|
+
setCurrentOrg(newOrg);
|
|
1941
|
+
await hydrateOrg(newOrg, ownMembership, user.id);
|
|
1942
|
+
}
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
const choice = decideWorkspaceChoice({
|
|
1946
|
+
memberships,
|
|
1947
|
+
// Consumed here, and only here: this is the one boot the marker was
|
|
1948
|
+
// written for, and leaving it behind would pin the next one too.
|
|
1949
|
+
switchingToId: takeWorkspaceSwitch(),
|
|
1950
|
+
handoffId: readAppHandoff()?.tenantId ?? null,
|
|
1951
|
+
pinnedId: getPersistedOrgId(),
|
|
1952
|
+
autoPick: getAutoPick()
|
|
1953
|
+
});
|
|
1954
|
+
if (choice.kind !== "resolved") return;
|
|
1955
|
+
if (!cancelled) {
|
|
1956
|
+
const org = await adapterRef.current.getOrg(choice.orgId);
|
|
1957
|
+
if (!cancelled) {
|
|
1958
|
+
setCurrentOrg(org);
|
|
1959
|
+
await hydrateOrg(org, memberships, user.id);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
} catch (err) {
|
|
1963
|
+
if (!cancelled) setLoadError(errorMessage(err));
|
|
1964
|
+
} finally {
|
|
1965
|
+
if (!cancelled && !silent) setLoading(false);
|
|
1966
|
+
if (!cancelled) setSettledFor(user.id);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
void loadOrgs();
|
|
1970
|
+
return () => {
|
|
1971
|
+
cancelled = true;
|
|
1972
|
+
};
|
|
1973
|
+
}, [user?.id, authLoading, retryTick]);
|
|
1974
|
+
async function switchOrg(orgId) {
|
|
1975
|
+
setLoading(true);
|
|
1976
|
+
try {
|
|
1977
|
+
const org = await adapterRef.current.getOrg(orgId);
|
|
1978
|
+
setCurrentOrg(org);
|
|
1979
|
+
await hydrateOrg(org, useOrganizationStore.getState().userOrgs, user?.id);
|
|
1980
|
+
} finally {
|
|
1981
|
+
setLoading(false);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
async function refreshOrg() {
|
|
1985
|
+
if (!currentOrg) return;
|
|
1986
|
+
setLoading(true);
|
|
1987
|
+
try {
|
|
1988
|
+
const org = await adapterRef.current.getOrg(currentOrg.id);
|
|
1989
|
+
setCurrentOrg(org);
|
|
1990
|
+
await hydrateOrg(org, useOrganizationStore.getState().userOrgs, user?.id);
|
|
1991
|
+
} finally {
|
|
1992
|
+
setLoading(false);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
useHouseCurrencySync(currentOrg?.id, currentOrg?.settings?.currency);
|
|
1996
|
+
const tenantValue = {
|
|
1997
|
+
org: currentOrg,
|
|
1998
|
+
userOrgs,
|
|
1999
|
+
loading,
|
|
2000
|
+
settled,
|
|
2001
|
+
orgsKnown,
|
|
2002
|
+
loadError,
|
|
2003
|
+
retryOrgs,
|
|
2004
|
+
switchOrg,
|
|
2005
|
+
refreshOrg
|
|
2006
|
+
};
|
|
2007
|
+
return /* @__PURE__ */ jsx(OrgAdapterContext.Provider, { value: adapter, children: /* @__PURE__ */ jsx(TenantContext.Provider, { value: tenantValue, children }) });
|
|
2008
|
+
}
|
|
1078
2009
|
|
|
1079
2010
|
// src/org/workspace-search.ts
|
|
1080
2011
|
var WORKSPACE_SEARCH_THRESHOLD = 8;
|
|
@@ -1104,16 +2035,16 @@ function WorkspaceChooser({ memberships, suggestedId, onCancel }) {
|
|
|
1104
2035
|
return !v || v === key ? fallback : v;
|
|
1105
2036
|
};
|
|
1106
2037
|
const { switchOrg } = useTenant();
|
|
1107
|
-
const [selected, setSelected] =
|
|
2038
|
+
const [selected, setSelected] = React3.useState(
|
|
1108
2039
|
suggestedId ?? memberships[0]?.orgId ?? null
|
|
1109
2040
|
);
|
|
1110
|
-
const [remember, setRemember] =
|
|
1111
|
-
const [query, setQuery] =
|
|
1112
|
-
const search =
|
|
2041
|
+
const [remember, setRemember] = React3.useState(false);
|
|
2042
|
+
const [query, setQuery] = React3.useState("");
|
|
2043
|
+
const search = React3.useMemo(
|
|
1113
2044
|
() => searchWorkspaces(memberships, query, selected, 12),
|
|
1114
2045
|
[memberships, query, selected]
|
|
1115
2046
|
);
|
|
1116
|
-
const [entering, setEntering] =
|
|
2047
|
+
const [entering, setEntering] = React3.useState(false);
|
|
1117
2048
|
const enter = async (orgId) => {
|
|
1118
2049
|
if (entering) return;
|
|
1119
2050
|
setEntering(true);
|
|
@@ -1216,7 +2147,7 @@ function WorkspaceChooser({ memberships, suggestedId, onCancel }) {
|
|
|
1216
2147
|
) })
|
|
1217
2148
|
] }) });
|
|
1218
2149
|
}
|
|
1219
|
-
var
|
|
2150
|
+
var getSupabaseClientOptional3 = () => getSupabaseClientOptional();
|
|
1220
2151
|
|
|
1221
2152
|
// src/app/useDomainTenant.ts
|
|
1222
2153
|
var LOCAL = /^(localhost|127\.0\.0\.1|\[::1\])$/;
|
|
@@ -1232,7 +2163,7 @@ function useDomainTenant() {
|
|
|
1232
2163
|
const [resolving, setResolving] = useState(true);
|
|
1233
2164
|
useEffect(() => {
|
|
1234
2165
|
const host = resolveHostname();
|
|
1235
|
-
const supabase =
|
|
2166
|
+
const supabase = getSupabaseClientOptional3();
|
|
1236
2167
|
if (!host || !supabase) {
|
|
1237
2168
|
setResolving(false);
|
|
1238
2169
|
return;
|
|
@@ -1267,8 +2198,8 @@ function WorkspaceChooserGate({ fallback, children }) {
|
|
|
1267
2198
|
const tenant = useTenantOptional();
|
|
1268
2199
|
const { tenant: byDomain, resolving: resolvingDomain } = useDomainTenant();
|
|
1269
2200
|
const switchOrg = tenant?.switchOrg;
|
|
1270
|
-
const entering =
|
|
1271
|
-
|
|
2201
|
+
const entering = React3.useRef(false);
|
|
2202
|
+
React3.useEffect(() => {
|
|
1272
2203
|
if (!byDomain || !tenant?.orgsKnown || tenant.org || !switchOrg || entering.current) return;
|
|
1273
2204
|
if (!tenant.userOrgs.some((m) => m.orgId === byDomain.tenantId)) return;
|
|
1274
2205
|
entering.current = true;
|
|
@@ -1310,7 +2241,7 @@ function WorkspaceSwitching({ orgName, orgLogoUrl, onDone }) {
|
|
|
1310
2241
|
const v = t(key);
|
|
1311
2242
|
return !v || v === key ? fallback : v;
|
|
1312
2243
|
};
|
|
1313
|
-
const steps =
|
|
2244
|
+
const steps = React3.useMemo(
|
|
1314
2245
|
() => [
|
|
1315
2246
|
tr("organization.switching.step1", "Alterando organiza\xE7\xE3o"),
|
|
1316
2247
|
tr("organization.switching.step2", "Garantindo seguran\xE7a dos dados"),
|
|
@@ -1319,10 +2250,10 @@ function WorkspaceSwitching({ orgName, orgLogoUrl, onDone }) {
|
|
|
1319
2250
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1320
2251
|
[]
|
|
1321
2252
|
);
|
|
1322
|
-
const [step, setStep] =
|
|
1323
|
-
const doneRef =
|
|
2253
|
+
const [step, setStep] = React3.useState(0);
|
|
2254
|
+
const doneRef = React3.useRef(onDone);
|
|
1324
2255
|
doneRef.current = onDone;
|
|
1325
|
-
|
|
2256
|
+
React3.useEffect(() => {
|
|
1326
2257
|
const id = window.setInterval(() => {
|
|
1327
2258
|
setStep((s) => {
|
|
1328
2259
|
if (s < steps.length - 1) return s + 1;
|
|
@@ -1399,6 +2330,6 @@ function WorkspaceSwitchingHost() {
|
|
|
1399
2330
|
);
|
|
1400
2331
|
}
|
|
1401
2332
|
|
|
1402
|
-
export { OrgMark, WorkspaceChooser, WorkspaceChooserGate, WorkspaceSwitching, WorkspaceSwitchingHost, activePluginIds, createFayzSupabaseClient, createMockOrgAdapter, createSupabaseOrgAdapter, getFayzSupabaseClient, getFayzSupabaseClientOptional, getSupabaseClientOptional, hasPlugin, orgInitials, resolveAppId, searchWorkspaces, useAppConfigStore, useWorkspaceSwitchingStore };
|
|
1403
|
-
//# sourceMappingURL=chunk-
|
|
1404
|
-
//# sourceMappingURL=chunk-
|
|
2333
|
+
export { ORG_CACHE_TTL_MS, OrgMark, OrgProvider, UNIT_ACCESS_CHANGED, WorkspaceChooser, WorkspaceChooserGate, WorkspaceSwitching, WorkspaceSwitchingHost, activePluginIds, bindAllMembersTo, bindUserToUnit, clearOrgSnapshot, countUnboundMembers, createFayzSupabaseClient, createMockOrgAdapter, createSupabaseOrgAdapter, decideWorkspaceChoice, dedup2 as dedup, fetchMemberActivity, fetchMemberUnits, fetchUnitMembers, fetchUserIdForPerson, getAutoPick, getFayzSupabaseClient, getFayzSupabaseClientOptional, getSupabaseClientOptional3 as getSupabaseClientOptional, hasPlugin, markWorkspaceSwitch, orgInitials, readOrgSnapshot, resolveAppId, searchWorkspaces, setAutoPick, setUnitModeRemote, takeWorkspaceSwitch, unbindUserFromUnit, useAppConfigStore, useOrgAdapter, useOrgAdapterOptional, useTenant, useTenantOptional, useUnitStore, useWorkspaceSwitchingStore };
|
|
2334
|
+
//# sourceMappingURL=chunk-V2IBAF4O.js.map
|
|
2335
|
+
//# sourceMappingURL=chunk-V2IBAF4O.js.map
|