@fayz-ai/admin 0.44.0 → 0.45.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/index.js CHANGED
@@ -21,13 +21,13 @@ export { getPersistedOrgId, useOrganizationStore } from './chunk-QOS6UAQ4.js';
21
21
  import * as React133 from 'react';
22
22
  import React133__default, { createContext, useMemo, useState, useRef, useCallback, useEffect, useLayoutEffect, useContext, createElement } from 'react';
23
23
  import * as LucideIcons from 'lucide-react';
24
- import { RotateCcw, WifiOff, Pencil, Copy, Trash2, Upload, Search, ArrowDown, ArrowUp, ArrowUpDown, Check, Download, ChevronLeft, ChevronRight, AlertTriangle, Building2, Inbox, Package, Sparkles, Plug, DollarSign, Clock, X, WandSparkles, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, Crown, ArrowRight, History, SquarePen, Settings2, Mic, User, Plus, Minus, ListChecks, ChevronDown, ArrowLeft, Calendar, ClipboardList, BookOpen, Lock, PinOff, Pin, Eye, Users, StickyNote, CheckSquare2, Square, Globe, LayoutTemplate, Store, ChevronsUpDown, ExternalLink, PenSquare, RefreshCw, XCircle, Camera, Edit, Star, Link2Off, Layers, ShoppingBag, MoreHorizontal, ChevronUp, List, LayoutGrid, CircleDollarSign, Receipt, Settings, Mail, MessageCircle, Cake, HelpCircle, Wrench, ShieldCheck, MessageSquare, Paperclip, Infinity, CheckCheck, PartyPopper, Compass, Play, MapPin, Landmark, LocateFixed, ArrowUpCircle, ArrowUpRight, Pause, CalendarDays, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, FileText, CircleDashed, Power, ArrowRightLeft, File as File$1, FileImage, FileSpreadsheet, AlertCircle, CreditCard, SlidersHorizontal, Puzzle, Shield, Palette, Blocks, ShieldAlert, UserCog, LogOut, Bell, Database, Radio, Zap, Save, ImagePlus, UserPlus, Contact, UserMinus, Send, Sun, Moon, Monitor, Languages, Info, Link2, KeyRound, Ellipsis, Phone, Bug, Home, Heart, Briefcase, BarChart3, Tag, Truck, Globe2 } from 'lucide-react';
24
+ import { RotateCcw, WifiOff, Pencil, Copy, Trash2, Upload, Search, ArrowDown, ArrowUp, ArrowUpDown, Check, Download, ChevronLeft, ChevronRight, AlertTriangle, Building2, Inbox, Package, Sparkles, Plug, DollarSign, Clock, X, WandSparkles, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, Crown, ArrowRight, History, SquarePen, Settings2, Mic, User, Plus, Minus, ListChecks, ChevronDown, ArrowLeft, Calendar, ClipboardList, BookOpen, Lock, PinOff, Pin, Eye, Users, StickyNote, CheckSquare2, Square, Globe, LayoutTemplate, Store, ChevronsUpDown, ExternalLink, PenSquare, RefreshCw, XCircle, Edit, Camera, Star, Link2Off, Layers, ShoppingBag, MoreHorizontal, ChevronUp, List, LayoutGrid, CircleDollarSign, Receipt, Settings, Mail, MessageCircle, Cake, HelpCircle, Wrench, ShieldCheck, MessageSquare, Paperclip, Infinity, CheckCheck, PartyPopper, Compass, Play, MapPin, Landmark, LocateFixed, ArrowUpCircle, ArrowUpRight, Pause, CalendarDays, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, FileText, CircleDashed, Power, ArrowRightLeft, File as File$1, FileImage, FileSpreadsheet, AlertCircle, CreditCard, SlidersHorizontal, Puzzle, Shield, Palette, Blocks, ShieldAlert, UserCog, LogOut, Bell, Database, Radio, Zap, Save, ImagePlus, UserPlus, Contact, UserMinus, Send, Sun, Moon, Monitor, Languages, Info, Link2, KeyRound, Ellipsis, Phone, Bug, Home, Heart, Briefcase, BarChart3, Tag, Truck, Globe2 } from 'lucide-react';
25
25
  import { reportAppError, toDataError, tl, createDebug, fetchAppConfig, getSupabaseClientOptional, payeeAccountFields, maskCpfCnpj, recordRailFields, RECORD_GROUP, activeFacet, createSafeDataProvider, isKnownMissing, rememberIfMissing, deriveEntityKey, registerEntity, getActiveTenantId, resolveDataProvider, bulkRemove, emitDomainEvent, entityEventKey, errorMessage, useTranslation, describeError, useDataChanged, emitDataChanged, exportCSV, getScopedResource, formatDateTime as formatDateTime$1, formatDate as formatDate$1, formatCurrency as formatCurrency$1, bulkCreate, matchesRange, isConnectorCategory, usePluginRuntimeOptional, useDataVersion, getWidgetsForZone, resolvePluginComponent, useUnitMode, invalidateAppConfig, toTenantPluginBindings, setCurrentLocale, resolveLocale, resolvePluginRuntime, PluginRuntimeProvider, I18nProvider, getAllEntities, resolveIntegrationSurface, defineApp, registerScaffold, subscribeHouseCurrency, getHouseCurrency, resolveCurrency, useActiveTenantId, onDataChanged, useConnectorsForPlugin, renderApp as renderApp$1, parseRangeFilter, isFilterObject, connectionKey, useManifest, registerEventSink, formatPostalCode, isValidPostalCode, lookupPostalCode, usePluginRuntime, useConnectivity, RECONNECTED_EVENT, installGlobalErrorHandlers, registerErrorReporter, getConnectivity, applyNavComposition, mergeTranslations, coreTranslations, attachPersistentCache, getPersistentCache, createMockProvider, listConnections, connectorPlanes, connectorPermissions, connectorEntities, preferredPlane, connectorDirection, countByTenant, isAddressIncomplete, isBankIncomplete, payeeMethodOf, getCurrentLocale, searchEverything, readImpersonation, getComponent, getPluginFactory, connectorSettings, groupedPermissions, invocationPlane, missingRequiredSettings, isBankEmpty, isAddressEmpty, writeImpersonation, isSettingSet, getEntityByKey, connectorSubscribesTo, eventKeyMatches } from '@fayz-ai/core';
26
26
  export { ConnectorRuntimeTokenUnavailableError, createSafeDataProvider, formatDate, formatDateTime, getActiveLocale, getActiveTenantId, getConnectorRuntimeTokenSource, getCurrentLocale, getSupabaseClientOptional, preloadLocale, resolveBackendEnv, setConnectorRuntimeTokenSource, setCurrentLocale, tl, useActiveTenantId } from '@fayz-ai/core';
27
27
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
28
28
  import { usePasswordSetupGate, SetPasswordPage, AuthGate, AuthProvider, createAuthRuntime } from '@fayz-ai/plugin-auth';
29
29
  export { LoginPage } from '@fayz-ai/plugin-auth';
