@fayz-ai/admin 0.44.1 → 0.46.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';
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';
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
+ 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, isAddressIncomplete, isBankIncomplete, formatPostalCode, isValidPostalCode, lookupPostalCode, usePluginRuntime, useConnectivity, RECONNECTED_EVENT, installGlobalErrorHandlers, registerErrorReporter, getConnectivity, applyNavComposition, mergeTranslations, coreTranslations, attachPersistentCache, getPersistentCache, createMockProvider, listConnections, connectorPlanes, connectorPermissions, connectorEntities, preferredPlane, connectorDirection, countByTenant, 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';
@@ -7622,12 +7622,13 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice }) {
7622
7622
  "div",
7623
7623
  {
7624
7624
  className: cn(
7625
- "absolute bottom-0 right-0 flex items-center overflow-hidden rounded-full shadow-lg",
7625
+ "absolute bottom-0 right-0 flex items-center overflow-hidden shadow-lg",
7626
7626
  // Same fill as the FAB: the pill is the FAB stretching out, not a
7627
7627
  // second black object parked next to a purple one. Reserving
7628
7628
  // exactly the circle's width also closes the dead gap the dismiss
7629
- // button used to float in.
7630
- "bg-primary text-primary-foreground"
7629
+ // button used to float in. Over a mascot there is no circle to
7630
+ // stretch, so it speaks in the greeting's bubble instead.
7631
+ hasFace ? "rounded-2xl rounded-br-md border border-border bg-popover text-popover-foreground" : "rounded-full bg-primary text-primary-foreground"
7631
7632
  ),
7632
7633
  style: {
7633
7634
  ...pillSeat,
@@ -22345,6 +22346,84 @@ function RecordMetaFooter({
22345
22346
  }
22346
22347
  );
22347
22348
  }
22349
+ function resolveTabIcon(name) {
22350
+ const icon = LucideIcons[name];
22351
+ return typeof icon === "function" || typeof icon === "object" ? icon ?? null : null;
22352
+ }
22353
+ function statusLabel(entityDef, value) {
22354
+ const options = entityDef.fields.find((f) => f.key === "status")?.options;
22355
+ if (Array.isArray(options)) {
22356
+ for (const option of options) {
22357
+ if (typeof option === "string") {
22358
+ if (option === value) return option;
22359
+ } else if (option.value === value) {
22360
+ return option.label;
22361
+ }
22362
+ }
22363
+ }
22364
+ return typeof value === "string" ? value.charAt(0).toUpperCase() + value.slice(1) : value;
22365
+ }
22366
+ function RecordHeader({ entityDef, item, title, parentLabel, onBack, badges, actions }) {
22367
+ const t = useTranslation();
22368
+ const myPersonId = useMyPersonId(entityDef.layout === "person");
22369
+ const isMyRecord = Boolean(myPersonId) && myPersonId === item.id;
22370
+ const imageValue = entityDef.image?.get(item) ?? (entityDef.imageField ? item[entityDef.imageField] : null);
22371
+ const customAvatar = entityDef.renderAvatar ? entityDef.renderAvatar(item) : null;
22372
+ const subtitle = entityDef.subtitleField ? item[entityDef.subtitleField] : null;
22373
+ const activeValue = item.isActive ?? item.is_active;
22374
+ const showStatus = Boolean(item.status) && entityDef.fields.some((f) => f.key === "status");
22375
+ const hasChips = isMyRecord || showStatus || activeValue !== void 0 || Boolean(badges);
22376
+ const chips = hasChips ? /* @__PURE__ */ jsxs(Fragment, { children: [
22377
+ isMyRecord && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: t("common.you") }),
22378
+ showStatus && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: statusLabel(entityDef, item.status) }),
22379
+ activeValue !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: activeValue ? "success" : "destructive", className: "shrink-0", children: activeValue ? t("common.active") : t("common.inactive") }),
22380
+ badges
22381
+ ] }) : null;
22382
+ const thumb = !customAvatar && imageValue ? /* @__PURE__ */ jsx("img", { src: imageValue, alt: "", className: "mr-0.5 h-6 w-6 rounded-md object-cover" }) : void 0;
22383
+ const trailing = subtitle || chips ? /* @__PURE__ */ jsxs(Fragment, { children: [
22384
+ subtitle && /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: subtitle }),
22385
+ chips
22386
+ ] }) : void 0;
22387
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22388
+ /* @__PURE__ */ jsx(
22389
+ SubpageHeader,
22390
+ {
22391
+ title,
22392
+ parentLabel,
22393
+ onBack,
22394
+ actions,
22395
+ titleLeading: thumb,
22396
+ titleTrailing: trailing
22397
+ }
22398
+ ),
22399
+ customAvatar && /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center", children: customAvatar }),
22400
+ (thumb || trailing) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2 text-sm md:hidden", children: [
22401
+ thumb,
22402
+ trailing
22403
+ ] })
22404
+ ] });
22405
+ }
22406
+ function RecordTabStrip({ tabs, compact }) {
22407
+ const t = useTranslation();
22408
+ const trigger = `gap-1.5 ${compact ? "text-xs px-2 py-1" : ""}`;
22409
+ const iconSize = compact ? "h-3 w-3" : "h-3.5 w-3.5";
22410
+ return /* @__PURE__ */ jsxs(TabsList, { className: `max-w-full justify-start overflow-x-auto scrollbar-none${compact ? " h-8" : ""}`, children: [
22411
+ /* @__PURE__ */ jsxs(TabsTrigger, { value: "overview", className: trigger, children: [
22412
+ /* @__PURE__ */ jsx(Eye, { className: iconSize }),
22413
+ t("common.overview")
22414
+ ] }),
22415
+ tabs.map((tab) => {
22416
+ const translated = t(`crud.tabs.${tab.id}`);
22417
+ const label3 = translated.startsWith("crud.tabs.") ? tab.label : translated;
22418
+ const TabIcon = tab.icon ? resolveTabIcon(tab.icon) : null;
22419
+ return /* @__PURE__ */ jsxs(TabsTrigger, { value: tab.id, className: trigger, children: [
22420
+ TabIcon && /* @__PURE__ */ jsx(TabIcon, { className: iconSize }),
22421
+ label3,
22422
+ 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
22423
+ ] }, tab.id);
22424
+ })
22425
+ ] });
22426
+ }
22348
22427
 
