@fayz-ai/admin 0.61.0 → 0.63.0
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/{CommandPalette-B5PMQFDG.js → CommandPalette-XDJJ5E3O.js} +3 -3
- package/dist/{CommandPalette-B5PMQFDG.js.map → CommandPalette-XDJJ5E3O.js.map} +1 -1
- package/dist/ConnectedBrandingSettings-43ZM3HCI.js +680 -0
- package/dist/ConnectedBrandingSettings-43ZM3HCI.js.map +1 -0
- package/dist/ConnectedCompanySettings-I4MALI2Q.js +197 -0
- package/dist/ConnectedCompanySettings-I4MALI2Q.js.map +1 -0
- package/dist/ConnectedModulesSettings-A2VJ5BMJ.js +563 -0
- package/dist/ConnectedModulesSettings-A2VJ5BMJ.js.map +1 -0
- package/dist/ConnectedSecuritySettings-25AHA4HJ.js +189 -0
- package/dist/ConnectedSecuritySettings-25AHA4HJ.js.map +1 -0
- package/dist/ConnectedUserProfile-E7USJRUE.js +424 -0
- package/dist/ConnectedUserProfile-E7USJRUE.js.map +1 -0
- package/dist/FieldRulesEditor-YGIPKI5T.js +129 -0
- package/dist/FieldRulesEditor-YGIPKI5T.js.map +1 -0
- package/dist/GettingStartedWidget-HMIJ3Y24.js +12 -0
- package/dist/GettingStartedWidget-HMIJ3Y24.js.map +1 -0
- package/dist/PermissionProfilesTab-3ZPRCHMZ.js +548 -0
- package/dist/PermissionProfilesTab-3ZPRCHMZ.js.map +1 -0
- package/dist/PersonTimeline-HQZT527S.js +3 -0
- package/dist/{PersonTimeline-X7JTPUWJ.js.map → PersonTimeline-HQZT527S.js.map} +1 -1
- package/dist/ProjectsSettings-7O2F27MD.js +5 -0
- package/dist/ProjectsSettings-7O2F27MD.js.map +1 -0
- package/dist/ScheduleEditor-54ELWR45.js +8 -0
- package/dist/{ScheduleEditor-L2UJXKMY.js.map → ScheduleEditor-54ELWR45.js.map} +1 -1
- package/dist/SeuSiteWidget-QLSUVC63.js +6 -0
- package/dist/SeuSiteWidget-QLSUVC63.js.map +1 -0
- package/dist/SubscriptionPage-5GE5HFJK.js +17 -0
- package/dist/{SubscriptionPage-IDVEFLZ5.js.map → SubscriptionPage-5GE5HFJK.js.map} +1 -1
- package/dist/TeamTab-6RAULFLW.js +502 -0
- package/dist/TeamTab-6RAULFLW.js.map +1 -0
- package/dist/TrialWidget-Z6CLUCU3.js +7 -0
- package/dist/TrialWidget-Z6CLUCU3.js.map +1 -0
- package/dist/app/AdminShell.d.ts.map +1 -1
- package/dist/app/admin-app.d.ts.map +1 -1
- package/dist/app/config.d.ts +14 -0
- package/dist/app/config.d.ts.map +1 -1
- package/dist/app/routing.d.ts +6 -0
- package/dist/app/routing.d.ts.map +1 -1
- package/dist/app/scaffold.d.ts.map +1 -1
- package/dist/billing/index.js +9 -4
- package/dist/chunk-2H46WDG5.js +53 -0
- package/dist/chunk-2H46WDG5.js.map +1 -0
- package/dist/chunk-3HLX7YB2.js +94 -0
- package/dist/chunk-3HLX7YB2.js.map +1 -0
- package/dist/chunk-3OUD4XBK.js +3 -0
- package/dist/chunk-3OUD4XBK.js.map +1 -0
- package/dist/{chunk-IXSY4GPK.js → chunk-5D5QLMOQ.js} +5 -3
- package/dist/chunk-5D5QLMOQ.js.map +1 -0
- package/dist/chunk-5WR62X2R.js +1081 -0
- package/dist/chunk-5WR62X2R.js.map +1 -0
- package/dist/{chunk-KPYRTOCK.js → chunk-6CQFGMEO.js} +4 -140
- package/dist/chunk-6CQFGMEO.js.map +1 -0
- package/dist/chunk-6MMSYC2I.js +116 -0
- package/dist/chunk-6MMSYC2I.js.map +1 -0
- package/dist/chunk-6QFOE3UD.js +1466 -0
- package/dist/chunk-6QFOE3UD.js.map +1 -0
- package/dist/chunk-6UUFVQ2E.js +283 -0
- package/dist/chunk-6UUFVQ2E.js.map +1 -0
- package/dist/chunk-7WNLL3F6.js +7889 -0
- package/dist/chunk-7WNLL3F6.js.map +1 -0
- package/dist/chunk-A74IHAQE.js +439 -0
- package/dist/chunk-A74IHAQE.js.map +1 -0
- package/dist/{chunk-JOBRLBIQ.js → chunk-AFA6IO23.js} +4 -3
- package/dist/chunk-AFA6IO23.js.map +1 -0
- package/dist/{chunk-P5UIGQFY.js → chunk-ALCSIN3Z.js} +5 -5
- package/dist/chunk-ALCSIN3Z.js.map +1 -0
- package/dist/chunk-AWM4AMBS.js +39 -0
- package/dist/chunk-AWM4AMBS.js.map +1 -0
- package/dist/{chunk-OQARLKZN.js → chunk-BG5Z6KLN.js} +6 -18
- package/dist/chunk-BG5Z6KLN.js.map +1 -0
- package/dist/chunk-DPNFZONC.js +19 -0
- package/dist/chunk-DPNFZONC.js.map +1 -0
- package/dist/chunk-DRCV643L.js +193 -0
- package/dist/chunk-DRCV643L.js.map +1 -0
- package/dist/chunk-E6DF3VR6.js +206 -0
- package/dist/chunk-E6DF3VR6.js.map +1 -0
- package/dist/chunk-ENHCVGXZ.js +120 -0
- package/dist/chunk-ENHCVGXZ.js.map +1 -0
- package/dist/chunk-FK6RHGI7.js +56 -0
- package/dist/chunk-FK6RHGI7.js.map +1 -0
- package/dist/chunk-G5HNA5QK.js +233 -0
- package/dist/chunk-G5HNA5QK.js.map +1 -0
- package/dist/chunk-HCFO4Z6A.js +53 -0
- package/dist/chunk-HCFO4Z6A.js.map +1 -0
- package/dist/chunk-JFIU6NQP.js +51 -0
- package/dist/chunk-JFIU6NQP.js.map +1 -0
- package/dist/chunk-JRZJBMOY.js +3 -0
- package/dist/chunk-JRZJBMOY.js.map +1 -0
- package/dist/{chunk-3YP7KW47.js → chunk-JVVOL4BB.js} +41 -512
- package/dist/chunk-JVVOL4BB.js.map +1 -0
- package/dist/chunk-KAQ3UIAA.js +415 -0
- package/dist/chunk-KAQ3UIAA.js.map +1 -0
- package/dist/{chunk-P6XUDT2P.js → chunk-KJELMULA.js} +4 -51
- package/dist/chunk-KJELMULA.js.map +1 -0
- package/dist/chunk-KLO7DKXX.js +1651 -0
- package/dist/chunk-KLO7DKXX.js.map +1 -0
- package/dist/chunk-M2CMDWUI.js +75 -0
- package/dist/chunk-M2CMDWUI.js.map +1 -0
- package/dist/chunk-PQ47MLTH.js +255 -0
- package/dist/chunk-PQ47MLTH.js.map +1 -0
- package/dist/{chunk-VX3WJLKK.js → chunk-QD7BMUDF.js} +17 -8
- package/dist/chunk-QD7BMUDF.js.map +1 -0
- package/dist/{chunk-E4MESMNR.js → chunk-QTSKCPSW.js} +7 -10
- package/dist/chunk-QTSKCPSW.js.map +1 -0
- package/dist/chunk-S56FQKPL.js +186 -0
- package/dist/chunk-S56FQKPL.js.map +1 -0
- package/dist/chunk-SGAFPNMA.js +75 -0
- package/dist/chunk-SGAFPNMA.js.map +1 -0
- package/dist/{chunk-OD7P4L3D.js → chunk-SOKCLVM4.js} +3 -3
- package/dist/{chunk-OD7P4L3D.js.map → chunk-SOKCLVM4.js.map} +1 -1
- package/dist/chunk-TL42NKTH.js +3 -0
- package/dist/chunk-TL42NKTH.js.map +1 -0
- package/dist/chunk-U6ZCO2PS.js +2964 -0
- package/dist/chunk-U6ZCO2PS.js.map +1 -0
- package/dist/chunk-VSXNROYH.js +16 -0
- package/dist/chunk-VSXNROYH.js.map +1 -0
- package/dist/chunk-WPFZCYZL.js +619 -0
- package/dist/chunk-WPFZCYZL.js.map +1 -0
- package/dist/chunk-XFTNIBDW.js +23 -0
- package/dist/chunk-XFTNIBDW.js.map +1 -0
- package/dist/chunk-Y66JDL6I.js +1020 -0
- package/dist/chunk-Y66JDL6I.js.map +1 -0
- package/dist/chunk-YEHIBPIX.js +137 -0
- package/dist/chunk-YEHIBPIX.js.map +1 -0
- package/dist/chunk-YKPPCV2Z.js +53 -0
- package/dist/chunk-YKPPCV2Z.js.map +1 -0
- package/dist/components/chat/markdown.d.ts.map +1 -1
- package/dist/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
- package/dist/components/settings/SettingsPage.d.ts.map +1 -1
- package/dist/components/shared/ConnectionIndicator.d.ts.map +1 -1
- package/dist/connect-XKQJOWR7.js +18 -0
- package/dist/connect-XKQJOWR7.js.map +1 -0
- package/dist/crud/CrudDetailPage.d.ts.map +1 -1
- package/dist/crud/CrudListView.d.ts.map +1 -1
- package/dist/crud/CrudPage.d.ts.map +1 -1
- package/dist/crud/RelatedListTab.d.ts.map +1 -1
- package/dist/crud/archetypes/EntityLink.d.ts +1 -1
- package/dist/crud/archetypes/EntityLink.d.ts.map +1 -1
- package/dist/crud/record-chrome.d.ts.map +1 -1
- package/dist/entity-routes-PGU7EHAD.js +4 -0
- package/dist/{entity-routes-LXOBW5IB.js.map → entity-routes-PGU7EHAD.js.map} +1 -1
- package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
- package/dist/impersonation/ImpersonateDialog.d.ts.map +1 -1
- package/dist/impersonation/session.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11922 -33564
- package/dist/index.js.map +1 -1
- package/dist/lib/connection.d.ts +14 -0
- package/dist/lib/connection.d.ts.map +1 -1
- package/dist/lib/entity-routes.d.ts.map +1 -1
- package/dist/org/index.js +4 -2
- package/dist/plugins/PluginRegistryManager.d.ts.map +1 -1
- package/dist/settings/MessageTemplateRow.d.ts.map +1 -1
- package/dist/timeline/PersonTimeline.d.ts.map +1 -1
- package/dist/timeline/TimelineList.d.ts.map +1 -1
- package/dist/units/UnitRailPage.d.ts.map +1 -1
- package/dist/units/useUnitFilter.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/PersonTimeline-X7JTPUWJ.js +0 -3
- package/dist/ScheduleEditor-L2UJXKMY.js +0 -6
- package/dist/SubscriptionPage-IDVEFLZ5.js +0 -8
- package/dist/chunk-3YP7KW47.js.map +0 -1
- package/dist/chunk-E4MESMNR.js.map +0 -1
- package/dist/chunk-IXSY4GPK.js.map +0 -1
- package/dist/chunk-JOBRLBIQ.js.map +0 -1
- package/dist/chunk-KPYRTOCK.js.map +0 -1
- package/dist/chunk-OQARLKZN.js.map +0 -1
- package/dist/chunk-P5UIGQFY.js.map +0 -1
- package/dist/chunk-P6XUDT2P.js.map +0 -1
- package/dist/chunk-VX3WJLKK.js.map +0 -1
- package/dist/entity-routes-LXOBW5IB.js +0 -4
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { getLimitDeclaration, useAccessOptional, useUpgradeModalStore } from './chunk-S56FQKPL.js';
|
|
2
|
+
import { useBillingStore } from './chunk-SGAFPNMA.js';
|
|
3
|
+
import { usePermissionsStore } from './chunk-UQ2NTFQC.js';
|
|
4
|
+
import { useOrganizationStore } from './chunk-YKPPCV2Z.js';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import React__default from 'react';
|
|
7
|
+
import { createPortal } from 'react-dom';
|
|
8
|
+
import { create } from 'zustand';
|
|
9
|
+
import { jsx } from 'react/jsx-runtime';
|
|
10
|
+
import { countByTenant } from '@fayz-ai/core';
|
|
11
|
+
import { resolveAccess, agentDenial } from '@fayz-ai/core/access';
|
|
12
|
+
import { useAuthStore } from '@fayz-ai/auth';
|
|
13
|
+
import { PageActionBar } from '@fayz-ai/ui';
|
|
14
|
+
|
|
15
|
+
var useRightRailStore = create((set) => ({
|
|
16
|
+
mounted: false,
|
|
17
|
+
setMounted: (mounted) => set({ mounted }),
|
|
18
|
+
open: false,
|
|
19
|
+
active: null,
|
|
20
|
+
panels: [],
|
|
21
|
+
// Switching or closing drops any internal page: the drill belongs to the
|
|
22
|
+
// panel that opened it, and its title outliving it would head the wrong one.
|
|
23
|
+
openPanel: (id) => set({ open: true, active: id, drill: null, pageOpen: false, pageDrill: null }),
|
|
24
|
+
reselects: 0,
|
|
25
|
+
reselectId: null,
|
|
26
|
+
reselect: (id) => set((s) => ({ reselects: s.reselects + 1, reselectId: id })),
|
|
27
|
+
close: () => set({ open: false, drill: null, pageOpen: false, pageDrill: null }),
|
|
28
|
+
togglePanel: (id) => set(
|
|
29
|
+
(s) => s.open && s.active === id && !s.pageOpen ? { open: false, drill: null, pageOpen: false, pageDrill: null } : { open: true, active: id, drill: null, pageOpen: false, pageDrill: null }
|
|
30
|
+
),
|
|
31
|
+
registerPanel: (panel) => set((s) => {
|
|
32
|
+
const rest = s.panels.filter((p) => p.id !== panel.id);
|
|
33
|
+
const panels = [...rest, panel].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
34
|
+
return { panels, active: s.active ?? panel.id };
|
|
35
|
+
}),
|
|
36
|
+
unregisterPanel: (id) => set((s) => {
|
|
37
|
+
const panels = s.panels.filter((p) => p.id !== id);
|
|
38
|
+
const active = s.active === id ? panels[0]?.id ?? null : s.active;
|
|
39
|
+
return { panels, active, open: active ? s.open : false };
|
|
40
|
+
}),
|
|
41
|
+
updateBadge: (id, badge) => set((s) => {
|
|
42
|
+
const current = s.panels.find((p) => p.id === id);
|
|
43
|
+
if (!current) return s;
|
|
44
|
+
if (current.badge?.count === badge?.count && current.badge?.tone === badge?.tone) return s;
|
|
45
|
+
return { panels: s.panels.map((p) => p.id === id ? { ...p, badge } : p) };
|
|
46
|
+
}),
|
|
47
|
+
updateMeta: (id, meta) => set((s) => {
|
|
48
|
+
const current = s.panels.find((p) => p.id === id);
|
|
49
|
+
if (!current) return s;
|
|
50
|
+
const pick = (key) => key in meta ? meta[key] : current[key];
|
|
51
|
+
const next = {
|
|
52
|
+
...current,
|
|
53
|
+
label: pick("label") ?? current.label,
|
|
54
|
+
icon: pick("icon"),
|
|
55
|
+
iconBare: pick("iconBare"),
|
|
56
|
+
trailing: pick("trailing"),
|
|
57
|
+
sublabel: pick("sublabel")
|
|
58
|
+
};
|
|
59
|
+
if (current.label === next.label && current.icon === next.icon && current.iconBare === next.iconBare && current.trailing === next.trailing && current.sublabel === next.sublabel) return s;
|
|
60
|
+
return { panels: s.panels.map((p) => p.id === id ? next : p) };
|
|
61
|
+
}),
|
|
62
|
+
width: readRightRailWidth(),
|
|
63
|
+
setWidth: (width) => set({ width }),
|
|
64
|
+
drill: null,
|
|
65
|
+
setDrill: (drill) => set({ drill }),
|
|
66
|
+
pageDrill: null,
|
|
67
|
+
setPageDrill: (pageDrill) => set({ pageDrill }),
|
|
68
|
+
menu: [],
|
|
69
|
+
setMenu: (menu) => set({ menu }),
|
|
70
|
+
pageSlot: null,
|
|
71
|
+
setPageSlot: (pageSlot) => set({ pageSlot }),
|
|
72
|
+
pageOpen: false,
|
|
73
|
+
showPage: () => set({ open: true, pageOpen: true }),
|
|
74
|
+
closePage: () => set({ open: false, pageOpen: false, drill: null, pageDrill: null })
|
|
75
|
+
}));
|
|
76
|
+
function useRightRailPanel(panel) {
|
|
77
|
+
const register = useRightRailStore((s) => s.registerPanel);
|
|
78
|
+
const unregister = useRightRailStore((s) => s.unregisterPanel);
|
|
79
|
+
const updateBadge = useRightRailStore((s) => s.updateBadge);
|
|
80
|
+
const updateMeta = useRightRailStore((s) => s.updateMeta);
|
|
81
|
+
const id = panel?.id;
|
|
82
|
+
const label = panel?.label;
|
|
83
|
+
const order = panel?.order;
|
|
84
|
+
const Component = panel?.Component;
|
|
85
|
+
const icon = panel?.icon;
|
|
86
|
+
const iconBare = panel?.iconBare;
|
|
87
|
+
const trailing = panel?.trailing;
|
|
88
|
+
const sublabel = panel?.sublabel;
|
|
89
|
+
const badgeCount = panel?.badge?.count;
|
|
90
|
+
const badgeTone = panel?.badge?.tone;
|
|
91
|
+
const initial = React.useRef({ label, icon });
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (!id || !Component) return;
|
|
94
|
+
register({ id, label: initial.current.label ?? id, order, Component, icon: initial.current.icon });
|
|
95
|
+
return () => unregister(id);
|
|
96
|
+
}, [id, order, Component, register, unregister]);
|
|
97
|
+
React.useEffect(() => {
|
|
98
|
+
if (!id) return;
|
|
99
|
+
updateMeta(id, { label, icon, iconBare, trailing, sublabel });
|
|
100
|
+
}, [id, label, icon, iconBare, trailing, sublabel, updateMeta]);
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
if (!id) return;
|
|
103
|
+
updateBadge(id, badgeCount !== void 0 ? { count: badgeCount, tone: badgeTone } : void 0);
|
|
104
|
+
}, [id, badgeCount, badgeTone, updateBadge]);
|
|
105
|
+
}
|
|
106
|
+
var RightRailActionsSlotContext = React.createContext(null);
|
|
107
|
+
var RightRailActionsSlotProvider = RightRailActionsSlotContext.Provider;
|
|
108
|
+
function RightRailActions({ children }) {
|
|
109
|
+
const slot = React.useContext(RightRailActionsSlotContext);
|
|
110
|
+
if (slot) return createPortal(children, slot);
|
|
111
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-0.5 px-2 pt-1.5", children });
|
|
112
|
+
}
|
|
113
|
+
var RightRailLeadingSlotContext = React.createContext(null);
|
|
114
|
+
var RightRailLeadingSlotProvider = RightRailLeadingSlotContext.Provider;
|
|
115
|
+
function RightRailLeading({ children }) {
|
|
116
|
+
const slot = React.useContext(RightRailLeadingSlotContext);
|
|
117
|
+
if (slot) return createPortal(children, slot);
|
|
118
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5 px-2 pt-1.5", children });
|
|
119
|
+
}
|
|
120
|
+
function useRightRailReselect(id, handler) {
|
|
121
|
+
const reselects = useRightRailStore((s) => s.reselects);
|
|
122
|
+
const reselectId = useRightRailStore((s) => s.reselectId);
|
|
123
|
+
const ref = React.useRef(handler);
|
|
124
|
+
ref.current = handler;
|
|
125
|
+
const seen = React.useRef(reselects);
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
if (reselects === seen.current) return;
|
|
128
|
+
seen.current = reselects;
|
|
129
|
+
if (reselectId === id) ref.current();
|
|
130
|
+
}, [reselects, reselectId, id]);
|
|
131
|
+
}
|
|
132
|
+
function useRightRailHosted() {
|
|
133
|
+
return useRightRailStore((s) => s.mounted);
|
|
134
|
+
}
|
|
135
|
+
function useRightRailDrill(drill) {
|
|
136
|
+
useDrillSlot(useRightRailStore((s) => s.setDrill), drill);
|
|
137
|
+
}
|
|
138
|
+
function useRightRailPageDrill(drill) {
|
|
139
|
+
useDrillSlot(useRightRailStore((s) => s.setPageDrill), drill);
|
|
140
|
+
}
|
|
141
|
+
function useDrillSlot(setDrill, drill) {
|
|
142
|
+
const title = drill?.title ?? null;
|
|
143
|
+
const backLabel = drill?.backLabel;
|
|
144
|
+
const keepActions = drill?.keepActions;
|
|
145
|
+
const avatarName = drill?.avatar?.name;
|
|
146
|
+
const avatarSrc = drill?.avatar?.src;
|
|
147
|
+
const avatarFace = drill?.avatar?.Face;
|
|
148
|
+
const backRef = React.useRef(drill?.back);
|
|
149
|
+
backRef.current = drill?.back;
|
|
150
|
+
React.useEffect(() => {
|
|
151
|
+
if (!title) return;
|
|
152
|
+
setDrill({
|
|
153
|
+
title,
|
|
154
|
+
backLabel,
|
|
155
|
+
keepActions,
|
|
156
|
+
...avatarName ? { avatar: { name: avatarName, src: avatarSrc, Face: avatarFace } } : {},
|
|
157
|
+
back: () => backRef.current?.()
|
|
158
|
+
});
|
|
159
|
+
return () => setDrill(null);
|
|
160
|
+
}, [title, backLabel, keepActions, avatarName, avatarSrc, avatarFace, setDrill]);
|
|
161
|
+
}
|
|
162
|
+
function useRightRailMenu(items) {
|
|
163
|
+
const setMenu = useRightRailStore((s) => s.setMenu);
|
|
164
|
+
const ref = React.useRef(items);
|
|
165
|
+
ref.current = items;
|
|
166
|
+
const signature = (items ?? []).map((i) => `${i.id}:${i.label}:${i.active ? 1 : 0}`).join("|");
|
|
167
|
+
React.useEffect(() => {
|
|
168
|
+
if (!signature) return;
|
|
169
|
+
setMenu(
|
|
170
|
+
(ref.current ?? []).map((item) => ({
|
|
171
|
+
...item,
|
|
172
|
+
onSelect: () => ref.current?.find((i) => i.id === item.id)?.onSelect()
|
|
173
|
+
}))
|
|
174
|
+
);
|
|
175
|
+
return () => setMenu([]);
|
|
176
|
+
}, [signature, setMenu]);
|
|
177
|
+
}
|
|
178
|
+
function useHasRightRail() {
|
|
179
|
+
return useRightRailStore((s) => s.panels.length > 0);
|
|
180
|
+
}
|
|
181
|
+
var WIDTH_KEY = "fayz.shell.right-rail.width";
|
|
182
|
+
function readRightRailWidth(fallback = 380) {
|
|
183
|
+
try {
|
|
184
|
+
const stored = Number(localStorage?.getItem(WIDTH_KEY));
|
|
185
|
+
return Number.isFinite(stored) && stored > 0 ? stored : fallback;
|
|
186
|
+
} catch {
|
|
187
|
+
return fallback;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function writeRightRailWidth(width) {
|
|
191
|
+
try {
|
|
192
|
+
localStorage?.setItem(WIDTH_KEY, String(Math.round(width)));
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function getAccessSnapshot() {
|
|
197
|
+
const user = useAuthStore.getState().user;
|
|
198
|
+
const org = useOrganizationStore.getState().currentOrg;
|
|
199
|
+
const profile = usePermissionsStore.getState().currentProfile;
|
|
200
|
+
const plans = useBillingStore.getState().plans;
|
|
201
|
+
const plan = org?.plan ? plans.find((p) => p.id === org.plan) ?? null : null;
|
|
202
|
+
return { user, org, profile, plan };
|
|
203
|
+
}
|
|
204
|
+
function checkAccess(feature, action) {
|
|
205
|
+
const session = getAccessSnapshot();
|
|
206
|
+
const decision = resolveAccess(session, feature, action);
|
|
207
|
+
if (decision.allowed) return { allowed: true };
|
|
208
|
+
return agentDenial(decision.reason ?? "role");
|
|
209
|
+
}
|
|
210
|
+
async function guardLimit(key, n = 1) {
|
|
211
|
+
const session = getAccessSnapshot();
|
|
212
|
+
const tenantId = session.org?.id;
|
|
213
|
+
const cap = session.plan?.entitlements?.limits?.[key];
|
|
214
|
+
const declaration = getLimitDeclaration(key);
|
|
215
|
+
if (!declaration || cap === void 0 || cap === -1 || !tenantId) return { allowed: true };
|
|
216
|
+
const used = await countByTenant(declaration.table, {
|
|
217
|
+
kind: declaration.kindFilter,
|
|
218
|
+
period: declaration.period,
|
|
219
|
+
tenantId,
|
|
220
|
+
fresh: true
|
|
221
|
+
});
|
|
222
|
+
if (used + n > cap) {
|
|
223
|
+
return agentDenial("limit", { key, max: cap, used });
|
|
224
|
+
}
|
|
225
|
+
return { allowed: true };
|
|
226
|
+
}
|
|
227
|
+
function PageActions({ actions, className }) {
|
|
228
|
+
const access = useAccessOptional();
|
|
229
|
+
const openUpgrade = useUpgradeModalStore((s) => s.open);
|
|
230
|
+
const resolved = React__default.useMemo(
|
|
231
|
+
() => actions.map((a) => {
|
|
232
|
+
const { permission, limitKey, onSelect, ...rest } = a;
|
|
233
|
+
let hidden = false;
|
|
234
|
+
if (permission && access) {
|
|
235
|
+
const decision = access.can(permission.feature, permission.action);
|
|
236
|
+
hidden = !decision.allowed && decision.reason === "role";
|
|
237
|
+
}
|
|
238
|
+
const guarded = limitKey ? async () => {
|
|
239
|
+
const verdict = await guardLimit(limitKey);
|
|
240
|
+
if (!verdict.allowed) {
|
|
241
|
+
openUpgrade({ limitKey });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await onSelect();
|
|
245
|
+
} : onSelect;
|
|
246
|
+
return { ...rest, hidden, onSelect: guarded };
|
|
247
|
+
}),
|
|
248
|
+
[actions, access, openUpgrade]
|
|
249
|
+
);
|
|
250
|
+
return /* @__PURE__ */ jsx(PageActionBar, { actions: resolved, className });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export { PageActions, RightRailActions, RightRailActionsSlotProvider, RightRailLeading, RightRailLeadingSlotProvider, checkAccess, getAccessSnapshot, guardLimit, readRightRailWidth, useHasRightRail, useRightRailDrill, useRightRailHosted, useRightRailMenu, useRightRailPageDrill, useRightRailPanel, useRightRailReselect, useRightRailStore, writeRightRailWidth };
|
|
254
|
+
//# sourceMappingURL=chunk-PQ47MLTH.js.map
|
|
255
|
+
//# sourceMappingURL=chunk-PQ47MLTH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/right-rail.tsx","../src/access/headless.ts","../src/plugins/PageActions.tsx"],"names":["React","jsx"],"mappings":"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","file":"chunk-PQ47MLTH.js","sourcesContent":["import * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { create } from 'zustand'\nimport type { AgentFaceProps } from '@fayz-ai/core'\n\n/** The shell's companion column: one open state, one active tab, and a\n * registry anything can put a panel in. */\n\nexport interface RightRailPanelDef {\n id: string\n label: string\n icon?: React.ComponentType<{ className?: string }>\n /** The icon is already a mark of its own (a portrait) — render it bare\n * instead of inside the tinted square a glyph needs. */\n iconBare?: boolean\n /** Glyph after the label, saying the tab does more than select itself — the\n * assistant's chevron, which says the name on it can be changed. */\n trailing?: React.ComponentType<{ className?: string }>\n /** What the panel IS, after what it is called — the colleague's job title\n * beside their name. Shown only where the header has room for it: the\n * identity row, never the tab strip. */\n sublabel?: string\n /** Lower sorts first. Default 100. */\n order?: number\n /** Count chip on the tab. Hidden when count is 0. `destructive` renders the\n * attention red (open tasks); default is the neutral muted chip. */\n badge?: { count: number; tone?: 'neutral' | 'destructive' }\n /** Mounted only while active. Must carry its own providers — the rail\n * renders it far from where it was registered. */\n Component: React.ComponentType\n}\n\n/** An internal page inside the active panel: the header stops being a tab strip\n * and becomes \"← Voltar | <title>\" for as long as one is open. */\nexport interface RightRailDrill {\n title: string\n back: () => void\n /** Names where back goes (\"Voltar ao Chat\"). Defaults to a plain \"Voltar\" —\n * worth setting when the page came from somewhere else in the app and the\n * destination is not obvious from where you are standing. */\n backLabel?: string\n /**\n * Keep the panel's own controls in the header instead of clearing it.\n *\n * Off by default, and that is right for a page from ELSEWHERE: nothing of the\n * chat's applies to a connector's settings. A panel drilling into its own\n * view is the other case — the conversation is still the chat, and taking\n * away \"nova conversa\" while you are in one is taking away the way out.\n */\n keepActions?: boolean\n /**\n * A face to put before the title.\n *\n * The centred title answers \"what am I looking at\"; when the answer is a\n * PERSON, the name alone is the weaker half of it — the portrait is how the\n * colleague is recognised everywhere else in the panel, and dropping it here\n * made the header of a conversation look like the header of a settings page.\n */\n avatar?: { name: string; src?: string; Face?: React.ComponentType<AgentFaceProps> }\n}\n\n/** A control the active panel owns. The rail decides how it is drawn: an icon\n * on the header row, or a line in the header's overflow menu. */\nexport interface RightRailMenuItem {\n id: string\n label: string\n icon?: React.ComponentType<{ className?: string }>\n active?: boolean\n onSelect: () => void\n}\n\ninterface RightRailState {\n /** A shell able to render the rail is mounted. Contributors read this to\n * decide between the rail and their own fallback surface. */\n mounted: boolean\n setMounted: (mounted: boolean) => void\n open: boolean\n /** Panel id currently showing. Meaningless while `open` is false. */\n active: string | null\n panels: RightRailPanelDef[]\n openPanel: (id: string) => void\n close: () => void\n /** Same tab closes, a different tab switches. */\n togglePanel: (id: string) => void\n /**\n * The active tab was clicked again.\n *\n * Selecting what is already selected is normally nothing, but a tab that\n * carries an identity — the assistant's face and name — is also the natural\n * place to change that identity. The rail does not know what that means, so\n * it just counts the click and the panel decides (see useRightRailReselect).\n */\n reselects: number\n reselectId: string | null\n reselect: (id: string) => void\n registerPanel: (panel: RightRailPanelDef) => void\n unregisterPanel: (id: string) => void\n /** Badge-only update. Separate from `registerPanel` on purpose: a badge\n * ticking is NOT a lifecycle event, and routing it through\n * unregister+register took the active panel down with it (see\n * `useRightRailPanel`). */\n updateBadge: (id: string, badge: RightRailPanelDef['badge']) => void\n /** Label/icon change without a lifecycle event — same reasoning as the badge.\n * The chat panel renames itself to the persona the current screen invited\n * (\"Growth Analyst\"), and re-registering for that would close the open rail. */\n updateMeta: (id: string, meta: Partial<Pick<RightRailPanelDef, 'label' | 'icon' | 'iconBare' | 'trailing' | 'sublabel'>>) => void\n /** Distance (px) from the viewport's right edge to the open column's left\n * edge — MEASURED from the rendered card, so clamps and drags are included.\n * The FAB reads it to stay anchored to the CONTENT's bottom-right corner. */\n width: number\n setWidth: (width: number) => void\n /** Set by whatever owns the header while an internal page is open. */\n drill: RightRailDrill | null\n setDrill: (drill: RightRailDrill | null) => void\n /**\n * The header of a page from elsewhere (see RightRailPage), kept apart from\n * the panel's own `drill`.\n *\n * They shared one slot, and the panel behind the page stays MOUNTED: the\n * chat re-registering its conversation's drill (with the mascot's face), or\n * clearing it on cleanup, overwrote the page's header — and the mascot came\n * back over a connector's settings. Two owners, two slots.\n */\n pageDrill: RightRailDrill | null\n setPageDrill: (drill: RightRailDrill | null) => void\n /** The active panel's own controls, as data — see useRightRailMenu. */\n menu: RightRailMenuItem[]\n setMenu: (menu: RightRailMenuItem[]) => void\n\n // -- Pages from elsewhere in the app ------------------------------------\n // A screen's own detail panel (a deal, a connector) rendered IN the column\n // instead of in a sheet of its own. It cannot go through `panels`: that\n // registry mounts a component inside the rail, far from the plugin's\n // providers, and a deal panel without its CRM context is a crash. So the\n // page stays where it was written and portals its body in — which means the\n // target element has to be reachable from anywhere, hence the store rather\n // than a context.\n pageSlot: HTMLElement | null\n setPageSlot: (element: HTMLElement | null) => void\n /** A page is occupying the column; the active panel stays mounted behind it. */\n pageOpen: boolean\n /** Opens the column if it was closed — clicking a deal should show the deal. */\n showPage: () => void\n /**\n * Leaving the page CLOSES the column.\n *\n * It used to only drop the page, which handed the column back to the chat —\n * so saving a booking, or simply navigating to another screen, left the\n * assistant open on top of a screen that never asked for it. The column was\n * opened to show the page; with the page gone it has nothing to show.\n */\n closePage: () => void\n}\n\nexport const useRightRailStore = create<RightRailState>((set) => ({\n mounted: false,\n setMounted: (mounted) => set({ mounted }),\n open: false,\n active: null,\n panels: [],\n\n // Switching or closing drops any internal page: the drill belongs to the\n // panel that opened it, and its title outliving it would head the wrong one.\n openPanel: (id) => set({ open: true, active: id, drill: null, pageOpen: false, pageDrill: null }),\n reselects: 0,\n reselectId: null,\n reselect: (id) => set((s) => ({ reselects: s.reselects + 1, reselectId: id })),\n close: () => set({ open: false, drill: null, pageOpen: false, pageDrill: null }),\n togglePanel: (id) =>\n set((s) =>\n s.open && s.active === id && !s.pageOpen\n ? { open: false, drill: null, pageOpen: false, pageDrill: null }\n : { open: true, active: id, drill: null, pageOpen: false, pageDrill: null },\n ),\n\n registerPanel: (panel) =>\n set((s) => {\n const rest = s.panels.filter((p) => p.id !== panel.id)\n const panels = [...rest, panel].sort((a, b) => (a.order ?? 100) - (b.order ?? 100))\n // First to arrive becomes the default tab.\n return { panels, active: s.active ?? panel.id }\n }),\n\n unregisterPanel: (id) =>\n set((s) => {\n const panels = s.panels.filter((p) => p.id !== id)\n const active = s.active === id ? (panels[0]?.id ?? null) : s.active\n return { panels, active, open: active ? s.open : false }\n }),\n\n updateBadge: (id, badge) =>\n set((s) => {\n const current = s.panels.find((p) => p.id === id)\n if (!current) return s\n if (current.badge?.count === badge?.count && current.badge?.tone === badge?.tone) return s\n return { panels: s.panels.map((p) => (p.id === id ? { ...p, badge } : p)) }\n }),\n\n updateMeta: (id, meta) =>\n set((s) => {\n const current = s.panels.find((p) => p.id === id)\n if (!current) return s\n // Key PRESENCE, not `??`. A caller clearing a field passes it as\n // undefined, and `??` read that as \"not provided\" and kept the old value\n // — which is why the assistant's chevron survived into directed mode,\n // where the whole point was that it should not be offered.\n const pick = <K extends keyof RightRailPanelDef>(key: K): RightRailPanelDef[K] =>\n key in meta ? (meta as RightRailPanelDef)[key] : current[key]\n const next = {\n ...current,\n label: pick('label') ?? current.label,\n icon: pick('icon'),\n iconBare: pick('iconBare'),\n trailing: pick('trailing'),\n sublabel: pick('sublabel'),\n }\n if (\n current.label === next.label && current.icon === next.icon\n && current.iconBare === next.iconBare && current.trailing === next.trailing\n && current.sublabel === next.sublabel\n ) return s\n return { panels: s.panels.map((p) => (p.id === id ? next : p)) }\n }),\n\n width: readRightRailWidth(),\n setWidth: (width) => set({ width }),\n\n drill: null,\n setDrill: (drill) => set({ drill }),\n pageDrill: null,\n setPageDrill: (pageDrill) => set({ pageDrill }),\n\n menu: [],\n setMenu: (menu) => set({ menu }),\n\n pageSlot: null,\n setPageSlot: (pageSlot) => set({ pageSlot }),\n pageOpen: false,\n showPage: () => set({ open: true, pageOpen: true }),\n closePage: () => set({ open: false, pageOpen: false, drill: null, pageDrill: null }),\n}))\n\n/** Register a panel while the caller is mounted. `Component` must be\n * referentially stable or the panel remounts on every parent render. */\nexport function useRightRailPanel(panel: RightRailPanelDef | null): void {\n const register = useRightRailStore((s) => s.registerPanel)\n const unregister = useRightRailStore((s) => s.unregisterPanel)\n const updateBadge = useRightRailStore((s) => s.updateBadge)\n const updateMeta = useRightRailStore((s) => s.updateMeta)\n const id = panel?.id\n const label = panel?.label\n const order = panel?.order\n const Component = panel?.Component\n const icon = panel?.icon\n const iconBare = panel?.iconBare\n const trailing = panel?.trailing\n const sublabel = panel?.sublabel\n const badgeCount = panel?.badge?.count\n const badgeTone = panel?.badge?.tone\n\n // Lifecycle ONLY. The badge and the label/icon deliberately stay out of these\n // deps: they change while the panel is open and in use (the chat's unread\n // count ticks on every streamed turn; its title follows the screen's invited\n // persona), and the cleanup here unregisters — which moves `active` to the\n // next panel, or closes the rail outright when this was the only one. The chat\n // was flipping to the Tasks tab, or vanishing mid-answer, for exactly that\n // reason. Both ride their own updater below.\n const initial = React.useRef({ label, icon })\n React.useEffect(() => {\n if (!id || !Component) return\n register({ id, label: initial.current.label ?? id, order, Component, icon: initial.current.icon })\n return () => unregister(id)\n }, [id, order, Component, register, unregister])\n\n React.useEffect(() => {\n if (!id) return\n updateMeta(id, { label, icon, iconBare, trailing, sublabel })\n }, [id, label, icon, iconBare, trailing, sublabel, updateMeta])\n\n React.useEffect(() => {\n if (!id) return\n updateBadge(id, badgeCount !== undefined ? { count: badgeCount, tone: badgeTone } : undefined)\n }, [id, badgeCount, badgeTone, updateBadge])\n}\n\n// ---------------------------------------------------------------------------\n// Panel-owned header actions.\n//\n// The panel's own controls (new conversation, tools, connectors) used to sit in\n// a strip the panel drew for itself, directly under the rail's header — a\n// second bar of chrome under the first one, in a 380px column. They belong in\n// the header, on the row that already carries the panel's name and its close\n// button, which is where every reference puts them.\n//\n// Same shape as the shell's PageHeaderActions: a portal when the rail is\n// hosting, an inline row when the panel is mounted anywhere else.\n// ---------------------------------------------------------------------------\n\nconst RightRailActionsSlotContext = React.createContext<HTMLElement | null>(null)\n\nexport const RightRailActionsSlotProvider = RightRailActionsSlotContext.Provider\n\nexport function RightRailActions({ children }: { children: React.ReactNode }) {\n const slot = React.useContext(RightRailActionsSlotContext)\n if (slot) return createPortal(children, slot)\n return <div className=\"flex items-center justify-end gap-0.5 px-2 pt-1.5\">{children}</div>\n}\n\n// ---------------------------------------------------------------------------\n// The mirror of the actions slot, at the other end of the header row.\n//\n// Actions are things you DO to the panel; this is who the panel IS — the\n// assistant's face, before its name. It has to be the header rather than a\n// strip inside the panel for the same reason the actions are: a second row of\n// chrome under the first one, carrying one avatar, is chrome for its own sake.\n// ---------------------------------------------------------------------------\n\nconst RightRailLeadingSlotContext = React.createContext<HTMLElement | null>(null)\n\nexport const RightRailLeadingSlotProvider = RightRailLeadingSlotContext.Provider\n\nexport function RightRailLeading({ children }: { children: React.ReactNode }) {\n const slot = React.useContext(RightRailLeadingSlotContext)\n if (slot) return createPortal(children, slot)\n return <div className=\"flex items-center gap-0.5 px-2 pt-1.5\">{children}</div>\n}\n\n/** Run `handler` when the user clicks the tab of a panel that is already open.\n * Counts, not booleans: two clicks in a row are two events. */\nexport function useRightRailReselect(id: string, handler: () => void): void {\n const reselects = useRightRailStore((s) => s.reselects)\n const reselectId = useRightRailStore((s) => s.reselectId)\n const ref = React.useRef(handler)\n ref.current = handler\n const seen = React.useRef(reselects)\n React.useEffect(() => {\n if (reselects === seen.current) return\n seen.current = reselects\n if (reselectId === id) ref.current()\n }, [reselects, reselectId, id])\n}\n\n/** A shell able to host the rail is mounted, so a panel's chrome (its header,\n * its back link) belongs to the rail rather than to the panel itself. */\nexport function useRightRailHosted(): boolean {\n return useRightRailStore((s) => s.mounted)\n}\n\n/**\n * Give the rail's header to an internal page: \"← Voltar | <title>\" replaces the\n * tab strip until the page closes.\n *\n * Pass null when there is no internal page open. `back` is read through a ref,\n * so a caller does not have to memoise it to avoid re-registering.\n */\nexport function useRightRailDrill(drill: RightRailDrill | null): void {\n useDrillSlot(useRightRailStore((s) => s.setDrill), drill)\n}\n\n/** The same, for a page from elsewhere — see `pageDrill`. */\nexport function useRightRailPageDrill(drill: RightRailDrill | null): void {\n useDrillSlot(useRightRailStore((s) => s.setPageDrill), drill)\n}\n\nfunction useDrillSlot(\n setDrill: (drill: RightRailDrill | null) => void,\n drill: RightRailDrill | null,\n): void {\n const title = drill?.title ?? null\n const backLabel = drill?.backLabel\n const keepActions = drill?.keepActions\n const avatarName = drill?.avatar?.name\n const avatarSrc = drill?.avatar?.src\n const avatarFace = drill?.avatar?.Face\n const backRef = React.useRef(drill?.back)\n backRef.current = drill?.back\n\n React.useEffect(() => {\n if (!title) return\n setDrill({\n title,\n backLabel,\n keepActions,\n ...(avatarName ? { avatar: { name: avatarName, src: avatarSrc, Face: avatarFace } } : {}),\n back: () => backRef.current?.(),\n })\n return () => setDrill(null)\n }, [title, backLabel, keepActions, avatarName, avatarSrc, avatarFace, setDrill])\n}\n\n/**\n * Hand the panel's controls to the rail's header as data.\n *\n * `RightRailActions` portals finished buttons, which only works for a header\n * that shows them as buttons. A header that folds them into a menu needs to\n * know what they ARE. Handlers are read through a ref, so the list re-registers\n * only when what is on screen changes.\n */\nexport function useRightRailMenu(items: RightRailMenuItem[] | null): void {\n const setMenu = useRightRailStore((s) => s.setMenu)\n const ref = React.useRef(items)\n ref.current = items\n const signature = (items ?? []).map((i) => `${i.id}:${i.label}:${i.active ? 1 : 0}`).join('|')\n React.useEffect(() => {\n if (!signature) return\n setMenu(\n (ref.current ?? []).map((item) => ({\n ...item,\n onSelect: () => ref.current?.find((i) => i.id === item.id)?.onSelect(),\n })),\n )\n return () => setMenu([])\n }, [signature, setMenu])\n}\n\n/** No panels registered means no column and no divider. */\nexport function useHasRightRail(): boolean {\n return useRightRailStore((s) => s.panels.length > 0)\n}\n\nconst WIDTH_KEY = 'fayz.shell.right-rail.width'\n\n/** The user's dragged width, remembered per browser. */\nexport function readRightRailWidth(fallback = 380): number {\n try {\n const stored = Number(localStorage?.getItem(WIDTH_KEY))\n return Number.isFinite(stored) && stored > 0 ? stored : fallback\n } catch {\n return fallback\n }\n}\n\nexport function writeRightRailWidth(width: number): void {\n try {\n localStorage?.setItem(WIDTH_KEY, String(Math.round(width)))\n } catch {\n /* preference stays session-only */\n }\n}\n","import type { PermissionAction, Plan } from '@fayz-ai/core'\nimport { countByTenant } from '@fayz-ai/core'\nimport { resolveAccess, agentDenial, type AgentGuardResult } from '@fayz-ai/core/access'\nimport { useAuthStore } from '@fayz-ai/auth'\nimport { useOrganizationStore } from '../org/store'\nimport { usePermissionsStore } from '../permissions/store'\nimport { useBillingStore } from '../billing/store'\nimport { getLimitDeclaration } from './limits-registry'\nimport type { AccessSession } from './types'\n\n// ---------------------------------------------------------------------------\n// Headless access — the imperative counterpart of useAccess()/useLimitGuard()\n// for runtimes without React: the agent's client-plane tool executor, command\n// handlers, background sync. Reads the SAME singleton stores the providers\n// hydrate, and decides with the SAME engine (@fayz-ai/core/access), so a\n// decision here never diverges from what the UI shows.\n//\n// Denials come back as the structured AgentDenial (never a silent failure) so\n// an agent can explain the denial and offer the upgrade conversationally.\n// ---------------------------------------------------------------------------\n\n/**\n * Snapshot of the live session assembled from the singleton stores — the same\n * assembly AccessProvider does reactively. Values are as-hydrated: before\n * AuthProvider/OrgProvider finish, fields are null, and an unresolved profile\n * DENIES — same semantics as the providers.\n */\nexport function getAccessSnapshot(): AccessSession {\n const user = useAuthStore.getState().user\n const org = useOrganizationStore.getState().currentOrg\n const profile = usePermissionsStore.getState().currentProfile\n const plans = useBillingStore.getState().plans\n const plan: Plan | null = org?.plan ? plans.find((p) => p.id === org.plan) ?? null : null\n return { user, org, profile, plan }\n}\n\n/** Imperative role × plan check over the live snapshot. */\nexport function checkAccess(feature: string, action?: PermissionAction): AgentGuardResult {\n const session = getAccessSnapshot()\n const decision = resolveAccess(session, feature, action)\n if (decision.allowed) return { allowed: true }\n return agentDenial(decision.reason ?? 'role')\n}\n\n/**\n * Imperative plan-limit guard: would adding `n` rows exceed the cap for\n * `key`? Counts FRESH (never trusts stale hook state), mirrors useLimitGuard.\n * No declaration / no cap / `-1` / no tenant ⇒ allowed. Does NOT open the\n * UpgradeModal — the caller decides how to surface the denial (an agent turns\n * it into a conversational upgrade prompt).\n */\nexport async function guardLimit(key: string, n = 1): Promise<AgentGuardResult> {\n const session = getAccessSnapshot()\n const tenantId = session.org?.id\n const cap = session.plan?.entitlements?.limits?.[key]\n const declaration = getLimitDeclaration(key)\n\n if (!declaration || cap === undefined || cap === -1 || !tenantId) return { allowed: true }\n\n const used = await countByTenant(declaration.table, {\n kind: declaration.kindFilter,\n period: declaration.period,\n tenantId,\n fresh: true,\n })\n\n if (used + n > cap) {\n return agentDenial('limit', { key, max: cap, used })\n }\n return { allowed: true }\n}\n","import React from 'react'\nimport { PageActionBar, type PageActionItem } from '@fayz-ai/ui'\nimport type { PermissionAction } from '@fayz-ai/core'\n\nimport { useAccessOptional } from '../access/context'\nimport { guardLimit } from '../access/headless'\nimport { useUpgradeModalStore } from '../access/upgrade-modal-store'\n\n// ---------------------------------------------------------------------------\n// The access-aware half of PageActions.\n//\n// `@fayz-ai/ui` has no access engine and should not grow one, so it renders\n// whatever it is handed. This resolves the two gates the shell already applies\n// everywhere else, and hands down plain booleans:\n//\n// role denial → the action is GONE. Same rule the navigation uses: something\n// you can never do should not advertise itself.\n// plan cap → the action STAYS and opens the upgrade path when tapped. A\n// limit is a \"not yet\", and hiding it hides the upsell.\n//\n// Screens stop wrapping each button in their own PermissionGate/LimitGate pair,\n// which is where the gating drifted: the same button is gated in three\n// different ways depending on which file drew it.\n// ---------------------------------------------------------------------------\n\nexport interface PageAction extends Omit<PageActionItem, 'hidden'> {\n permission?: { feature: string; action: PermissionAction }\n /** Plan quantity cap, e.g. an entity's `limitKey`. */\n limitKey?: string\n}\n\nexport function PageActions({ actions, className }: { actions: PageAction[]; className?: string }) {\n const access = useAccessOptional()\n const openUpgrade = useUpgradeModalStore((s) => s.open)\n\n const resolved = React.useMemo<PageActionItem[]>(\n () =>\n actions.map((a) => {\n const { permission, limitKey, onSelect, ...rest } = a\n\n let hidden = false\n if (permission && access) {\n const decision = access.can(permission.feature, permission.action)\n // Plan denial keeps the action — the upgrade prompt is the point.\n hidden = !decision.allowed && decision.reason === 'role'\n }\n\n const guarded = limitKey\n ? async () => {\n const verdict = await guardLimit(limitKey)\n if (!verdict.allowed) { openUpgrade({ limitKey }); return }\n await onSelect()\n }\n : onSelect\n\n return { ...rest, hidden, onSelect: guarded }\n }),\n [actions, access, openUpgrade],\n )\n\n return <PageActionBar actions={resolved} className={className} />\n}\n"]}
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { appRouter, appHref, currentAppPath, onAppPathChange } from '@fayz-ai/core';
|
|
3
3
|
|
|
4
4
|
// src/app/routing.tsx
|
|
5
|
-
var adapter = hashRouterAdapter();
|
|
6
5
|
function normalizeAdminPath(path) {
|
|
7
6
|
const clean = `/${path}`.replace(/\/+/g, "/");
|
|
8
7
|
return clean.length > 1 ? clean.replace(/\/+$/, "") : clean;
|
|
9
8
|
}
|
|
10
9
|
function navigateTo(to) {
|
|
11
|
-
|
|
10
|
+
appRouter().navigate(normalizeAdminPath(to));
|
|
11
|
+
}
|
|
12
|
+
function replaceTo(to) {
|
|
13
|
+
appRouter().replace(normalizeAdminPath(to));
|
|
14
|
+
}
|
|
15
|
+
function adminHref(to) {
|
|
16
|
+
return appHref(normalizeAdminPath(to));
|
|
17
|
+
}
|
|
18
|
+
function currentAdminPath() {
|
|
19
|
+
return normalizeAdminPath(currentAppPath());
|
|
12
20
|
}
|
|
13
21
|
function useAdminPath() {
|
|
14
|
-
const [path, setPath] = React.useState(
|
|
22
|
+
const [path, setPath] = React.useState(currentAdminPath);
|
|
15
23
|
React.useEffect(() => {
|
|
16
|
-
|
|
24
|
+
setPath(currentAdminPath());
|
|
25
|
+
return onAppPathChange((p) => {
|
|
17
26
|
setPath(normalizeAdminPath(p || "/"));
|
|
18
27
|
if (typeof window !== "undefined") window.scrollTo(0, 0);
|
|
19
28
|
});
|
|
@@ -49,6 +58,6 @@ function routeScore(pattern) {
|
|
|
49
58
|
return score;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
export { matchRoute, navigateTo, normalizeAdminPath, routeScore, useAdminPath };
|
|
53
|
-
//# sourceMappingURL=chunk-
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
61
|
+
export { adminHref, currentAdminPath, matchRoute, navigateTo, normalizeAdminPath, replaceTo, routeScore, useAdminPath };
|
|
62
|
+
//# sourceMappingURL=chunk-QD7BMUDF.js.map
|
|
63
|
+
//# sourceMappingURL=chunk-QD7BMUDF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/app/routing.tsx"],"names":[],"mappings":"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","file":"chunk-QD7BMUDF.js","sourcesContent":["import * as React from 'react'\nimport { appHref, appRouter, currentAppPath, onAppPathChange } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Admin router — the same lightweight approach the storefront uses. The admin\n// shell collects routes from the plugin runtime + manifest pages and switches\n// on the current path. Deliberately not react-router: an SDK shell shipping a\n// router creates singleton hazards under source-alias dev.\n//\n// The addressing scheme is core's app router (`configureAppRouter`): fragment\n// addresses (`/#/agenda`) by default, real ones (`/agenda`) when the app asks\n// for `routing: 'history'`. Nothing here reads `window.location` itself.\n// ---------------------------------------------------------------------------\n\nexport function normalizeAdminPath(path: string): string {\n const clean = `/${path}`.replace(/\\/+/g, '/')\n return clean.length > 1 ? clean.replace(/\\/+$/, '') : clean\n}\n\nexport function navigateTo(to: string): void {\n appRouter().navigate(normalizeAdminPath(to))\n}\n\n/** Change the address without a new history entry (a tab, a filter). */\nexport function replaceTo(to: string): void {\n appRouter().replace(normalizeAdminPath(to))\n}\n\n/** The `href` an anchor to an admin path carries, in the app's scheme. */\nexport function adminHref(to: string): string {\n return appHref(normalizeAdminPath(to))\n}\n\n/** The current admin path, query included, outside React. */\nexport function currentAdminPath(): string {\n return normalizeAdminPath(currentAppPath())\n}\n\nexport function useAdminPath(): string {\n const [path, setPath] = React.useState(currentAdminPath)\n React.useEffect(() => {\n setPath(currentAdminPath())\n return onAppPathChange((p) => {\n setPath(normalizeAdminPath(p || '/'))\n if (typeof window !== 'undefined') window.scrollTo(0, 0)\n })\n }, [])\n return path\n}\n\n/** Match '/courses/:id' against '/courses/abc' → { id: 'abc' }; supports a\n * trailing '/*' wildcard (matches the base and any deeper path). */\nexport function matchRoute(pattern: string, path: string): Record<string, string> | null {\n const cleanPath = (path.split('?')[0] ?? '').replace(/\\/+$/, '') || '/'\n if (pattern.endsWith('/*')) {\n const base = pattern.slice(0, -2)\n if (cleanPath === base || cleanPath.startsWith(`${base}/`)) return {}\n return null\n }\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = cleanPath.split('/').filter(Boolean)\n if (patternParts.length !== pathParts.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pat = patternParts[i]!\n const part = pathParts[i]!\n if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)\n else if (pat !== part) return null\n }\n return params\n}\n\n/** Score a route pattern for specificity so the most specific match wins\n * (static segments beat params; longer beats shorter; wildcard loses). */\nexport function routeScore(pattern: string): number {\n const isWildcard = pattern.endsWith('/*')\n const routePattern = isWildcard ? pattern.slice(0, -2) : pattern\n const parts = routePattern.split('/').filter(Boolean)\n let score = parts.length * 10\n for (const p of parts) if (!p.startsWith(':')) score += 5\n if (isWildcard) score -= 1\n return score\n}\n"]}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React, { useMemo, useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
-
import { Card, CardContent, Button, Badge } from '@fayz-ai/ui';
|
|
3
|
-
import { getActiveTenantId } from '@fayz-ai/core';
|
|
4
|
-
import
|
|
5
|
-
import { ChevronRight } from 'lucide-react';
|
|
2
|
+
import { Card, CardContent, Button, Badge, getLucideIcon } from '@fayz-ai/ui';
|
|
3
|
+
import { getActiveTenantId, appHref } from '@fayz-ai/core';
|
|
4
|
+
import { ChevronRight, CircleDashed } from 'lucide-react';
|
|
6
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
6
|
|
|
8
7
|
// src/timeline/PersonTimeline.tsx
|
|
@@ -105,9 +104,7 @@ var TONE_CLASSES = {
|
|
|
105
104
|
rose: "bg-destructive-soft text-destructive-soft-foreground"
|
|
106
105
|
};
|
|
107
106
|
function resolveIcon(name) {
|
|
108
|
-
|
|
109
|
-
const icon = name ? icons[name] : void 0;
|
|
110
|
-
return (typeof icon === "function" || typeof icon === "object" ? icon : void 0) ?? LucideIcons.CircleDashed;
|
|
107
|
+
return (name ? getLucideIcon(name) : void 0) ?? CircleDashed;
|
|
111
108
|
}
|
|
112
109
|
function formatWhen(value) {
|
|
113
110
|
try {
|
|
@@ -234,7 +231,7 @@ function PersonTimeline({
|
|
|
234
231
|
(item) => {
|
|
235
232
|
if (item.href) return item.href;
|
|
236
233
|
if (!recordPath || !item.targetTab) return void 0;
|
|
237
|
-
return
|
|
234
|
+
return appHref(`${recordPath}/${item.targetTab}`);
|
|
238
235
|
},
|
|
239
236
|
[recordPath]
|
|
240
237
|
);
|
|
@@ -272,5 +269,5 @@ function PersonTimeline({
|
|
|
272
269
|
}
|
|
273
270
|
|
|
274
271
|
export { PersonTimeline, TimelineList, TimelineSkeleton, getTimelineSources, registerTimelineSource, unregisterTimelineSource, usePersonTimeline };
|
|
275
|
-
//# sourceMappingURL=chunk-
|
|
276
|
-
//# sourceMappingURL=chunk-
|
|
272
|
+
//# sourceMappingURL=chunk-QTSKCPSW.js.map
|
|
273
|
+
//# sourceMappingURL=chunk-QTSKCPSW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/timeline/registry.ts","../src/timeline/usePersonTimeline.ts","../src/timeline/TimelineList.tsx","../src/timeline/PersonTimeline.tsx"],"names":["sources","useState","jsx","jsxs","Card","CardContent"],"mappings":"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","file":"chunk-QTSKCPSW.js","sourcesContent":["import type { TimelineSource } from './types'\n\n/**\n * Quem contribui para a linha do tempo de uma pessoa.\n *\n * Registro de módulo, e não contexto de React: os plugins registram no momento\n * em que a fábrica deles roda (`createScribePlugin(...)` dentro do\n * `defineSaas`), que é antes de qualquer árvore montar. Um contexto obrigaria\n * cada app a lembrar de embrulhar a aba num provider — e \"funciona se você\n * lembrou\" é o oposto do que este registro existe para dar.\n */\nconst sources = new Map<string, TimelineSource>()\n\nexport function registerTimelineSource(source: TimelineSource): void {\n // Idempotente: em dev o módulo do plugin recarrega com o HMR, e uma segunda\n // fonte com o mesmo id duplicaria cada evento na tela.\n sources.set(source.id, source)\n}\n\nexport function unregisterTimelineSource(id: string): void {\n sources.delete(id)\n}\n\nexport function getTimelineSources(): TimelineSource[] {\n return Array.from(sources.values())\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { getActiveTenantId } from '@fayz-ai/core'\nimport { getTimelineSources } from './registry'\nimport type { TimelineItem, TimelineSource } from './types'\n\nconst DEFAULT_PAGE_SIZE = 20\n\nfunction newestFirst(a: TimelineItem, b: TimelineItem): number {\n return new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime()\n}\n\nexport interface UsePersonTimelineResult {\n items: TimelineItem[]\n sources: TimelineSource[]\n loading: boolean\n loadingMore: boolean\n hasMore: boolean\n loadMore: () => void\n reload: () => void\n}\n\n/**\n * Intercala as fontes registradas numa lista só, em ordem.\n *\n * O ponto todo é a paginação. A versão ingênua pede N de cada fonte e corta o\n * resultado — e aí a fonte volumosa esconde a outra: com 30 agendamentos e um\n * pagamento de hoje, o pagamento não aparece, porque o corte veio depois de os\n * agendamentos já terem enchido a lista. Aqui cada página pergunta a TODAS as\n * fontes \"o que você tem antes deste instante\", junta, ordena e corta; a\n * próxima página repete a pergunta a partir da data do último item mostrado.\n * Assim nenhuma fonte esconde a outra, por mais desbalanceadas que sejam.\n */\nexport function usePersonTimeline({\n personId,\n pageSize = DEFAULT_PAGE_SIZE,\n activeSourceIds,\n}: {\n personId: string\n pageSize?: number\n /** Filtro por tipo. Ausente = todas. */\n activeSourceIds?: string[]\n}): UsePersonTimelineResult {\n const allSources = useMemo(() => getTimelineSources(), [])\n const sources = useMemo(\n () => (activeSourceIds ? allSources.filter((s) => activeSourceIds.includes(s.id)) : allSources),\n [allSources, activeSourceIds],\n )\n\n const [items, setItems] = useState<TimelineItem[]>([])\n const [loading, setLoading] = useState(true)\n const [loadingMore, setLoadingMore] = useState(false)\n const [hasMore, setHasMore] = useState(false)\n\n // Cortar por data faz o mesmo evento poder voltar na página seguinte (dois\n // eventos no mesmo instante, relógios diferentes). O id é o que decide.\n const seen = useRef<Set<string>>(new Set())\n // Muda a cada recarga: uma resposta de uma busca abandonada não pode\n // sobrescrever a lista da busca atual.\n const runId = useRef(0)\n\n const tenantId = getActiveTenantId() ?? ''\n\n const fetchPage = useCallback(\n async (before: string | undefined, run: number): Promise<{ page: TimelineItem[]; more: boolean }> => {\n // `limit + 1` para saber se ainda tem, sem uma segunda ida ao banco.\n const wanted = pageSize + 1\n const results = await Promise.all(\n sources.map((source) =>\n source\n .fetch({ personId, tenantId, before, limit: wanted })\n // Uma fonte que falha vira lista vazia: a linha do tempo inteira não\n // pode sumir porque um plugin quebrou.\n .catch((err) => {\n console.warn(`[timeline] source \"${source.id}\" failed`, err)\n return [] as TimelineItem[]\n }),\n ),\n )\n\n if (run !== runId.current) return { page: [], more: false }\n\n const merged = results\n .flat()\n .filter((item) => item.occurredAt && !seen.current.has(item.id))\n .sort(newestFirst)\n\n const page = merged.slice(0, pageSize)\n for (const item of page) seen.current.add(item.id)\n\n // Ainda tem se sobrou no bolo já lido, OU se alguma fonte devolveu o\n // limite cheio (sinal de que ela tem mais atrás).\n const more = merged.length > page.length || results.some((r) => r.length >= wanted)\n return { page, more }\n },\n [sources, personId, tenantId, pageSize],\n )\n\n const reload = useCallback(() => {\n const run = ++runId.current\n seen.current = new Set()\n setLoading(true)\n void fetchPage(undefined, run).then(({ page, more }) => {\n if (run !== runId.current) return\n setItems(page)\n setHasMore(more)\n setLoading(false)\n })\n }, [fetchPage])\n\n useEffect(() => {\n if (!personId || !tenantId || sources.length === 0) {\n setItems([])\n setHasMore(false)\n setLoading(false)\n return\n }\n reload()\n }, [personId, tenantId, sources, reload])\n\n const loadMore = useCallback(() => {\n const last = items[items.length - 1]\n if (!last || loadingMore) return\n const run = runId.current\n setLoadingMore(true)\n void fetchPage(last.occurredAt, run).then(({ page, more }) => {\n if (run !== runId.current) return\n setItems((prev) => [...prev, ...page])\n setHasMore(more && page.length > 0)\n setLoadingMore(false)\n })\n }, [items, loadingMore, fetchPage])\n\n return { items, sources: allSources, loading, loadingMore, hasMore, loadMore, reload }\n}\n","import React from 'react'\nimport { CircleDashed } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { ChevronRight } from 'lucide-react'\nimport { Badge, Card, CardContent, getLucideIcon } from '@fayz-ai/ui'\nimport type { TimelineItem, TimelineTone } from './types'\n\n// ---------------------------------------------------------------------------\n// O DESENHO da linha do tempo, sem de onde os itens vieram.\n//\n// Separado da `PersonTimeline` porque o desenho passou a ter DOIS consumidores:\n// a aba Atividade (que intercala sete fontes) e a aba Registros (que lista as\n// respostas de formulário da própria pessoa). Uma ficha clínica que mostrasse\n// duas listas com formas diferentes para a mesma pergunta — \"o que aconteceu,\n// em ordem\" — obrigaria a reaprender a leitura ao trocar de aba.\n//\n// Aqui não há consulta, paginação nem filtro: quem monta a lista é quem sabe de\n// onde ela vem.\n// ---------------------------------------------------------------------------\n\n// Tom → tokens do tema. Um lugar só: é isto que faz uma fonte nova herdar\n// claro/escuro e a paleta do app sem escrever uma cor.\nconst TONE_CLASSES: Record<TimelineTone, string> = {\n neutral: 'bg-muted text-muted-foreground',\n info: 'bg-info-soft text-info-soft-foreground',\n success: 'bg-success-soft text-success-soft-foreground',\n warning: 'bg-warning-soft text-warning-soft-foreground',\n magic: 'bg-magic-soft text-magic-soft-foreground',\n primary: 'bg-primary/10 text-primary',\n rose: 'bg-destructive-soft text-destructive-soft-foreground',\n}\n\n// Resolved through the SDK's icon registry (@fayz-ai/ui `getLucideIcon`), not\n// a namespace import: that pulled all of lucide (~500 KB) into every app.\n// An app needing a name outside the set adds it with `registerIcons`.\nfunction resolveIcon(name?: string): LucideIcon {\n return (name ? getLucideIcon(name) : undefined) ?? CircleDashed\n}\n\nfunction formatWhen(value: string): string {\n try {\n return new Intl.DateTimeFormat(undefined, {\n day: '2-digit',\n month: 'short',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n }).format(new Date(value))\n } catch {\n return value\n }\n}\n\n/**\n * O trilho + a bolha, numa coluna fixa à esquerda. Mesma grade no esqueleto e\n * no item real, para a chegada do conteúdo ser troca de cinza por texto e não a\n * lista mudando de forma.\n *\n * Flex com largura fixa, e não `grid-cols-[…]`: uma utility arbitrária que só\n * existe neste arquivo não chega a ser gerada no CSS do app, e o sintoma não é\n * erro nenhum — é a linha do tempo empilhada, bolha centrada e cartão embaixo.\n * O trilho vira segmento de flex pela mesma razão: nada de posição absoluta\n * dependendo de uma coluna que pode não existir.\n */\nfunction Rail({ first, last, children }: { first: boolean; last: boolean; children: React.ReactNode }) {\n return (\n <div className=\"flex w-11 shrink-0 flex-col items-center\">\n {/* Invisível no primeiro: o espaço continua, para todas as bolhas\n começarem na mesma altura do cartão ao lado. */}\n <span className={`h-4 w-px ${first ? '' : 'bg-border'}`} aria-hidden />\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full border border-border bg-background shadow-sm\">\n {children}\n </div>\n {!last && <span className=\"w-px flex-1 bg-border\" aria-hidden />}\n </div>\n )\n}\n\nfunction TimelineSkeleton() {\n return (\n <ol className=\"space-y-0\" aria-hidden>\n {[0, 1, 2].map((row, index) => (\n <li key={row} className=\"flex gap-4\">\n <Rail first={index === 0} last={index === 2}>\n <div className=\"h-7 w-7 animate-pulse rounded-full bg-muted\" />\n </Rail>\n <Card className={`min-w-0 flex-1 ${index === 2 ? 'mb-0' : 'mb-4'}`}>\n <CardContent className=\"p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1 space-y-2\">\n <div className=\"h-5 w-20 animate-pulse rounded-full bg-muted\" />\n {/* Larguras diferentes por linha: três barras idênticas leem\n como padrão gráfico, não como texto carregando. */}\n <div className={`h-4 animate-pulse rounded bg-muted ${index === 1 ? 'w-1/2' : 'w-2/3'}`} />\n </div>\n <div className=\"h-3 w-24 shrink-0 animate-pulse rounded bg-muted\" />\n </div>\n </CardContent>\n </Card>\n </li>\n ))}\n </ol>\n )\n}\n\nfunction TimelineRow({\n item,\n tone,\n href,\n onSelect,\n actions,\n preview,\n first,\n last,\n}: {\n item: TimelineItem\n tone: TimelineTone\n href?: string\n onSelect?: (item: TimelineItem) => void\n actions?: React.ReactNode\n /** O conteúdo do registro, aberto DENTRO do cartão. Ver `renderPreview`. */\n preview?: React.ReactNode\n first: boolean\n last: boolean\n}) {\n const clickable = Boolean(href) || Boolean(onSelect)\n const Icon = resolveIcon(item.icon)\n const card = (\n <Card\n className={`group ${last ? 'mb-0' : 'mb-4'} ${\n // O cursor e a sombra só existem quando há para onde ir: é o que\n // distingue, ANTES do clique, o que é atalho do que é só registro.\n clickable ? 'cursor-pointer transition-shadow hover:shadow-md' : ''\n }`}\n {...(!href && onSelect ? { onClick: () => onSelect(item) } : {})}\n >\n <div className=\"flex items-stretch\">\n <CardContent className=\"min-w-0 flex-1 p-4\">\n <div className=\"flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n {item.badge && <Badge variant=\"secondary\">{item.badge}</Badge>}\n <h3 className={`truncate text-sm font-semibold text-foreground ${item.badge ? 'mt-2' : ''}`}>\n {item.title}\n </h3>\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n <time className=\"text-xs font-medium text-muted-foreground\">{formatWhen(item.occurredAt)}</time>\n {/* Os verbos da linha aparecem no hover, como no resto do SDK: uma\n fileira de ícones sempre visível transforma a lista num painel\n de botões e rouba a leitura do que aconteceu. */}\n {actions && (\n <div className=\"flex gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n {actions}\n </div>\n )}\n </div>\n </div>\n {item.description && (\n <p className=\"mt-2 whitespace-pre-wrap text-sm leading-6 text-muted-foreground\">{item.description}</p>\n )}\n {/* O conteúdo, aberto no lugar. Separado por uma régua porque sem ela\n o corpo do formulário encosta no título e os dois viram um bloco\n só de texto. */}\n {preview && (\n <div\n className=\"mt-3 border-t border-border pt-3\"\n // O cartão inteiro é clicável; o conteúdo dentro dele não pode\n // abrir a tela cheia ao encostar numa palavra — nem impedir que se\n // selecione um trecho para copiar.\n onClick={(e) => e.stopPropagation()}\n >\n {preview}\n </div>\n )}\n </CardContent>\n\n {/* A SETA que promete o clique.\n Estava em cima, na fileira de ações, como mais um ícone entre outros —\n e um botão \"abrir\" ao lado de um \"ocultar\" faz o leitor escolher entre\n dois verbos quando o gesto natural é clicar no cartão. À direita e no\n meio da altura, ela diz o que o cartão inteiro faz, em vez de\n concorrer com ele. O espaço fica reservado sempre: aparecer no hover\n empurrando o texto seria pior do que não aparecer. */}\n {clickable && (\n <div className=\"flex shrink-0 items-center pr-4 opacity-0 transition-opacity group-hover:opacity-100\">\n <ChevronRight className=\"h-6 w-6 text-muted-foreground\" aria-hidden />\n </div>\n )}\n </div>\n </Card>\n )\n\n return (\n <li className=\"flex gap-4\">\n <Rail first={first} last={last}>\n <div className={`flex h-7 w-7 items-center justify-center rounded-full ${TONE_CLASSES[tone]}`}>\n <Icon className=\"h-4 w-4\" />\n </div>\n </Rail>\n <div className=\"min-w-0 flex-1\">\n {/* `<a>` de verdade, e não um onClick: o clique do meio abre em outra\n aba, o hover mostra o destino na barra de status, e o teclado alcança\n sem nada além do HTML. */}\n {href ? <a href={href} className=\"block no-underline\">{card}</a> : card}\n </div>\n </li>\n )\n}\n\n\n/**\n * A lista: trilho, bolha e cartão, do jeito que a ficha inteira mostra\n * histórico.\n *\n * `renderActions` é a única costura que a aba Registros precisou: ver e\n * arquivar um documento são verbos DAQUELA aba, e a Atividade — que é leitura —\n * não tem nenhum.\n */\nexport function TimelineList({\n items,\n toneFor,\n hrefFor,\n onSelect,\n renderActions,\n renderPreview,\n expandedIds,\n}: {\n items: TimelineItem[]\n /** Tom da bolha quando o item não declara o seu. */\n toneFor?: (item: TimelineItem) => TimelineTone\n hrefFor?: (item: TimelineItem) => string | undefined\n /** Clique no cartão quando ele não é um link — o caso da aba Registros, que\n * abre o documento na própria aba em vez de navegar. */\n onSelect?: (item: TimelineItem) => void\n renderActions?: (item: TimelineItem) => React.ReactNode\n /**\n * O conteúdo do item, aberto dentro do cartão — para ler sem clicar.\n *\n * Só é chamado para o que `expandedIds` mandar abrir: montar o corpo de cem\n * formulários para esconder noventa e nove custa a rolagem inteira.\n */\n renderPreview?: (item: TimelineItem) => React.ReactNode\n /** Quais itens estão abertos. Ausente = nenhum. */\n expandedIds?: ReadonlySet<string>\n}) {\n return (\n <ol className=\"space-y-0\">\n {items.map((item, index) => (\n <TimelineRow\n key={item.id}\n item={item}\n // Item ganha do padrão: um evento que foge da regra da fonte dele\n // (uma exclusão dentro de \"Cadastro\") pode dizer o seu.\n tone={item.tone ?? toneFor?.(item) ?? 'neutral'}\n href={hrefFor?.(item)}\n onSelect={onSelect}\n actions={renderActions?.(item)}\n preview={expandedIds?.has(item.id) ? renderPreview?.(item) : undefined}\n first={index === 0}\n last={index === items.length - 1}\n />\n ))}\n </ol>\n )\n}\n\nexport { TimelineSkeleton }\n","import React, { useState } from 'react'\nimport { Button, Card, CardContent } from '@fayz-ai/ui'\nimport { usePersonTimeline } from './usePersonTimeline'\nimport { TimelineList, TimelineSkeleton } from './TimelineList'\nimport type { TimelineItem } from './types'\n// Importing the component registers the core sources; there is no separate install step.\nimport './core-sources'\nimport { appHref } from '@fayz-ai/core'\n\n/**\n * A linha do tempo de uma pessoa, montada a partir das fontes registradas.\n *\n * Genérico de propósito, em `packages/admin` e não no app: \"o que aconteceu com\n * esta pessoa\" não é específico de salão — a mesma tela serve aluno, paciente e\n * cliente. O app só monta.\n */\nexport function PersonTimeline({\n personId,\n pageSize,\n recordPath,\n}: {\n personId: string\n pageSize?: number\n /**\n * A rota da ficha desta pessoa (`/clients/8e48…`), que é o app quem sabe.\n *\n * Com ela, cada item vira atalho para a aba onde o evento mora — clicar num\n * pagamento abre o Extrato, clicar numa gravação abre Gravações. Sem ela a\n * linha do tempo funciona igual, só não navega: é degradação, não erro.\n */\n recordPath?: string\n}) {\n const [activeSourceIds, setActiveSourceIds] = useState<string[] | undefined>(undefined)\n const { items, sources, loading, loadingMore, hasMore, loadMore } = usePersonTimeline({\n personId,\n pageSize,\n activeSourceIds,\n })\n\n const toneBySource = React.useMemo(\n () => new Map(sources.map((s) => [s.id, s.tone ?? 'neutral'] as const)),\n [sources],\n )\n\n const hrefFor = React.useCallback(\n (item: TimelineItem): string | undefined => {\n if (item.href) return item.href\n if (!recordPath || !item.targetTab) return undefined\n // Mesma forma de rota que o CrudDetailPage usa ao trocar de aba, para o\n // link cair exatamente onde a aba clicada à mão cairia.\n return appHref(`${recordPath}/${item.targetTab}`)\n },\n [recordPath],\n )\n\n // Com seis fontes numa base antiga, a lista única fica ilegível. As pastilhas\n // são de seleção ÚNICA (Tudo, ou um tipo): multi-seleção pareceria mais\n // poderosa e na prática vira um estado que ninguém consegue ler de relance.\n const filters = sources.length > 1 && (\n <div className=\"flex flex-wrap gap-1.5\">\n {[{ id: undefined, label: 'Tudo' }, ...sources.map((s) => ({ id: s.id as string | undefined, label: s.label }))].map(\n (chip) => {\n const active = chip.id\n ? activeSourceIds?.length === 1 && activeSourceIds[0] === chip.id\n : !activeSourceIds\n return (\n <Button\n variant=\"bare\"\n size=\"bare\"\n key={chip.id ?? '__all__'}\n type=\"button\"\n onClick={() => setActiveSourceIds(chip.id ? [chip.id] : undefined)}\n className={`font-normal rounded-full border px-2.5 py-1 text-xs transition-colors ${\n active\n ? 'border-primary bg-primary/10 text-primary'\n : 'border-border text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n {chip.label}\n </Button>\n )\n },\n )}\n </div>\n )\n\n return (\n <div className=\"space-y-4\">\n {filters}\n\n {loading ? (\n <TimelineSkeleton />\n ) : items.length === 0 ? (\n <Card>\n <CardContent className=\"flex min-h-40 items-center justify-center p-6 text-sm text-muted-foreground\">\n {activeSourceIds\n ? 'Nada deste tipo no histórico desta pessoa.'\n : 'Ainda não há histórico para esta pessoa.'}\n </CardContent>\n </Card>\n ) : (\n <>\n <TimelineList\n items={items}\n // Item ganha da fonte: um evento que foge da regra dela (uma\n // exclusão dentro de \"Cadastro\") pode dizer o seu — o próprio\n // TimelineList resolve essa precedência.\n toneFor={(item) => toneBySource.get(item.sourceId) ?? 'neutral'}\n hrefFor={hrefFor}\n />\n {hasMore && (\n <div className=\"flex justify-center\">\n <Button variant=\"outline\" size=\"sm\" disabled={loadingMore} onClick={loadMore}>\n {loadingMore ? 'Carregando…' : 'Carregar mais'}\n </Button>\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"]}
|