30
- import { Input, SearchSelect, toast, usePageTrailParent, navigateToCrumb, Button, PageHeader, ConfirmDialog, RowActions, useRowSelection, getLucideIcon, ListFilterBar, Dropdown, DropdownTrigger, Hint, cn as cn$1, DropdownContent, DropdownLabel, DropdownItem, DropdownSeparator, RefreshButton, PageHeaderActions, PageTabs, StatTiles, Card, BulkActionBar, DataTable, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Badge, PersonAvatar, PageActionBar, ICON_MAP, Sheet, SheetContent, SheetBody, useIsWide, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Checkbox, useTourStore, setTourNavigator, TourSpotlight, PracticeOverlay, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Wizard, SidebarOnboarding, OnboardingWidget, Skeleton, useLayoutStore, ModalPortal, ModalOverlay, ModalBody, useSaveBar, useBackHandler, SubpageHeader, Separator, Tabs, TabsList, TabsTrigger, TabsContent, CardContent, ToastProvider, NavTransitionProvider, ListView, Thumbnail, CurrencyInput, SearchField, ChevronPager, Popover, PopoverTrigger, PopoverContent, useModuleLayout, PhoneLink, normalizePhone, SearchCombobox, HeaderTabs, usePageTrail, PageHeaderOutlet, AppShellSkeleton, usePracticeStore, ViewTabs, Switch, ModalTask, setPrintBrand, useModuleViewLabel, ModuleLayoutProvider, AppShell, PageTransition, PickerDialog, MarkdownEditor, PhoneField, isCreateRoute, PageSubNav, MobileScrim, createFayzTheme, Avatar, AvatarImage, AvatarFallback, Markdown, PageTrail, CardTitle, CardDescription, DropdownSub, DropdownSubTrigger, DropdownSubContent, DropdownRadioGroup, DropdownRadioItem, BottomSheet, CardHeader, CardFooter, SegmentedControl, SheetCard, SheetRow, useSheetClose } from '@fayz-ai/ui';
30
+ import { Input, SearchSelect, toast, usePageTrailParent, navigateToCrumb, Button, PageHeader, ConfirmDialog, RowActions, useRowSelection, getLucideIcon, ListFilterBar, Dropdown, DropdownTrigger, Hint, cn as cn$1, DropdownContent, DropdownLabel, DropdownItem, DropdownSeparator, RefreshButton, PageHeaderActions, PageTabs, StatTiles, Card, BulkActionBar, DataTable, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Badge, PersonAvatar, PageActionBar, ICON_MAP, Sheet, SheetContent, SheetBody, useIsWide, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Checkbox, useTourStore, setTourNavigator, TourSpotlight, PracticeOverlay, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Wizard, SidebarOnboarding, OnboardingWidget, Skeleton, useLayoutStore, ModalPortal, ModalOverlay, ModalBody, useSaveBar, useBackHandler, SubpageHeader, Tabs, TabsContent, CardContent, ToastProvider, NavTransitionProvider, ListView, Thumbnail, CurrencyInput, TabsList, TabsTrigger, SearchField, ChevronPager, Popover, PopoverTrigger, PopoverContent, useModuleLayout, PhoneLink, normalizePhone, SearchCombobox, HeaderTabs, usePageTrail, PageHeaderOutlet, AppShellSkeleton, usePracticeStore, ViewTabs, Switch, ModalTask, setPrintBrand, useModuleViewLabel, ModuleLayoutProvider, AppShell, PageTransition, PickerDialog, MarkdownEditor, PhoneField, isCreateRoute, PageSubNav, MobileScrim, createFayzTheme, Avatar, AvatarImage, AvatarFallback, Markdown, PageTrail, CardTitle, CardDescription, DropdownSub, DropdownSubTrigger, DropdownSubContent, DropdownRadioGroup, DropdownRadioItem, BottomSheet, CardHeader, CardFooter, SegmentedControl, Separator, SheetCard, SheetRow, useSheetClose } from '@fayz-ai/ui';
31
31
  export { createFayzTheme, fayzThemePresets } from '@fayz-ai/ui';
32
32
  import { useAuthStore, useAuth, buildAppHandoffUrl } from '@fayz-ai/auth';
33
33
  export { useAuth } from '@fayz-ai/auth';
@@ -22345,6 +22345,84 @@ function RecordMetaFooter({
22345
22345
  }
22346
22346
  );
22347
22347
  }
22348
+ function resolveTabIcon(name) {
22349
+ const icon = LucideIcons[name];
22350
+ return typeof icon === "function" || typeof icon === "object" ? icon ?? null : null;
22351
+ }
22352
+ function statusLabel(entityDef, value) {
22353
+ const options = entityDef.fields.find((f) => f.key === "status")?.options;
22354
+ if (Array.isArray(options)) {
22355
+ for (const option of options) {
22356
+ if (typeof option === "string") {
22357
+ if (option === value) return option;
22358
+ } else if (option.value === value) {
22359
+ return option.label;
22360
+ }
22361
+ }
22362
+ }
22363
+ return typeof value === "string" ? value.charAt(0).toUpperCase() + value.slice(1) : value;
22364
+ }
22365
+ function RecordHeader({ entityDef, item, title, parentLabel, onBack, badges, actions }) {
22366
+ const t = useTranslation();
22367
+ const myPersonId = useMyPersonId(entityDef.layout === "person");
22368
+ const isMyRecord = Boolean(myPersonId) && myPersonId === item.id;
22369
+ const imageValue = entityDef.image?.get(item) ?? (entityDef.imageField ? item[entityDef.imageField] : null);
22370
+ const customAvatar = entityDef.renderAvatar ? entityDef.renderAvatar(item) : null;
22371
+ const subtitle = entityDef.subtitleField ? item[entityDef.subtitleField] : null;
22372
+ const activeValue = item.isActive ?? item.is_active;
22373
+ const showStatus = Boolean(item.status) && entityDef.fields.some((f) => f.key === "status");
22374
+ const hasChips = isMyRecord || showStatus || activeValue !== void 0 || Boolean(badges);
22375
+ const chips = hasChips ? /* @__PURE__ */ jsxs(Fragment, { children: [
22376
+ isMyRecord && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: t("common.you") }),
22377
+ showStatus && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: statusLabel(entityDef, item.status) }),
22378
+ activeValue !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: activeValue ? "success" : "destructive", className: "shrink-0", children: activeValue ? t("common.active") : t("common.inactive") }),
22379
+ badges
22380
+ ] }) : null;
22381
+ const thumb = !customAvatar && imageValue ? /* @__PURE__ */ jsx("img", { src: imageValue, alt: "", className: "mr-0.5 h-6 w-6 rounded-md object-cover" }) : void 0;
22382
+ const trailing = subtitle || chips ? /* @__PURE__ */ jsxs(Fragment, { children: [
22383
+ subtitle && /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: subtitle }),
22384
+ chips
22385
+ ] }) : void 0;
22386
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22387
+ /* @__PURE__ */ jsx(
22388
+ SubpageHeader,
22389
+ {
22390
+ title,
22391
+ parentLabel,
22392
+ onBack,
22393
+ actions,
22394
+ titleLeading: thumb,
22395
+ titleTrailing: trailing
22396
+ }
22397
+ ),
22398
+ customAvatar && /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center", children: customAvatar }),
22399
+ (thumb || trailing) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2 text-sm md:hidden", children: [
22400
+ thumb,
22401
+ trailing
22402
+ ] })
22403
+ ] });
22404
+ }
22405
+ function RecordTabStrip({ tabs, compact }) {
22406
+ const t = useTranslation();
22407
+ const trigger = `gap-1.5 ${compact ? "text-xs px-2 py-1" : ""}`;
22408
+ const iconSize = compact ? "h-3 w-3" : "h-3.5 w-3.5";
22409
+ return /* @__PURE__ */ jsxs(TabsList, { className: `max-w-full justify-start overflow-x-auto scrollbar-none${compact ? " h-8" : ""}`, children: [
22410
+ /* @__PURE__ */ jsxs(TabsTrigger, { value: "overview", className: trigger, children: [
22411
+ /* @__PURE__ */ jsx(Eye, { className: iconSize }),
22412
+ t("common.overview")
22413
+ ] }),
22414
+ tabs.map((tab) => {
22415
+ const translated = t(`crud.tabs.${tab.id}`);
22416
+ const label3 = translated.startsWith("crud.tabs.") ? tab.label : translated;
22417
+ const TabIcon = tab.icon ? resolveTabIcon(tab.icon) : null;
22418
+ return /* @__PURE__ */ jsxs(TabsTrigger, { value: tab.id, className: trigger, children: [
22419
+ TabIcon && /* @__PURE__ */ jsx(TabIcon, { className: iconSize }),
22420
+ label3,
22421
+ tab.count ? /* @__PURE__ */ jsx("span", { className: `rounded-full bg-muted px-1.5 tabular-nums text-muted-foreground ${compact ? "text-[9px]" : "text-[10px]"}`, children: tab.count }) : null
22422
+ ] }, tab.id);
22423
+ })
22424
+ ] });
22425
+ }
22348
22426
 