22349
22428
  // src/crud/entity-zone.ts
22350
22429
  function entityZoneId(entityDef) {
@@ -23218,7 +23297,7 @@ function PersonFormLayout({ fields: allFields, fieldGroups, values, onChange, re
23218
23297
  ] }),
23219
23298
  recordId && /* @__PURE__ */ jsxs("div", { children: [
23220
23299
  /* @__PURE__ */ jsx(AddressesSection, { item: { id: recordId } }),
23221
- /* @__PURE__ */ jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsx(PayeeAccountsSection, { item: { id: recordId } }) })
23300
+ allFields.some((f) => f.type === "bank") && /* @__PURE__ */ jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsx(PayeeAccountsSection, { item: { id: recordId } }) })
23222
23301
  ] }),
23223
23302
  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
23303
  ] });
@@ -23277,6 +23356,7 @@ function CrudRecordModal({
23277
23356
  description,
23278
23357
  emptyLabel,
23279
23358
  essentialFirst,
23359
+ highlightMissing,
23280
23360
  notice
23281
23361
  }) {
23282
23362
  const [record, setRecord] = React133.useState(null);
@@ -23335,6 +23415,7 @@ function CrudRecordModal({
23335
23415
  embedded: true,
23336
23416
  hideActions: true,
23337
23417
  essentialFirst,
23418
+ highlightMissing,
23338
23419
  formId,
23339
23420
  entityDef,
23340
23421
  mode,
@@ -25428,7 +25509,7 @@ function CrudFormPage(props) {
25428
25509
  const { provider: provider2, flush } = usePendingChildren();
25429
25510
  return /* @__PURE__ */ jsx(PendingChildrenProvider, { value: provider2, children: /* @__PURE__ */ jsx(CrudFormPageBody, { ...props, flushPending: flush }) });
25430
25511
  }
25431
- function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onCancel, namePlural, embedded, hideBreadcrumb, formId, hideActions, essentialFirst, noForm, submitRef, flushPending }) {
25512
+ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onCancel, namePlural, embedded, hideBreadcrumb, onRecordTab, formId, hideActions, essentialFirst, highlightMissing, noForm, submitRef, flushPending }) {
25432
25513
  const t = useTranslation();
25433
25514
  const formFields = entityDef.fields.filter((f) => f.showInForm !== false);
25434
25515
  const displayField = entityDef.displayField ?? entityDef.fields[0]?.key ?? "id";
@@ -25442,6 +25523,7 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25442
25523
  const init = computeInit();
25443
25524
  setValues(init);
25444
25525
  initialRef.current = JSON.stringify(init);
25526
+ if (highlightMissing) setInvalidKeys(new Set(missingRequiredFields(formFields, init).map((f) => f.key)));
25445
25527
  }, [mode, initialData]);
25446
25528
  const computedKeys = new Set(formFields.filter((f) => f.type === "computed").map((f) => f.key));
25447
25529
  const limitKey = entityDef.limitKey;
@@ -25592,6 +25674,35 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25592
25674
  }
25593
25675
  }, [invalidKeys]);
25594
25676
  const groupsOfTab = (name) => groups.filter((g) => !sideGroupIds.has(g.id) && (g.tab ?? FIRST_TAB) === name);
25677
+ const recordItem = React133__default.useMemo(() => ({ ...initialData ?? {}, ...values }), [initialData, values]);
25678
+ const record = useRecordTabs(entityDef, mode === "edit" ? recordItem : null);
25679
+ const showRecordTabs = !embedded && mode === "edit" && Boolean(onRecordTab) && Boolean(initialData?.id);
25680
+ const formHoldsTabs = tabbed && tabNames.length > 1 && (!entityDef.layout || entityDef.layout === "location");
25681
+ const FORM_TAB = "form:";
25682
+ const stripTabs = [];
25683
+ if (showRecordTabs) {
25684
+ const sections = formHoldsTabs ? tabNames.filter((name) => name !== FIRST_TAB) : [];
25685
+ const placed = /* @__PURE__ */ new Set();
25686
+ let afterSections = 0;
25687
+ for (const tab of record.customTabs) {
25688
+ if (tab.id.startsWith("fields:")) {
25689
+ if (!sections.includes(tab.label)) continue;
25690
+ stripTabs.push({ id: FORM_TAB + tab.label, label: tab.label, icon: tab.icon });
25691
+ placed.add(tab.label);
25692
+ afterSections = stripTabs.length;
25693
+ } else {
25694
+ stripTabs.push({ id: tab.id, label: tab.label, icon: tab.icon, count: record.tabCounts[tab.id] });
25695
+ }
25696
+ }
25697
+ const missing = sections.filter((name) => !placed.has(name)).map((name) => ({ id: FORM_TAB + name, label: name }));
25698
+ stripTabs.splice(afterSections, 0, ...missing);
25699
+ }
25700
+ const stripValue = formHoldsTabs && currentTab !== FIRST_TAB ? FORM_TAB + currentTab : "overview";
25701
+ const onStripChange = (id) => {
25702
+ if (id === "overview") setActiveTab(FIRST_TAB);
25703
+ else if (id.startsWith(FORM_TAB)) setActiveTab(id.slice(FORM_TAB.length));
25704
+ else onRecordTab?.(id);
25705
+ };
25595
25706
  const archetypeLayoutProps = {
25596
25707
  fields: archetypeFields,
25597
25708
  allFields: entityDef.fields,
@@ -25653,7 +25764,7 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25653
25764
  if (tabbed && tabNames.length > 1) {
25654
25765
  return withSideRail(
25655
25766
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
25656
- /* @__PURE__ */ jsx(
25767
+ !showRecordTabs && /* @__PURE__ */ jsx(
25657
25768
  ViewTabs,
25658
25769
  {
25659
25770
  value: currentTab,
@@ -25677,18 +25788,32 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25677
25788
  }
25678
25789
  }
25679
25790
  const trailParent = usePageTrailParent();
25680
- const header = !embedded && !hideBreadcrumb ? /* @__PURE__ */ jsx(
25791
+ const cancelAction = /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onCancel, disabled: saving, children: t("common.cancel") });
25792
+ const headerBack = trailParent ? () => navigateToCrumb(trailParent) : onCancel;
25793
+ const header = embedded || hideBreadcrumb ? null : mode === "edit" ? /* @__PURE__ */ jsx(
25794
+ RecordHeader,
25795
+ {
25796
+ entityDef,
25797
+ item: recordItem,
25798
+ title: String(breadcrumbLabel),
25799
+ parentLabel: trailParent?.label ?? namePlural,
25800
+ onBack: headerBack,
25801
+ actions: cancelAction
25802
+ }
25803
+ ) : /* @__PURE__ */ jsx(
25681
25804
  SubpageHeader,
25682
25805
  {
25683
25806
  title: breadcrumbLabel,
25684
25807
  parentLabel: trailParent?.label ?? namePlural,
25685
- onBack: trailParent ? () => navigateToCrumb(trailParent) : onCancel
25808
+ onBack: headerBack,
25809
+ actions: cancelAction
25686
25810
  }
25687
- ) : null;
25811
+ );
25688
25812
  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(
25813
+ 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: [
25814
+ header && /* @__PURE__ */ jsx("div", { className: "w-full", children: header }),
25815
+ showRecordTabs && /* @__PURE__ */ jsx(Tabs, { value: stripValue, onValueChange: onStripChange, className: "mb-4 w-full", children: /* @__PURE__ */ jsx(RecordTabStrip, { tabs: stripTabs }) }),
25816
+ /* @__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
25817
  FieldsTag,
25693
25818
  {
25694
25819
  ...noForm ? {} : { id: formId, onSubmit: handleSubmit },
@@ -25735,31 +25860,27 @@ function CrudFormPageBody({ entityDef, mode, initialData, onSubmit, onSaved, onC
25735
25860
  ] })
25736
25861
  ]
25737
25862
  }
25738
- )
25739
- ] }) }) });
25863
+ ) })
25864
+ ] }) });
25865
+ }
25866
+ function TabStripSkeleton() {
25867
+ return /* @__PURE__ */ jsxs("div", { className: "inline-flex h-10 items-center gap-1 rounded-button bg-muted p-1", children: [
25868
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-28 rounded-button bg-background" }),
25869
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-16 mx-3 rounded bg-muted-foreground/15" }),
25870
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-20 mx-3 rounded bg-muted-foreground/15" }),
25871
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-14 mx-3 rounded bg-muted-foreground/15" })
25872
+ ] });
25740
25873
  }
25741
25874
  function CrudDetailSkeleton(_props = {}) {
25742
25875
  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
- ] }),
25876
+ /* @__PURE__ */ jsx(TabStripSkeleton, {}),
25756
25877
  /* @__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
25878
  /* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted" }),
25758
25879
  /* @__PURE__ */ jsx("div", { className: "col-span-2 h-4 w-32 rounded bg-muted" })
25759
25880
  ] }, i)) }) })