22349
22427
  // src/crud/entity-zone.ts
22350
22428
  function entityZoneId(entityDef) {
@@ -23218,7 +23296,7 @@ function PersonFormLayout({ fields: allFields, fieldGroups, values, onChange, re
23218
23296
  ] }),
23219
23297
  recordId && /* @__PURE__ */ jsxs("div", { children: [
23220
23298
  /* @__PURE__ */ jsx(AddressesSection, { item: { id: recordId } }),
23221
- /* @__PURE__ */ jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsx(PayeeAccountsSection, { item: { id: recordId } }) })
23299
+ allFields.some((f) => f.type === "bank") && /* @__PURE__ */ jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsx(PayeeAccountsSection, { item: { id: recordId } }) })
23222
23300
  ] }),
23223
23301
  notesField && /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: cardPad, children: /* @__PURE__ */ jsx(FieldItem, { field: notesField, value: values[notesField.key], values, onChange: (val) => onChange(notesField.key, val), renderField: renderField2, compact }) }) })
23224
23302
  ] });
@@ -25428,7 +25506,7 @@ function CrudFormPage(props) {
25428
25506
  const { provider: provider2, flush } = usePendingChildren();
25429
25507
  return /* @__PURE__ */ jsx(PendingChildrenProvider, { value: provider2, children: /* @__PURE__ */ jsx(CrudFormPageBody, { ...props, flushPending: flush }) });
25430
25508
  }
25431
- function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onCancel, namePlural, embedded, hideBreadcrumb, formId, hideActions, essentialFirst, noForm, submitRef, flushPending }) {
25509
+ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onCancel, namePlural, embedded, hideBreadcrumb, onRecordTab, formId, hideActions, essentialFirst, noForm, submitRef, flushPending }) {
25432
25510
  const t = useTranslation();
25433
25511
  const formFields = entityDef.fields.filter((f) => f.showInForm !== false);
25434
25512
  const displayField = entityDef.displayField ?? entityDef.fields[0]?.key ?? "id";
@@ -25592,6 +25670,35 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25592
25670
  }
25593
25671
  }, [invalidKeys]);
25594
25672
  const groupsOfTab = (name) => groups.filter((g) => !sideGroupIds.has(g.id) && (g.tab ?? FIRST_TAB) === name);
25673
+ const recordItem = React133__default.useMemo(() => ({ ...initialData ?? {}, ...values }), [initialData, values]);
25674
+ const record = useRecordTabs(entityDef, mode === "edit" ? recordItem : null);
25675
+ const showRecordTabs = !embedded && mode === "edit" && Boolean(onRecordTab) && Boolean(initialData?.id);
25676
+ const formHoldsTabs = tabbed && tabNames.length > 1 && (!entityDef.layout || entityDef.layout === "location");
25677
+ const FORM_TAB = "form:";
25678
+ const stripTabs = [];
25679
+ if (showRecordTabs) {
25680
+ const sections = formHoldsTabs ? tabNames.filter((name) => name !== FIRST_TAB) : [];
25681
+ const placed = /* @__PURE__ */ new Set();
25682
+ let afterSections = 0;
25683
+ for (const tab of record.customTabs) {
25684
+ if (tab.id.startsWith("fields:")) {
25685
+ if (!sections.includes(tab.label)) continue;
25686
+ stripTabs.push({ id: FORM_TAB + tab.label, label: tab.label, icon: tab.icon });
25687
+ placed.add(tab.label);
25688
+ afterSections = stripTabs.length;
25689
+ } else {
25690
+ stripTabs.push({ id: tab.id, label: tab.label, icon: tab.icon, count: record.tabCounts[tab.id] });
25691
+ }
25692
+ }
25693
+ const missing = sections.filter((name) => !placed.has(name)).map((name) => ({ id: FORM_TAB + name, label: name }));
25694
+ stripTabs.splice(afterSections, 0, ...missing);
25695
+ }
25696
+ const stripValue = formHoldsTabs && currentTab !== FIRST_TAB ? FORM_TAB + currentTab : "overview";
25697
+ const onStripChange = (id) => {
25698
+ if (id === "overview") setActiveTab(FIRST_TAB);
25699
+ else if (id.startsWith(FORM_TAB)) setActiveTab(id.slice(FORM_TAB.length));
25700
+ else onRecordTab?.(id);
25701
+ };
25595
25702
  const archetypeLayoutProps = {
25596
25703
  fields: archetypeFields,
25597
25704
  allFields: entityDef.fields,
@@ -25653,7 +25760,7 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25653
25760
  if (tabbed && tabNames.length > 1) {
25654
25761
  return withSideRail(
25655
25762
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
25656
- /* @__PURE__ */ jsx(
25763
+ !showRecordTabs && /* @__PURE__ */ jsx(
25657
25764
  ViewTabs,
25658
25765
  {
25659
25766
  value: currentTab,
@@ -25677,18 +25784,32 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25677
25784
  }
25678
25785
  }
25679
25786
  const trailParent = usePageTrailParent();
25680
- const header = !embedded && !hideBreadcrumb ? /* @__PURE__ */ jsx(
25787
+ const cancelAction = /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onCancel, disabled: saving, children: t("common.cancel") });
25788
+ const headerBack = trailParent ? () => navigateToCrumb(trailParent) : onCancel;
25789
+ const header = embedded || hideBreadcrumb ? null : mode === "edit" ? /* @__PURE__ */ jsx(
25790
+ RecordHeader,
25791
+ {
25792
+ entityDef,
25793
+ item: recordItem,
25794
+ title: String(breadcrumbLabel),
25795
+ parentLabel: trailParent?.label ?? namePlural,
25796
+ onBack: headerBack,
25797
+ actions: cancelAction
25798
+ }
25799
+ ) : /* @__PURE__ */ jsx(
25681
25800
  SubpageHeader,
25682
25801
  {
25683
25802
  title: breadcrumbLabel,
25684
25803
  parentLabel: trailParent?.label ?? namePlural,
25685
- onBack: trailParent ? () => navigateToCrumb(trailParent) : onCancel
25804
+ onBack: headerBack,
25805
+ actions: cancelAction
25686
25806
  }
25687
- ) : null;
25807
+ );
25688
25808
  const FieldsTag = noForm ? "div" : "form";
25689
- return /* @__PURE__ */ jsx(SetFieldValueContext.Provider, { value: handleChange, children: /* @__PURE__ */ jsx("div", { className: `w-full flex flex-col items-center ${embedded ? "text-sm" : "pb-28"}`, children: /* @__PURE__ */ jsxs("div", { className: `fz-container w-full pb-24 ${embedded ? "space-y-3" : entityDef.formColumns === 2 ? "max-w-6xl space-y-5" : "max-w-3xl space-y-5"}`, children: [
25690
- header,
25691
- /* @__PURE__ */ jsxs(
25809
+ return /* @__PURE__ */ jsx(SetFieldValueContext.Provider, { value: handleChange, children: /* @__PURE__ */ jsxs("div", { className: `w-full flex flex-col items-center ${embedded ? "text-sm" : "pb-28"}`, children: [
25810
+ header && /* @__PURE__ */ jsx("div", { className: "w-full", children: header }),
25811
+ showRecordTabs && /* @__PURE__ */ jsx(Tabs, { value: stripValue, onValueChange: onStripChange, className: "mb-4 w-full", children: /* @__PURE__ */ jsx(RecordTabStrip, { tabs: stripTabs }) }),
25812
+ /* @__PURE__ */ jsx("div", { className: `fz-container w-full pb-24 ${embedded ? "space-y-3" : entityDef.formColumns === 2 ? "max-w-6xl space-y-5" : "max-w-3xl space-y-5"}`, children: /* @__PURE__ */ jsxs(
25692
25813
  FieldsTag,
25693
25814
  {
25694
25815
  ...noForm ? {} : { id: formId, onSubmit: handleSubmit },
@@ -25735,31 +25856,27 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25735
25856
  ] })
25736
25857
  ]
25737
25858
  }
25738
- )
25739
- ] }) }) });
25859
+ ) })
25860
+ ] }) });
25861
+ }
25862
+ function TabStripSkeleton() {
25863
+ return /* @__PURE__ */ jsxs("div", { className: "inline-flex h-10 items-center gap-1 rounded-button bg-muted p-1", children: [
25864
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-28 rounded-button bg-background" }),
25865
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-16 mx-3 rounded bg-muted-foreground/15" }),
25866
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-20 mx-3 rounded bg-muted-foreground/15" }),
25867
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-14 mx-3 rounded bg-muted-foreground/15" })
25868
+ ] });
25740
25869
  }