25760
25881
  ] });
25761
25882
  }
25762
- function CrudFormSkeleton({ groups = 2, twoColumn } = {}) {
25883
+ function CrudFormSkeleton({ groups = 2, twoColumn, tabs } = {}) {
25763
25884
  const section = (key, rows) => /* @__PURE__ */ jsxs(Card, { className: "p-5", children: [
25764
25885
  /* @__PURE__ */ jsx("div", { className: "mb-4 h-4 w-32 rounded bg-muted" }),
25765
25886
  /* @__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 +25889,13 @@ function CrudFormSkeleton({ groups = 2, twoColumn } = {}) {
25768
25889
  ] }, i)) })
25769
25890
  ] }, key);
25770
25891
  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 }) });
25892
+ return /* @__PURE__ */ jsxs("div", { className: "w-full animate-pulse", children: [
25893
+ tabs && /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx(TabStripSkeleton, {}) }),
25894
+ /* @__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: [
25895
+ /* @__PURE__ */ jsx("div", { children: main }),
25896
+ /* @__PURE__ */ jsx("div", { className: "space-y-5", children: section(99, 2) })
25897
+ ] }) : main })
25898
+ ] });
25775
25899
  }
25776
25900
  function formatValue2(field, value) {
25777
25901
  if (value == null || value === "") return null;
@@ -27027,7 +27151,7 @@ function CrudPage({ entityDef: rawEntityDef, useStore: useStore2, basePath, disp
27027
27151
  const item = store.getById(id);
27028
27152
  viewKey = `edit-${id}`;
27029
27153
  if (!item && store.items === null) {
27030
- content = /* @__PURE__ */ jsx(CrudFormSkeleton, { twoColumn: entityDef.formColumns === 2 });
27154
+ content = /* @__PURE__ */ jsx(CrudFormSkeleton, { twoColumn: entityDef.formColumns === 2, tabs: true });
27031
27155
  } else if (!item) {
27032
27156
  content = /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
27033
27157
  /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-1.5 text-sm text-muted-foreground", children: [
@@ -27052,6 +27176,9 @@ function CrudPage({ entityDef: rawEntityDef, useStore: useStore2, basePath, disp
27052
27176
  onCancel: () => {
27053
27177
  setCrudHash(normalizedBasePath, id);
27054
27178
  },
27179
+ onRecordTab: (tab) => {
27180
+ setCrudHash(normalizedBasePath, id, tab);
27181
+ },
27055
27182
  onSubmit: async (data) => {
27056
27183
  await store.update(id, data);
27057
27184
  setCrudHash(normalizedBasePath, id);
@@ -29214,22 +29341,52 @@ function resolveActiveTab(initialTab, tabs) {
29214
29341
  const alias = tabs.find((tab) => tab.aliases?.includes(normalized));
29215
29342
  return alias?.id ?? "overview";
29216
29343
  }
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
- }
29344
+ function useRecordTabs(entityDef, item) {
29345
+ const runtime = usePluginRuntimeOptional();
29346
+ const archetypeTabs = getArchetypeTabs(entityDef.layout, entityDef.data?.archetypeKind);
29347
+ const relatedTabs = (entityDef.relatedLists ?? []).map((related) => ({
29348
+ id: relatedListTabId(related),
29349
+ label: related.label ?? related.entity.namePlural ?? related.entity.name,
29350
+ icon: related.icon ?? related.entity.icon,
29351
+ component: (props) => /* @__PURE__ */ jsx(RelatedListTab, { ...props, related })
29352
+ }));
29353
+ const unitMode = useUnitMode();
29354
+ const fieldGroupTabs = React133__default.useMemo(() => {
29355
+ const groups = entityDef.fieldGroups ?? [];
29356
+ if (!groups.some((g) => g.tab) || !item) return [];
29357
+ const applicable = new Set(visibleDetailFields(entityDef, item).map((f) => f.group));
29358
+ const byTab = /* @__PURE__ */ new Map();
29359
+ const iconOf = /* @__PURE__ */ new Map();
29360
+ for (const g of groups) {
29361
+ if (!g.tab || !applicable.has(g.id)) continue;
29362
+ byTab.set(g.tab, [...byTab.get(g.tab) ?? [], g.id]);
29363
+ if (!iconOf.has(g.tab)) iconOf.set(g.tab, g.tabIcon);
29230
29364
  }
29231
- }
29232
- return typeof value === "string" ? value.charAt(0).toUpperCase() + value.slice(1) : value;
29365
+ return [...byTab.entries()].map(([label3, groupIds]) => ({
29366
+ id: `fields:${groupIds[0]}`,
29367
+ label: label3,
29368
+ icon: iconOf.get(label3),
29369
+ component: (props) => /* @__PURE__ */ jsx(
29370
+ FieldGroupTab,
29371
+ {
29372
+ entityDef: props.entityDef,
29373
+ item: props.item,
29374
+ groupIds,
29375
+ onFieldSave: props.onFieldSave,
29376
+ pendingKeys: props.pendingKeys
29377
+ }
29378
+ )
29379
+ }));
29380
+ }, [entityDef, item]);
29381
+ const customTabs = filterRuntimeTabs(
29382
+ mergeDetailTabs(archetypeTabs, [...fieldGroupTabs, ...relatedTabs, ...entityDef.detailTabs ?? []]),
29383
+ {
29384
+ hasWidget: (zone) => runtime ? getWidgetsForZone(runtime, zone).length > 0 : false,
29385
+ unitMode
29386
+ }
29387
+ );
29388
+ const tabCounts = usePersonTabCounts(entityDef.layout === "person" ? item?.id : void 0);
29389
+ return { customTabs, tabCounts };
29233
29390
  }
29234
29391
  function CrudDetailPage({
29235
29392
  entityDef,
@@ -29305,50 +29462,7 @@ function CrudDetailPage({
29305
29462
  const nameEditable = Boolean(onFieldSave || onFieldsSave) && Boolean(displayFieldDef) && isInlineEditable(displayFieldDef);
29306
29463
  const [editingName, setEditingName] = useState(false);
29307
29464
  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);
29465
+ const { customTabs, tabCounts } = useRecordTabs(entityDef, item);
29352
29466
  const entityId = entityZoneId(entityDef);
29353
29467
  const legacyEntityId = legacyEntityZoneId(entityDef);
29354
29468
  const widgetZone = `${entityId}.detail.tabs`;
@@ -29398,176 +29512,169 @@ function CrudDetailPage({
29398
29512
  }, [basePath, item.id, selectedTab, embedded]);
29399
29513
  const parentLabel = usePresetLabel(basePath, namePlural);
29400
29514
  const headerNode = React133__default.useMemo(
29401
- () => embedded ? null : /* @__PURE__ */ jsx(SubpageHeader, { title: displayValue, parentLabel, onBack }),
29402
- [embedded, displayValue, parentLabel, onBack]
29515
+ () => embedded ? null : /* @__PURE__ */ jsx(RecordHeader, { entityDef, item: view, title: String(displayValue), parentLabel, onBack, badges }),
29516
+ [embedded, entityDef, view, displayValue, parentLabel, onBack, badges]
29403
29517
  );
29518
+ const actionsNode = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(ActionSlot, { embedded, children: [
29519
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: headerActionsZone, item, entityId, embedded }),
29520
+ /* @__PURE__ */ jsx(
29521
+ PageActions,
29522
+ {
29523
+ actions: [
29524
+ ...onEdit ? [{ id: "edit", label: t("common.edit"), icon: Edit, intent: "primary", onSelect: onEdit }] : [],
29525
+ ...extraActions ?? [],
29526
+ ...declared,
29527
+ // Plugin contributions join the set instead of standing beside it,
29528
+ // so they fold into the caret and the phone's menu like the rest.
29529
+ ...contributed,
29530
+ ...onDelete ? [{ id: "delete", label: t("common.delete"), icon: Trash2, intent: "danger", onSelect: onDelete }] : []
29531
+ ]
29532
+ }
29533
+ )
29534
+ ] }) });
29404
29535
  return (
29405
29536
  // A largura que importa é a desta coluna — o assistente ocupa metade da
29406
29537
  // tela e o `lg:` da janela nunca soube disso.
29407
- /* @__PURE__ */ jsxs("div", { className: `fz-container ${embedded ? "space-y-4" : "space-y-6"}`, children: [
29538
+ /* @__PURE__ */ jsxs(Fragment, { children: [
29408
29539
  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",
29540
+ /* @__PURE__ */ jsxs("div", { className: `fz-container ${embedded ? "space-y-4" : "space-y-6"}`, children: [
29541
+ embedded && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
29542
+ /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
29543
+ entityDef.renderAvatar ? entityDef.renderAvatar(item) : imageValue ? /* @__PURE__ */ jsx(
29544
+ "img",
29422
29545
  {
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" })
29546
+ src: imageValue,
29547
+ alt: displayValue,
29548
+ className: `shrink-0 object-cover shadow-sm ${embedded ? "h-10 w-10 rounded-xl" : "h-20 w-20 rounded-2xl"}`
29428
29549
  }
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 = "";
29550
+ ) : /* @__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 }),
29551
+ onImageChange && /* @__PURE__ */ jsxs(Fragment, { children: [
29552
+ /* @__PURE__ */ jsx(
29553
+ "button",
29554
+ {
29555
+ type: "button",
29556
+ onClick: () => imageInputRef.current?.click(),
29557
+ "aria-label": t("common.edit"),
29558
+ 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"}`,
29559
+ children: /* @__PURE__ */ jsx(Camera, { className: embedded ? "h-3 w-3" : "h-3.5 w-3.5" })
29560
+ }
29561
+ ),
29562
+ /* @__PURE__ */ jsx(
29563
+ "input",
29564
+ {
29565
+ ref: imageInputRef,
29566
+ type: "file",
29567
+ accept: "image/*",
29568
+ className: "hidden",
29569
+ onChange: (e) => {
29570
+ const file = e.target.files?.[0];
29571
+ if (file) void onImageChange(file);
29572
+ if (e.target) e.target.value = "";
29573
+ }
29441
29574
  }
29575
+ )
29576
+ ] })
29577
+ ] }),
29578
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
29579
+ /* @__PURE__ */ jsx("div", { className: "group flex items-center gap-2", children: editingName && nameEditable && displayFieldDef ? /* @__PURE__ */ jsx(
29580
+ InlineEditor,
29581
+ {
29582
+ field: displayFieldDef,
29583
+ value: displayValue,
29584
+ onCancel: () => setEditingName(false),
29585
+ onSave: async (next) => {
29586
+ stageField(displayField, next);
29587
+ },
29588
+ cancelLabel: t("common.cancel")
29442
29589
  }
29443
- )
29444
- ] })
29590
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
29591
+ /* @__PURE__ */ jsx("h1", { className: `font-bold text-foreground truncate ${embedded ? "text-base" : "text-2xl"}`, children: displayValue }),
29592
+ nameEditable && /* @__PURE__ */ jsx(
29593
+ "button",
29594
+ {
29595
+ type: "button",
29596
+ onClick: () => setEditingName(true),
29597
+ "aria-label": `${t("common.edit")} ${displayFieldDef?.label ?? ""}`,
29598
+ title: `${t("common.edit")} ${displayFieldDef?.label ?? ""}`,
29599
+ 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",
29600
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
29601
+ }
29602
+ ),
29603
+ isMyRecord && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: t("common.you") }),
29604
+ item.status && entityDef.fields.some((f) => f.key === "status") && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: statusLabel(entityDef, item.status) }),
29605
+ activeValue !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: activeValue ? "success" : "destructive", className: "shrink-0", children: activeValue ? t("common.active") : t("common.inactive") }),
29606
+ badges
29607
+ ] }) }),
29608
+ subtitleValue && /* @__PURE__ */ jsx("p", { className: `text-muted-foreground mt-0.5 ${embedded ? "text-xs" : ""}`, children: subtitleValue })
29609
+ ] }),
29610
+ actionsNode
29445
29611
  ] }),
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,
29612
+ !embedded && actionsNode,
29613
+ pendingAction && /* @__PURE__ */ jsx(
29614
+ ConfirmDialog,
29615
+ {
29616
+ open: true,
29617
+ title: pendingAction.label,
29618
+ description: pendingAction.confirm,
29619
+ confirmLabel: pendingAction.label,
29620
+ cancelLabel: t("common.cancel"),
29621
+ variant: pendingAction.intent === "danger" ? "destructive" : "default",
29622
+ loading: runningAction === pendingAction.id,
29623
+ onConfirm: () => executeAction(pendingAction),
29624
+ onCancel: () => setPendingAction(null)
29625
+ }
29626
+ ),
29627
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: aboveTabsZone, item, entityId, embedded }),
29628
+ archetypeAboveTabsZone && archetypeAboveTabsZone !== aboveTabsZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: archetypeAboveTabsZone, item, entityId, embedded }),
29629
+ /* @__PURE__ */ jsxs(Tabs, { value: selectedTab, onValueChange: handleTabChange, children: [
29630
+ /* @__PURE__ */ jsx(
29631
+ RecordTabStrip,
29449
29632
  {
29450
- field: displayFieldDef,
29451
- value: displayValue,
29452
- onCancel: () => setEditingName(false),
29453
- onSave: async (next) => {
29454
- stageField(displayField, next);
29455
- },
29456
- cancelLabel: t("common.cancel")
29633
+ compact: embedded,
29634
+ tabs: customTabs.map((tab) => ({ id: tab.id, label: tab.label, icon: tab.icon, count: tabCounts[tab.id] }))
29457
29635
  }
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",
29636
+ ),
29637
+ /* @__PURE__ */ jsxs(TabsContent, { value: "overview", className: embedded ? "mt-2" : "mt-4", children: [
29638
+ entityDef.overviewComponent ? React133__default.createElement(entityDef.overviewComponent, { item, entityDef }) : /* @__PURE__ */ jsx(
29639
+ OverviewTab,
29462
29640
  {
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" })
29641
+ entityDef,
29642
+ item: view,
29643
+ compact: embedded,
29644
+ onFieldSave: onFieldSave || onFieldsSave ? (key, value) => stageField(key, value) : void 0,
29645
+ pendingKeys
29469
29646
  }
29470
29647
  ),
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")
29648
+ entityDef.layout === "person" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(AddressesTab, { item }) }),
29649
+ 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 }) }),
29650
+ entityDef.layout === "location" && item?.id && /* @__PURE__ */ jsx("div", { className: embedded ? "mt-3" : "mt-5", children: /* @__PURE__ */ jsx(AddressesTab, { item, ownerType: "unit", single: true }) }),
29651
+ 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
29652
  ] }),
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
- {
29653
+ customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? (
29654
+ // `tabPath` only to the tab the URL is actually addressing —
29655
+ // handing it to every tab would have the documents tab trying to
29656
+ // open a recording id when the user switches across.
29657
+ React133__default.createElement(tab.component, {
29658
+ item,
29535
29659
  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)))
29660
+ // A rota DESTE registro. Uma aba que lista eventos precisa
29661
+ // saber para onde apontar cada um (`#/clients/:id/:aba`), e
29662
+ // só esta página sabe em que caminho o app pendurou o
29663
+ // cadastro — a aba não tem como descobrir.
29664
+ recordPath: `${basePath}/${item.id}`,
29665
+ // Edição inline para quem a desenha (as abas das fichas). Uma
29666
+ // aba que não usa isto simplesmente ignora as duas chaves.
29667
+ onFieldSave: onFieldSave || onFieldsSave ? stageField : void 0,
29668
+ pendingKeys,
29669
+ ...tab.id === selectedTab ? { tabPath, setTabPath } : {},
29670
+ ...tab.props ?? {}
29671
+ })
29672
+ ) : /* @__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
29673
  ] }),
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 })
29674
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: widgetZone }),
29675
+ legacyWidgetZone !== widgetZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: legacyWidgetZone }),
29676
+ /* @__PURE__ */ jsx(RecordMetaFooter, { item, compact: embedded })
29677
+ ] })
29571
29678
  ] })
29572
29679
  );
29573
29680
  }
@@ -34846,6 +34953,18 @@ function AdminShellInner({ appName, brandedByTenant = false, layout = "sidebar",
34846
34953
  return { onBack: () => navigateTo(parent), label: label3 };
34847
34954
  }, [match, navigation, routes, tr, settingsTabDef]);
34848
34955
  const routeDecision = match?.route.permission && !navWaiting ? access.can(match.route.permission.feature, match.route.permission.action) : { allowed: true };
34956
+ const tenantIdForRoute = activeTenantId;
34957
+ const lastTenantRef = React133.useRef(tenantIdForRoute);
34958
+ const switchedTenantRef = React133.useRef(false);
34959
+ if (lastTenantRef.current !== tenantIdForRoute) {
34960
+ if (lastTenantRef.current && tenantIdForRoute) switchedTenantRef.current = true;
34961
+ lastTenantRef.current = tenantIdForRoute;
34962
+ }
34963
+ React133.useEffect(() => {
34964
+ if (!switchedTenantRef.current || navWaiting || navigation.length === 0) return;
34965
+ switchedTenantRef.current = false;
34966
+ if ((!match || !routeDecision.allowed) && path !== homeRoute) navigateTo(homeRoute);
34967
+ }, [tenantIdForRoute, navWaiting, navigation.length, match, routeDecision.allowed, path, homeRoute]);
34849
34968
  const activeContent = navWaiting ? (
34850
34969
  // O conteúdo espera pela MESMA condição que o rail.
34851
34970
  //
@@ -36259,7 +36378,7 @@ var TONE_CLASS = {
36259
36378
  info: "bg-sky-100 text-sky-800 dark:bg-sky-900/40 dark:text-sky-300",
36260
36379
  muted: "bg-muted text-muted-foreground"
36261
36380
  };
36262
- function Badge26({ badge, className = "" }) {
36381
+ function Badge27({ badge, className = "" }) {
36263
36382
  return (
36264
36383
  // shrink-0: in the crumb it sits beside a truncating title, and without it
36265
36384
  // a long record name eats the status instead of ellipsing itself.
@@ -36337,7 +36456,7 @@ function OrderDetailView(props) {
36337
36456
  const hasBadges = badges.length > 0 || !!props.badgesNode;
36338
36457
  const titleNode = hasBadges ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
36339
36458
  /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: title }),
36340
- props.badgesNode ?? badges.map((b, i) => /* @__PURE__ */ jsx(Badge26, { badge: b, className: i > 0 ? "hidden sm:inline-flex" : "" }, i))
36459
+ props.badgesNode ?? badges.map((b, i) => /* @__PURE__ */ jsx(Badge27, { badge: b, className: i > 0 ? "hidden sm:inline-flex" : "" }, i))
36341
36460
  ] }) : title;