25741
25870
  function CrudDetailSkeleton(_props = {}) {
25742
25871
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6 animate-pulse", children: [
25743
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-5", children: [
25744
- /* @__PURE__ */ jsx("div", { className: "h-20 w-20 shrink-0 rounded-2xl bg-muted" }),
25745
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2 pt-1", children: [
25746
- /* @__PURE__ */ jsx("div", { className: "h-7 w-48 rounded bg-muted" }),
25747
- /* @__PURE__ */ jsx("div", { className: "h-4 w-32 rounded bg-muted" }),
25748
- /* @__PURE__ */ jsx("div", { className: "mt-1 h-5 w-16 rounded-full bg-muted" })
25749
- ] })
25750
- ] }),
25751
- /* @__PURE__ */ jsx("div", { className: "h-px bg-border" }),
25752
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
25753
- /* @__PURE__ */ jsx("div", { className: "h-8 w-20 rounded bg-muted" }),
25754
- /* @__PURE__ */ jsx("div", { className: "h-8 w-24 rounded bg-muted" })
25755
- ] }),
25872
+ /* @__PURE__ */ jsx(TabStripSkeleton, {}),
25756
25873
  /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("div", { className: "divide-y", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4 px-5 py-3", children: [
25757
25874
  /* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted" }),
25758
25875
  /* @__PURE__ */ jsx("div", { className: "col-span-2 h-4 w-32 rounded bg-muted" })
25759
25876
  ] }, i)) }) })
25760
25877
  ] });
25761
25878
  }
25762
- function CrudFormSkeleton({ groups = 2, twoColumn } = {}) {
25879
+ function CrudFormSkeleton({ groups = 2, twoColumn, tabs } = {}) {
25763
25880
  const section = (key, rows) => /* @__PURE__ */ jsxs(Card, { className: "p-5", children: [
25764
25881
  /* @__PURE__ */ jsx("div", { className: "mb-4 h-4 w-32 rounded bg-muted" }),
25765
25882
  /* @__PURE__ */ jsx("div", { className: "fz-fields fz-fields-2 gap-4", children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
@@ -25768,10 +25885,13 @@ function CrudFormSkeleton({ groups = 2, twoColumn } = {}) {
25768
25885
  ] }, i)) })
25769
25886
  ] }, key);
25770
25887
  const main = /* @__PURE__ */ jsx("div", { className: "space-y-5", children: Array.from({ length: groups }).map((_, i) => section(i, i === 0 ? 4 : 2)) });
25771
- return /* @__PURE__ */ jsx("div", { className: "w-full animate-pulse", children: /* @__PURE__ */ jsx("div", { className: `fz-container mx-auto w-full space-y-5 ${twoColumn ? "max-w-6xl" : "max-w-3xl"}`, children: twoColumn ? /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
25772
- /* @__PURE__ */ jsx("div", { children: main }),
25773
- /* @__PURE__ */ jsx("div", { className: "space-y-5", children: section(99, 2) })
25774
- ] }) : main }) });
25888
+ return /* @__PURE__ */ jsxs("div", { className: "w-full animate-pulse", children: [
25889
+ tabs && /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx(TabStripSkeleton, {}) }),
25890
+ /* @__PURE__ */ jsx("div", { className: `fz-container mx-auto w-full space-y-5 ${twoColumn ? "max-w-6xl" : "max-w-3xl"}`, children: twoColumn ? /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
25891
+ /* @__PURE__ */ jsx("div", { children: main }),
25892
+ /* @__PURE__ */ jsx("div", { className: "space-y-5", children: section(99, 2) })
25893
+ ] }) : main })
25894
+ ] });
25775
25895
  }