36342
36461
  return (
36343
36462
  // A coluna decide o layout, não a janela: com o assistente aberto ao lado,
@@ -36435,7 +36554,7 @@ function OrderDetailView(props) {
36435
36554
  !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: [
36436
36555
  (receivable.referenceNumber || receivable.statusBadge) && /* @__PURE__ */ jsxs("div", { className: "mb-2 flex flex-wrap items-center gap-2", children: [
36437
36556
  receivable.referenceNumber && /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: receivable.referenceNumber }),
36438
- receivable.statusBadge && /* @__PURE__ */ jsx(Badge26, { badge: receivable.statusBadge })
36557
+ receivable.statusBadge && /* @__PURE__ */ jsx(Badge27, { badge: receivable.statusBadge })
36439
36558
  ] }),
36440
36559
  receivable.rows.map((r, i) => /* @__PURE__ */ jsx(OrderDetailRow, { label: r.label, value: r.value, strong: r.strong, muted: r.muted }, i)),
36441
36560
  /* @__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: [
@@ -38585,7 +38704,7 @@ var ORDER_CHANNEL_LABELS = {
38585
38704
  retirada: "Retirada",
38586
38705
  ticketing: "Bilheteria"
38587
38706
  };
38588
- function Badge28({ children }) {
38707
+ function Badge29({ children }) {
38589
38708
  return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[11px] font-medium", children });
38590
38709
  }
38591
38710
  function orderKindColumn(kindOf, header = "Tipo") {
@@ -38595,7 +38714,7 @@ function orderKindColumn(kindOf, header = "Tipo") {
38595
38714
  render: (row) => {
38596
38715
  const kind = kindOf(row);
38597
38716
  if (!kind) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
38598
- return /* @__PURE__ */ jsx(Badge28, { children: ORDER_KIND_LABELS[kind] ?? kind });
38717
+ return /* @__PURE__ */ jsx(Badge29, { children: ORDER_KIND_LABELS[kind] ?? kind });
38599
38718
  }
38600
38719
  };
38601
38720
  }
@@ -38606,7 +38725,7 @@ function orderChannelColumn(channelOf, header = "Canal") {
38606
38725
  render: (row) => {
38607
38726
  const channel = channelOf(row);
38608
38727
  if (!channel) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
38609
- return /* @__PURE__ */ jsx(Badge28, { children: ORDER_CHANNEL_LABELS[channel] ?? channel });
38728
+ return /* @__PURE__ */ jsx(Badge29, { children: ORDER_CHANNEL_LABELS[channel] ?? channel });
38610
38729
  }
38611
38730
  };
38612
38731
  }