25776
25896
  function formatValue2(field, value) {
25777
25897
  if (value == null || value === "") return null;
@@ -27027,7 +27147,7 @@ function CrudPage({ entityDef: rawEntityDef, useStore: useStore2, basePath, disp
27027
27147
  const item = store.getById(id);
27028
27148
  viewKey = `edit-${id}`;
27029
27149
  if (!item && store.items === null) {
27030
- content = /* @__PURE__ */ jsx(CrudFormSkeleton, { twoColumn: entityDef.formColumns === 2 });
27150
+ content = /* @__PURE__ */ jsx(CrudFormSkeleton, { twoColumn: entityDef.formColumns === 2, tabs: true });
27031
27151
  } else if (!item) {
27032
27152
  content = /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
27033
27153
  /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-1.5 text-sm text-muted-foreground", children: [
@@ -27052,6 +27172,9 @@ function CrudPage({ entityDef: rawEntityDef, useStore: useStore2, basePath, disp
27052
27172
  onCancel: () => {
27053
27173
  setCrudHash(normalizedBasePath, id);
27054
27174
  },
27175
+ onRecordTab: (tab) => {
27176
+ setCrudHash(normalizedBasePath, id, tab);
27177
+ },
27055
27178
  onSubmit: async (data) => {
27056
27179
  await store.update(id, data);
27057
27180
  setCrudHash(normalizedBasePath, id);
@@ -29214,22 +29337,52 @@ function resolveActiveTab(initialTab, tabs) {
29214
29337
  const alias = tabs.find((tab) => tab.aliases?.includes(normalized));
29215
29338
  return alias?.id ?? "overview";
29216
29339
  }
29217
- function resolveTabIcon(name) {
29218
- const icon = LucideIcons[name];
29219
- return typeof icon === "function" || typeof icon === "object" ? icon ?? null : null;
29220
- }
29221
- function statusLabel(entityDef, value) {
29222
- const options = entityDef.fields.find((f) => f.key === "status")?.options;
29223
- if (Array.isArray(options)) {
29224
- for (const option of options) {
29225
- if (typeof option === "string") {
29226
- if (option === value) return option;
29227
- } else if (option.value === value) {
29228
- return option.label;
29229
- }
29340
+ function useRecordTabs(entityDef, item) {
29341
+ const runtime = usePluginRuntimeOptional();
29342
+ const archetypeTabs = getArchetypeTabs(entityDef.layout, entityDef.data?.archetypeKind);
29343
+ const relatedTabs = (entityDef.relatedLists ?? []).map((related) => ({
29344
+ id: relatedListTabId(related),
29345
+ label: related.label ?? related.entity.namePlural ?? related.entity.name,
29346
+ icon: related.icon ?? related.entity.icon,
29347
+ component: (props) => /* @__PURE__ */ jsx(RelatedListTab, { ...props, related })
29348
+ }));
29349
+ const unitMode = useUnitMode();
29350
+ const fieldGroupTabs = React133__default.useMemo(() => {
29351
+ const groups = entityDef.fieldGroups ?? [];
29352
+ if (!groups.some((g) => g.tab) || !item) return [];
29353
+ const applicable = new Set(visibleDetailFields(entityDef, item).map((f) => f.group));
29354
+ const byTab = /* @__PURE__ */ new Map();
29355
+ const iconOf = /* @__PURE__ */ new Map();
29356
+ for (const g of groups) {
29357
+ if (!g.tab || !applicable.has(g.id)) continue;
29358
+ byTab.set(g.tab, [...byTab.get(g.tab) ?? [], g.id]);
29359
+ if (!iconOf.has(g.tab)) iconOf.set(g.tab, g.tabIcon);
29230
29360
  }
29231
- }
29232
- return typeof value === "string" ? value.charAt(0).toUpperCase() + value.slice(1) : value;
29361
+ return [...byTab.entries()].map(([label3, groupIds]) => ({
29362
+ id: `fields:${groupIds[0]}`,
29363
+ label: label3,
29364
+ icon: iconOf.get(label3),
29365
+ component: (props) => /* @__PURE__ */ jsx(
29366
+ FieldGroupTab,
29367
+ {
29368
+ entityDef: props.entityDef,
29369
+ item: props.item,
29370
+ groupIds,
29371
+ onFieldSave: props.onFieldSave,
29372
+ pendingKeys: props.pendingKeys
29373
+ }
29374
+ )
29375
+ }));
29376
+ }, [entityDef, item]);
29377
+ const customTabs = filterRuntimeTabs(
29378
+ mergeDetailTabs(archetypeTabs, [...fieldGroupTabs, ...relatedTabs, ...entityDef.detailTabs ?? []]),
29379
+ {
29380
+ hasWidget: (zone) => runtime ? getWidgetsForZone(runtime, zone).length > 0 : false,
29381
+ unitMode
29382
+ }
29383
+ );
29384
+ const tabCounts = usePersonTabCounts(entityDef.layout === "person" ? item?.id : void 0);
29385
+ return { customTabs, tabCounts };
29233
29386
  }
29234
29387
  function CrudDetailPage({
29235
29388
  entityDef,
@@ -29305,50 +29458,7 @@ function CrudDetailPage({
29305
29458
  const nameEditable = Boolean(onFieldSave || onFieldsSave) && Boolean(displayFieldDef) && isInlineEditable(displayFieldDef);
29306
29459
  const [editingName, setEditingName] = useState(false);
29307
29460
  const imageInputRef = React133__default.useRef(null);
29308
- const runtime = usePluginRuntimeOptional();
29309
- const archetypeTabs = getArchetypeTabs(entityDef.layout, entityDef.data?.archetypeKind);
29310
- const relatedTabs = (entityDef.relatedLists ?? []).map((related) => ({
29311
- id: relatedListTabId(related),
29312
- label: related.label ?? related.entity.namePlural ?? related.entity.name,
29313
- icon: related.icon ?? related.entity.icon,
29314
- component: (props) => /* @__PURE__ */ jsx(RelatedListTab, { ...props, related })
29315
- }));
29316
- const unitMode = useUnitMode();
29317
- const fieldGroupTabs = React133__default.useMemo(() => {
29318
- const groups = entityDef.fieldGroups ?? [];
29319
- if (!groups.some((g) => g.tab) || !item) return [];
29320
- const applicable = new Set(visibleDetailFields(entityDef, item).map((f) => f.group));
29321
- const byTab = /* @__PURE__ */ new Map();
29322
- const iconOf = /* @__PURE__ */ new Map();
29323
- for (const g of groups) {
29324
- if (!g.tab || !applicable.has(g.id)) continue;
29325
- byTab.set(g.tab, [...byTab.get(g.tab) ?? [], g.id]);
29326
- if (!iconOf.has(g.tab)) iconOf.set(g.tab, g.tabIcon);
29327
- }
29328
- return [...byTab.entries()].map(([label3, groupIds]) => ({
29329
- id: `fields:${groupIds[0]}`,
29330
- label: label3,
29331
- icon: iconOf.get(label3),
29332
- component: (props) => /* @__PURE__ */ jsx(
29333
- FieldGroupTab,
29334
- {
29335
- entityDef: props.entityDef,
29336
- item: props.item,
29337
- groupIds,
29338
- onFieldSave: props.onFieldSave,
29339
- pendingKeys: props.pendingKeys
29340
- }
29341
- )
29342
- }));
29343
- }, [entityDef, item]);
29344
- const customTabs = filterRuntimeTabs(
29345
- mergeDetailTabs(archetypeTabs, [...fieldGroupTabs, ...relatedTabs, ...entityDef.detailTabs ?? []]),
29346
- {
29347
- hasWidget: (zone) => runtime ? getWidgetsForZone(runtime, zone).length > 0 : false,
29348
- unitMode
29349
- }
29350
- );
29351
- const tabCounts = usePersonTabCounts(entityDef.layout === "person" ? item?.id : void 0);
29461
+ const { customTabs, tabCounts } = useRecordTabs(entityDef, item);
29352
29462
  const entityId = entityZoneId(entityDef);
29353
29463
  const legacyEntityId = legacyEntityZoneId(entityDef);
29354
29464
  const widgetZone = `${entityId}.detail.tabs`;
@@ -29398,176 +29508,169 @@ function CrudDetailPage({
29398
29508
  }, [basePath, item.id, selectedTab, embedded]);
29399
29509
  const parentLabel = usePresetLabel(basePath, namePlural);
29400
29510
  const headerNode = React133__default.useMemo(
29401
- () => embedded ? null : /* @__PURE__ */ jsx(SubpageHeader, { title: displayValue, parentLabel, onBack }),
29402
- [embedded, displayValue, parentLabel, onBack]
29511
+ () => embedded ? null : /* @__PURE__ */ jsx(RecordHeader, { entityDef, item: view, title: String(displayValue), parentLabel, onBack, badges }),
29512
+ [embedded, entityDef, view, displayValue, parentLabel, onBack, badges]
29403
29513
  );
29514
+ const actionsNode = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(ActionSlot, { embedded, children: [
29515
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: headerActionsZone, item, entityId, embedded }),
29516
+ /* @__PURE__ */ jsx(
29517
+ PageActions,
29518
+ {
29519
+ actions: [
29520
+ ...onEdit ? [{ id: "edit", label: t("common.edit"), icon: Edit, intent: "primary", onSelect: onEdit }] : [],
29521
+ ...extraActions ?? [],
29522
+ ...declared,
29523
+ // Plugin contributions join the set instead of standing beside it,
29524
+ // so they fold into the caret and the phone's menu like the rest.
29525
+ ...contributed,
29526
+ ...onDelete ? [{ id: "delete", label: t("common.delete"), icon: Trash2, intent: "danger", onSelect: onDelete }] : []
29527
+ ]
29528
+ }
29529
+ )
29530
+ ] }) });
29404
29531
  return (
29405
29532
  // A largura que importa é a desta coluna — o assistente ocupa metade da
29406
29533
  // tela e o `lg:` da janela nunca soube disso.
29407
- /* @__PURE__ */ jsxs("div", { className: `fz-container ${embedded ? "space-y-4" : "space-y-6"}`, children: [
29534
+ /* @__PURE__ */ jsxs(Fragment, { children: [
29408
29535
  headerNode,
29409
- /* @__PURE__ */ jsxs("div", { className: `flex items-start ${embedded ? "gap-3" : "gap-5"}`, children: [
29410
- /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
29411
- entityDef.renderAvatar ? entityDef.renderAvatar(item) : imageValue ? /* @__PURE__ */ jsx(
29412
- "img",
29413
- {
29414
- src: imageValue,
29415
- alt: displayValue,
29416
- className: `shrink-0 object-cover shadow-sm ${embedded ? "h-10 w-10 rounded-xl" : "h-20 w-20 rounded-2xl"}`
29417
- }
29418
- ) : /* @__PURE__ */ jsx("div", { className: `flex shrink-0 items-center justify-center bg-primary/10 text-primary font-bold shadow-sm ${embedded ? "h-10 w-10 rounded-xl text-sm" : "h-20 w-20 rounded-2xl text-2xl"}`, children: initial }),
29419
- onImageChange && /* @__PURE__ */ jsxs(Fragment, { children: [
29420
- /* @__PURE__ */ jsx(
29421
- "button",
29536
+ /* @__PURE__ */ jsxs("div", { className: `fz-container ${embedded ? "space-y-4" : "space-y-6"}`, children: [
29537
+ embedded && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
29538
+ /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
29539
+ entityDef.renderAvatar ? entityDef.renderAvatar(item) : imageValue ? /* @__PURE__ */ jsx(
29540
+ "img",
29422
29541
  {
29423
- type: "button",
29424
- onClick: () => imageInputRef.current?.click(),
29425
- "aria-label": t("common.edit"),
29426
- className: `absolute -bottom-1 -right-1 flex items-center justify-center rounded-full border-2 border-background bg-primary text-primary-foreground transition-colors hover:bg-primary/90 ${embedded ? "h-6 w-6" : "h-8 w-8"}`,
29427
- children: /* @__PURE__ */ jsx(Camera, { className: embedded ? "h-3 w-3" : "h-3.5 w-3.5" })
29542
+ src: imageValue,
29543
+ alt: displayValue,
29544
+ className: `shrink-0 object-cover shadow-sm ${embedded ? "h-10 w-10 rounded-xl" : "h-20 w-20 rounded-2xl"}`
29428
29545
  }
29429
- ),
29430
- /* @__PURE__ */ jsx(
29431
- "input",
29432
- {
29433
- ref: imageInputRef,
29434
- type: "file",
29435
- accept: "image/*",
29436
- className: "hidden",
29437
- onChange: (e) => {
29438
- const file = e.target.files?.[0];
29439
- if (file) void onImageChange(file);
29440
- if (e.target) e.target.value = "";
29546
+ ) : /* @__PURE__ */ jsx("div", { className: `flex shrink-0 items-center justify-center bg-primary/10 text-primary font-bold shadow-sm ${embedded ? "h-10 w-10 rounded-xl text-sm" : "h-20 w-20 rounded-2xl text-2xl"}`, children: initial }),
29547
+ onImageChange && /* @__PURE__ */ jsxs(Fragment, { children: [
29548
+ /* @__PURE__ */ jsx(
29549
+ "button",
29550
+ {
29551
+ type: "button",
29552
+ onClick: () => imageInputRef.current?.click(),
29553
+ "aria-label": t("common.edit"),
29554
+ className: `absolute -bottom-1 -right-1 flex items-center justify-center rounded-full border-2 border-background bg-primary text-primary-foreground transition-colors hover:bg-primary/90 ${embedded ? "h-6 w-6" : "h-8 w-8"}`,
29555
+ children: /* @__PURE__ */ jsx(Camera, { className: embedded ? "h-3 w-3" : "h-3.5 w-3.5" })
29556
+ }
29557
+ ),
29558
+ /* @__PURE__ */ jsx(
29559
+ "input",
29560
+ {
29561
+ ref: imageInputRef,
29562
+ type: "file",
29563
+ accept: "image/*",
29564
+ className: "hidden",
29565
+ onChange: (e) => {
29566
+ const file = e.target.files?.[0];
29567
+ if (file) void onImageChange(file);
29568
+ if (e.target) e.target.value = "";
29569
+ }
29441
29570
  }
29571
+ )
29572
+ ] })
29573
+ ] }),
29574
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
29575
+ /* @__PURE__ */ jsx("div", { className: "group flex items-center gap-2", children: editingName && nameEditable && displayFieldDef ? /* @__PURE__ */ jsx(
29576
+ InlineEditor,
29577
+ {
29578
+ field: displayFieldDef,
29579
+ value: displayValue,
29580
+ onCancel: () => setEditingName(false),
29581
+ onSave: async (next) => {
29582
+ stageField(displayField, next);
29583
+ },
29584
+ cancelLabel: t("common.cancel")
29442
29585
  }
29443
- )
29444
- ] })
29586
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
29587
+ /* @__PURE__ */ jsx("h1", { className: `font-bold text-foreground truncate ${embedded ? "text-base" : "text-2xl"}`, children: displayValue }),
29588
+ nameEditable && /* @__PURE__ */ jsx(
29589
+ "button",
29590
+ {
29591
+ type: "button",
29592
+ onClick: () => setEditingName(true),
29593
+ "aria-label": `${t("common.edit")} ${displayFieldDef?.label ?? ""}`,
29594
+ title: `${t("common.edit")} ${displayFieldDef?.label ?? ""}`,
29595
+ className: "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:text-foreground focus:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100",
29596
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
29597
+ }
29598
+ ),
29599
+ isMyRecord && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: t("common.you") }),
29600
+ item.status && entityDef.fields.some((f) => f.key === "status") && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: statusLabel(entityDef, item.status) }),
29601
+ activeValue !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: activeValue ? "success" : "destructive", className: "shrink-0", children: activeValue ? t("common.active") : t("common.inactive") }),
29602
+ badges
29603
+ ] }) }),
29604
+ subtitleValue && /* @__PURE__ */ jsx("p", { className: `text-muted-foreground mt-0.5 ${embedded ? "text-xs" : ""}`, children: subtitleValue })
29605
+ ] }),
29606
+ actionsNode
29445
29607
  ] }),
29446
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
29447
- /* @__PURE__ */ jsx("div", { className: "group flex items-center gap-2", children: editingName && nameEditable && displayFieldDef ? /* @__PURE__ */ jsx(
29448
- InlineEditor,
29608
+ !embedded && actionsNode,
29609
+ pendingAction && /* @__PURE__ */ jsx(
29610
+ ConfirmDialog,
29611
+ {
29612
+ open: true,
29613
+ title: pendingAction.label,
29614
+ description: pendingAction.confirm,
29615
+ confirmLabel: pendingAction.label,
29616
+ cancelLabel: t("common.cancel"),
29617
+ variant: pendingAction.intent === "danger" ? "destructive" : "default",
29618
+ loading: runningAction === pendingAction.id,
29619
+ onConfirm: () => executeAction(pendingAction),
29620
+ onCancel: () => setPendingAction(null)
29621
+ }
29622
+ ),
29623
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: aboveTabsZone, item, entityId, embedded }),
29624
+ archetypeAboveTabsZone && archetypeAboveTabsZone !== aboveTabsZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: archetypeAboveTabsZone, item, entityId, embedded }),
29625
+ /* @__PURE__ */ jsxs(Tabs, { value: selectedTab, onValueChange: handleTabChange, children: [
29626
+ /* @__PURE__ */ jsx(
29627
+ RecordTabStrip,
29449
29628
  {
29450
- field: displayFieldDef,
29451
- value: displayValue,
29452
- onCancel: () => setEditingName(false),
29453
- onSave: async (next) => {
29454
- stageField(displayField, next);
29455
- },
29456
- cancelLabel: t("common.cancel")
29629
+ compact: embedded,
29630
+ tabs: customTabs.map((tab) => ({ id: tab.id, label: tab.label, icon: tab.icon, count: tabCounts[tab.id] }))
29457
29631
  }
29458
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
29459
- /* @__PURE__ */ jsx("h1", { className: `font-bold text-foreground truncate ${embedded ? "text-base" : "text-2xl"}`, children: displayValue }),
29460
- nameEditable && /* @__PURE__ */ jsx(
29461
- "button",
29632
+ ),
29633
+ /* @__PURE__ */ jsxs(TabsContent, { value: "overview", className: embedded ? "mt-2" : "mt-4", children: [
29634
+ entityDef.overviewComponent ? React133__default.createElement(entityDef.overviewComponent, { item, entityDef }) : /* @__PURE__ */ jsx(
29635
+ OverviewTab,
29462
29636
  {
29463
- type: "button",
29464
- onClick: () => setEditingName(true),
29465
- "aria-label": `${t("common.edit")} ${displayFieldDef?.label ?? ""}`,
29466
- title: `${t("common.edit")} ${displayFieldDef?.label ?? ""}`,
29467
- className: "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:text-foreground focus:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100",
29468
- children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
29637
+ entityDef,
29638
+ item: view,
29639
+ compact: embedded,
29640
+ onFieldSave: onFieldSave || onFieldsSave ? (key, value) => stageField(key, value) : void 0,
29641
+ pendingKeys
29469
29642
  }
29470
29643
  ),
29471
- isMyRecord && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: t("common.you") }),
29472
- item.status && entityDef.fields.some((f) => f.key === "status") && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: statusLabel(entityDef, item.status) }),
29473
- activeValue !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: activeValue ? "success" : "destructive", className: "shrink-0", children: activeValue ? t("common.active") : t("common.inactive") }),
29474
- badges
29475
- ] }) }),
29476
- subtitleValue && /* @__PURE__ */ jsx("p", { className: `text-muted-foreground mt-0.5 ${embedded ? "text-xs" : ""}`, children: subtitleValue })
29477
- ] }),
29478
- /* @__PURE__ */ jsxs(ActionSlot, { embedded, children: [
29479
- /* @__PURE__ */ jsx(WidgetSlot, { zone: headerActionsZone, item, entityId, embedded }),
29480
- /* @__PURE__ */ jsx(
29481
- PageActions,
29482
- {
29483
- actions: [
29484
- ...onEdit ? [{ id: "edit", label: t("common.edit"), icon: Edit, intent: "primary", onSelect: onEdit }] : [],
29485
- ...extraActions ?? [],
29486
- ...declared,
29487
- // Plugin contributions join the set instead of standing beside it,
29488
- // so they fold into the caret and the phone's menu like the rest.
29489
- ...contributed,
29490
- ...onDelete ? [{ id: "delete", label: t("common.delete"), icon: Trash2, intent: "danger", onSelect: onDelete }] : []
29491
- ]
29492
- }
29493
- )
29494
- ] })
29495
- ] }),
29496
- pendingAction && /* @__PURE__ */ jsx(
29497
- ConfirmDialog,
29498
- {
29499
- open: true,
29500
- title: pendingAction.label,
29501
- description: pendingAction.confirm,
29502
- confirmLabel: pendingAction.label,
29503
- cancelLabel: t("common.cancel"),
29504
- variant: pendingAction.intent === "danger" ? "destructive" : "default",
29505
- loading: runningAction === pendingAction.id,
29506
- onConfirm: () => executeAction(pendingAction),
29507
- onCancel: () => setPendingAction(null)
29508
- }
29509
- ),
29510
- /* @__PURE__ */ jsx(WidgetSlot, { zone: aboveTabsZone, item, entityId, embedded }),
29511
- archetypeAboveTabsZone && archetypeAboveTabsZone !== aboveTabsZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: archetypeAboveTabsZone, item, entityId, embedded }),
29512
- !embedded && /* @__PURE__ */ jsx(Separator, {}),
29513
- /* @__PURE__ */ jsxs(Tabs, { value: selectedTab, onValueChange: handleTabChange, children: [
29514
- /* @__PURE__ */ jsxs(TabsList, { className: `max-w-full justify-start overflow-x-auto scrollbar-none${embedded ? " h-8" : ""}`, children: [
29515
- /* @__PURE__ */ jsxs(TabsTrigger, { value: "overview", className: `gap-1.5 ${embedded ? "text-xs px-2 py-1" : ""}`, children: [
29516
- /* @__PURE__ */ jsx(Eye, { className: embedded ? "h-3 w-3" : "h-3.5 w-3.5" }),
29517
- t("common.overview")
29644
+ entityDef.layout === "person" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(AddressesTab, { item }) }),
29645
+ entityDef.layout === "person" && item?.id && entityDef.fields.some((f) => f.type === "bank") && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(PayeeAccountsSection, { item }) }),
29646
+ entityDef.layout === "location" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(AddressesTab, { item, ownerType: "unit", single: true }) }),
29647
+ item?.id && (entityDef.relatedSections ?? []).map((related) => /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(RelatedSection, { parentId: item.id, related, compact: embedded }) }, relatedListTabId(related)))
29518
29648
  ] }),
29519
- customTabs.map((tab) => {
29520
- const translated = t(`crud.tabs.${tab.id}`);
29521
- const label3 = translated.startsWith("crud.tabs.") ? tab.label : translated;
29522
- const TabIcon = tab.icon ? resolveTabIcon(tab.icon) : null;
29523
- const count = tabCounts[tab.id];
29524
- return /* @__PURE__ */ jsxs(TabsTrigger, { value: tab.id, className: `gap-1.5 ${embedded ? "text-xs px-2 py-1" : ""}`, children: [
29525
- TabIcon && /* @__PURE__ */ jsx(TabIcon, { className: embedded ? "h-3 w-3" : "h-3.5 w-3.5" }),
29526
- label3,
29527
- count ? /* @__PURE__ */ jsx("span", { className: `rounded-full bg-muted px-1.5 tabular-nums text-muted-foreground ${embedded ? "text-[9px]" : "text-[10px]"}`, children: count }) : null
29528
- ] }, tab.id);
29529
- })
29530
- ] }),
29531
- /* @__PURE__ */ jsxs(TabsContent, { value: "overview", className: embedded ? "mt-2" : "mt-4", children: [
29532
- entityDef.overviewComponent ? React133__default.createElement(entityDef.overviewComponent, { item, entityDef }) : /* @__PURE__ */ jsx(
29533
- OverviewTab,
29534
- {
29649
+ customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? (
29650
+ // `tabPath` only to the tab the URL is actually addressing —
29651
+ // handing it to every tab would have the documents tab trying to
29652
+ // open a recording id when the user switches across.
29653
+ React133__default.createElement(tab.component, {
29654
+ item,
29535
29655
  entityDef,
29536
- item: view,
29537
- compact: embedded,
29538
- onFieldSave: onFieldSave || onFieldsSave ? (key, value) => stageField(key, value) : void 0,
29539
- pendingKeys
29540
- }
29541
- ),
29542
- entityDef.layout === "person" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(AddressesTab, { item }) }),
29543
- entityDef.layout === "person" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(PayeeAccountsSection, { item }) }),
29544
- entityDef.layout === "location" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(AddressesTab, { item, ownerType: "unit", single: true }) }),
29545
- item?.id && (entityDef.relatedSections ?? []).map((related) => /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(RelatedSection, { parentId: item.id, related, compact: embedded }) }, relatedListTabId(related)))
29656
+ // A rota DESTE registro. Uma aba que lista eventos precisa
29657
+ // saber para onde apontar cada um (`#/clients/:id/:aba`), e
29658
+ // só esta página sabe em que caminho o app pendurou o
29659
+ // cadastro — a aba não tem como descobrir.
29660
+ recordPath: `${basePath}/${item.id}`,
29661
+ // Edição inline para quem a desenha (as abas das fichas). Uma
29662
+ // aba que não usa isto simplesmente ignora as duas chaves.
29663
+ onFieldSave: onFieldSave || onFieldsSave ? stageField : void 0,
29664
+ pendingKeys,
29665
+ ...tab.id === selectedTab ? { tabPath, setTabPath } : {},
29666
+ ...tab.props ?? {}
29667
+ })
29668
+ ) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.noContent") }) }) }) }, tab.id))
29546
29669
  ] }),
29547
- customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? (
29548
- // `tabPath` only to the tab the URL is actually addressing —
29549
- // handing it to every tab would have the documents tab trying to
29550
- // open a recording id when the user switches across.
29551
- React133__default.createElement(tab.component, {
29552
- item,
29553
- entityDef,
29554
- // A rota DESTE registro. Uma aba que lista eventos precisa
29555
- // saber para onde apontar cada um (`#/clients/:id/:aba`), e
29556
- // só esta página sabe em que caminho o app pendurou o
29557
- // cadastro — a aba não tem como descobrir.
29558
- recordPath: `${basePath}/${item.id}`,
29559
- // Edição inline para quem a desenha (as abas das fichas). Uma
29560
- // aba que não usa isto simplesmente ignora as duas chaves.
29561
- onFieldSave: onFieldSave || onFieldsSave ? stageField : void 0,
29562
- pendingKeys,
29563
- ...tab.id === selectedTab ? { tabPath, setTabPath } : {},
29564
- ...tab.props ?? {}
29565
- })
29566
- ) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.noContent") }) }) }) }, tab.id))
29567
- ] }),
29568
- /* @__PURE__ */ jsx(WidgetSlot, { zone: widgetZone }),
29569
- legacyWidgetZone !== widgetZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: legacyWidgetZone }),
29570
- /* @__PURE__ */ jsx(RecordMetaFooter, { item, compact: embedded })
29670
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: widgetZone }),
29671
+ legacyWidgetZone !== widgetZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: legacyWidgetZone }),
29672
+ /* @__PURE__ */ jsx(RecordMetaFooter, { item, compact: embedded })
29673
+ ] })
29571
29674
  ] })
29572
29675
  );
29573
29676
  }
@@ -33997,17 +34100,20 @@ function WorkspaceSwitcher({
33997
34100
  ) }),
33998
34101
  /* @__PURE__ */ jsxs(DropdownContent, { align: "start", className: "w-64", children: [
33999
34102
  /* @__PURE__ */ jsx(DropdownLabel, { children: tr("organization.workspaces", "Workspaces") }),
34000
- search.searchable && /* @__PURE__ */ jsx("div", { className: "px-2 pb-1.5 pt-1", children: /* @__PURE__ */ jsx(
34001
- "input",
34002
- {
34003
- value: query,
34004
- onChange: (e) => setQuery(e.target.value),
34005
- placeholder: tr("organization.searchWorkspace", "Buscar conta\u2026"),
34006
- autoFocus: true,
34007
- onKeyDown: (e) => e.stopPropagation(),
34008
- className: "w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-foreground outline-none placeholder:text-muted-foreground focus:border-primary"
34009
- }
34010
- ) }),
34103
+ search.searchable && /* @__PURE__ */ jsxs("div", { className: "relative px-2 pb-1 pt-0.5", children: [
34104
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
34105
+ /* @__PURE__ */ jsx(
34106
+ "input",
34107
+ {
34108
+ value: query,
34109
+ onChange: (e) => setQuery(e.target.value),
34110
+ placeholder: tr("organization.searchWorkspace", "Buscar conta\u2026"),
34111
+ autoFocus: true,
34112
+ onKeyDown: (e) => e.stopPropagation(),
34113
+ className: "h-8 w-full border-0 bg-transparent pl-7 pr-2 text-[13px] text-foreground outline-none placeholder:text-muted-foreground focus:ring-0"
34114
+ }
34115
+ )
34116
+ ] }),
34011
34117
  /* @__PURE__ */ jsx(DropdownSeparator, {}),
34012
34118
  search.visible.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: tr("organization.noWorkspaceMatch", "Nenhuma conta com esse nome.") }),
34013
34119
  search.visible.map((m) => /* @__PURE__ */ jsxs(
@@ -34843,6 +34949,18 @@ function AdminShellInner({ appName, brandedByTenant = false, layout = "sidebar",
34843
34949
  return { onBack: () => navigateTo(parent), label: label3 };
34844
34950
  }, [match, navigation, routes, tr, settingsTabDef]);
34845
34951
  const routeDecision = match?.route.permission && !navWaiting ? access.can(match.route.permission.feature, match.route.permission.action) : { allowed: true };
34952
+ const tenantIdForRoute = activeTenantId;
34953
+ const lastTenantRef = React133.useRef(tenantIdForRoute);
34954
+ const switchedTenantRef = React133.useRef(false);
34955
+ if (lastTenantRef.current !== tenantIdForRoute) {
34956
+ if (lastTenantRef.current && tenantIdForRoute) switchedTenantRef.current = true;
34957
+ lastTenantRef.current = tenantIdForRoute;
34958
+ }
34959
+ React133.useEffect(() => {
34960
+ if (!switchedTenantRef.current || navWaiting || navigation.length === 0) return;
34961
+ switchedTenantRef.current = false;
34962
+ if ((!match || !routeDecision.allowed) && path !== homeRoute) navigateTo(homeRoute);
34963
+ }, [tenantIdForRoute, navWaiting, navigation.length, match, routeDecision.allowed, path, homeRoute]);
34846
34964
  const activeContent = navWaiting ? (
34847
34965
  // O conteúdo espera pela MESMA condição que o rail.
34848
34966
  //
@@ -36256,7 +36374,7 @@ var TONE_CLASS = {
36256
36374
  info: "bg-sky-100 text-sky-800 dark:bg-sky-900/40 dark:text-sky-300",
36257
36375
  muted: "bg-muted text-muted-foreground"
36258
36376
  };
36259
- function Badge26({ badge, className = "" }) {
36377
+ function Badge27({ badge, className = "" }) {
36260
36378
  return (
36261
36379
  // shrink-0: in the crumb it sits beside a truncating title, and without it
36262
36380
  // a long record name eats the status instead of ellipsing itself.
@@ -36334,7 +36452,7 @@ function OrderDetailView(props) {
36334
36452
  const hasBadges = badges.length > 0 || !!props.badgesNode;
36335
36453
  const titleNode = hasBadges ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
36336
36454
  /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: title }),
36337
- props.badgesNode ?? badges.map((b, i) => /* @__PURE__ */ jsx(Badge26, { badge: b, className: i > 0 ? "hidden sm:inline-flex" : "" }, i))
36455
+ props.badgesNode ?? badges.map((b, i) => /* @__PURE__ */ jsx(Badge27, { badge: b, className: i > 0 ? "hidden sm:inline-flex" : "" }, i))
36338
36456
  ] }) : title;
36339
36457
  return (
36340
36458
  // A coluna decide o layout, não a janela: com o assistente aberto ao lado,
@@ -36432,7 +36550,7 @@ function OrderDetailView(props) {
36432
36550
  !receivable || receivable.rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: receivable?.emptyLabel ?? "Sem conta a receber." }) : /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
36433
36551
  (receivable.referenceNumber || receivable.statusBadge) && /* @__PURE__ */ jsxs("div", { className: "mb-2 flex flex-wrap items-center gap-2", children: [
36434
36552
  receivable.referenceNumber && /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: receivable.referenceNumber }),
36435
- receivable.statusBadge && /* @__PURE__ */ jsx(Badge26, { badge: receivable.statusBadge })
36553
+ receivable.statusBadge && /* @__PURE__ */ jsx(Badge27, { badge: receivable.statusBadge })
36436
36554
  ] }),
36437
36555
  receivable.rows.map((r, i) => /* @__PURE__ */ jsx(OrderDetailRow, { label: r.label, value: r.value, strong: r.strong, muted: r.muted }, i)),
36438
36556
  /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-1.5 border-t pt-2", children: receivable.installments.map((inst) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -38582,7 +38700,7 @@ var ORDER_CHANNEL_LABELS = {
38582
38700
  retirada: "Retirada",
38583
38701
  ticketing: "Bilheteria"
38584
38702
  };
38585
- function Badge28({ children }) {
38703
+ function Badge29({ children }) {
38586
38704
  return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[11px] font-medium", children });
38587
38705
  }
38588
38706
  function orderKindColumn(kindOf, header = "Tipo") {
@@ -38592,7 +38710,7 @@ function orderKindColumn(kindOf, header = "Tipo") {
38592
38710
  render: (row) => {
38593
38711
  const kind = kindOf(row);
38594
38712
  if (!kind) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
38595
- return /* @__PURE__ */ jsx(Badge28, { children: ORDER_KIND_LABELS[kind] ?? kind });
38713
+ return /* @__PURE__ */ jsx(Badge29, { children: ORDER_KIND_LABELS[kind] ?? kind });
38596
38714
  }
38597
38715
  };
38598
38716
  }
@@ -38603,7 +38721,7 @@ function orderChannelColumn(channelOf, header = "Canal") {
38603
38721
  render: (row) => {
38604
38722
  const channel = channelOf(row);
38605
38723
  if (!channel) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
38606
- return /* @__PURE__ */ jsx(Badge28, { children: ORDER_CHANNEL_LABELS[channel] ?? channel });
38724
+ return /* @__PURE__ */ jsx(Badge29, { children: ORDER_CHANNEL_LABELS[channel] ?? channel });
38607
38725
  }
38608
38726
  };
38609
38727
  }