@tleblancureta/proto 0.1.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.
Files changed (231) hide show
  1. package/core-web/src/ProtoApp.tsx +163 -0
  2. package/core-web/src/components/Shell.tsx +276 -0
  3. package/core-web/src/components/shell/EmptyState.tsx +33 -0
  4. package/core-web/src/components/shell/FocusView.tsx +55 -0
  5. package/core-web/src/components/shell/Toolbar.tsx +233 -0
  6. package/core-web/src/components/shell/persistence.ts +20 -0
  7. package/core-web/src/components/shell/types.ts +14 -0
  8. package/core-web/src/components/ui/avatar.tsx +18 -0
  9. package/core-web/src/components/ui/badge.tsx +28 -0
  10. package/core-web/src/components/ui/button.tsx +40 -0
  11. package/core-web/src/components/ui/card.tsx +32 -0
  12. package/core-web/src/components/ui/inline-edit.tsx +120 -0
  13. package/core-web/src/components/ui/input.tsx +18 -0
  14. package/core-web/src/components/ui/scroll-area.tsx +12 -0
  15. package/core-web/src/components/ui/separator.tsx +23 -0
  16. package/core-web/src/components/ui/shell-dialog.tsx +79 -0
  17. package/core-web/src/components/ui/skeleton.tsx +9 -0
  18. package/core-web/src/components/ui/textarea.tsx +17 -0
  19. package/core-web/src/components/widgets/agent/Generative.tsx +74 -0
  20. package/core-web/src/components/widgets/agent/Primitives.tsx +225 -0
  21. package/core-web/src/components/widgets/agent/actions.ts +52 -0
  22. package/core-web/src/hooks/useAuth.ts +80 -0
  23. package/core-web/src/hooks/useData.ts +44 -0
  24. package/core-web/src/hooks/useMountEffect.ts +10 -0
  25. package/core-web/src/hooks/useTheme.ts +37 -0
  26. package/core-web/src/index.ts +52 -0
  27. package/core-web/src/lib/api.ts +231 -0
  28. package/core-web/src/lib/config.ts +14 -0
  29. package/core-web/src/lib/define-widget.ts +71 -0
  30. package/core-web/src/lib/drag.ts +45 -0
  31. package/core-web/src/lib/supabase.ts +6 -0
  32. package/core-web/src/lib/utils.ts +6 -0
  33. package/core-web/src/lib/widgetCache.ts +29 -0
  34. package/core-web/src/vite-env.d.ts +1 -0
  35. package/dist/core-mcp/src/app.d.ts +40 -0
  36. package/dist/core-mcp/src/app.d.ts.map +1 -0
  37. package/dist/core-mcp/src/app.js +141 -0
  38. package/dist/core-mcp/src/app.js.map +1 -0
  39. package/dist/core-mcp/src/define-tool.d.ts +70 -0
  40. package/dist/core-mcp/src/define-tool.d.ts.map +1 -0
  41. package/dist/core-mcp/src/define-tool.js +38 -0
  42. package/dist/core-mcp/src/define-tool.js.map +1 -0
  43. package/dist/core-mcp/src/entity-tools.d.ts +27 -0
  44. package/dist/core-mcp/src/entity-tools.d.ts.map +1 -0
  45. package/dist/core-mcp/src/entity-tools.js +99 -0
  46. package/dist/core-mcp/src/entity-tools.js.map +1 -0
  47. package/dist/core-mcp/src/index.d.ts +36 -0
  48. package/dist/core-mcp/src/index.d.ts.map +1 -0
  49. package/dist/core-mcp/src/index.js +116 -0
  50. package/dist/core-mcp/src/index.js.map +1 -0
  51. package/dist/core-mcp/src/supabase.d.ts +7 -0
  52. package/dist/core-mcp/src/supabase.d.ts.map +1 -0
  53. package/dist/core-mcp/src/supabase.js +18 -0
  54. package/dist/core-mcp/src/supabase.js.map +1 -0
  55. package/dist/core-mcp/src/tools/_helpers.d.ts +44 -0
  56. package/dist/core-mcp/src/tools/_helpers.d.ts.map +1 -0
  57. package/dist/core-mcp/src/tools/_helpers.js +23 -0
  58. package/dist/core-mcp/src/tools/_helpers.js.map +1 -0
  59. package/dist/core-mcp/src/tools/ui.d.ts +9 -0
  60. package/dist/core-mcp/src/tools/ui.d.ts.map +1 -0
  61. package/dist/core-mcp/src/tools/ui.js +100 -0
  62. package/dist/core-mcp/src/tools/ui.js.map +1 -0
  63. package/dist/core-mcp/src/workflow-tools.d.ts +41 -0
  64. package/dist/core-mcp/src/workflow-tools.d.ts.map +1 -0
  65. package/dist/core-mcp/src/workflow-tools.js +382 -0
  66. package/dist/core-mcp/src/workflow-tools.js.map +1 -0
  67. package/dist/core-shared/src/define-entity.d.ts +73 -0
  68. package/dist/core-shared/src/define-entity.d.ts.map +1 -0
  69. package/dist/core-shared/src/define-entity.js +47 -0
  70. package/dist/core-shared/src/define-entity.js.map +1 -0
  71. package/dist/core-shared/src/define-workflow.d.ts +111 -0
  72. package/dist/core-shared/src/define-workflow.d.ts.map +1 -0
  73. package/dist/core-shared/src/define-workflow.js +92 -0
  74. package/dist/core-shared/src/define-workflow.js.map +1 -0
  75. package/dist/core-shared/src/index.d.ts +5 -0
  76. package/dist/core-shared/src/index.d.ts.map +1 -0
  77. package/dist/core-shared/src/index.js +7 -0
  78. package/dist/core-shared/src/index.js.map +1 -0
  79. package/dist/core-shared/src/scheduling.d.ts +69 -0
  80. package/dist/core-shared/src/scheduling.d.ts.map +1 -0
  81. package/dist/core-shared/src/scheduling.js +39 -0
  82. package/dist/core-shared/src/scheduling.js.map +1 -0
  83. package/dist/core-shared/src/schemas.d.ts +51 -0
  84. package/dist/core-shared/src/schemas.d.ts.map +1 -0
  85. package/dist/core-shared/src/schemas.js +18 -0
  86. package/dist/core-shared/src/schemas.js.map +1 -0
  87. package/dist/core-web/src/ProtoApp.d.ts +19 -0
  88. package/dist/core-web/src/ProtoApp.d.ts.map +1 -0
  89. package/dist/core-web/src/ProtoApp.js +92 -0
  90. package/dist/core-web/src/ProtoApp.js.map +1 -0
  91. package/dist/core-web/src/components/Shell.d.ts +46 -0
  92. package/dist/core-web/src/components/Shell.d.ts.map +1 -0
  93. package/dist/core-web/src/components/Shell.js +104 -0
  94. package/dist/core-web/src/components/Shell.js.map +1 -0
  95. package/dist/core-web/src/components/shell/EmptyState.d.ts +13 -0
  96. package/dist/core-web/src/components/shell/EmptyState.d.ts.map +1 -0
  97. package/dist/core-web/src/components/shell/EmptyState.js +7 -0
  98. package/dist/core-web/src/components/shell/EmptyState.js.map +1 -0
  99. package/dist/core-web/src/components/shell/FocusView.d.ts +16 -0
  100. package/dist/core-web/src/components/shell/FocusView.d.ts.map +1 -0
  101. package/dist/core-web/src/components/shell/FocusView.js +12 -0
  102. package/dist/core-web/src/components/shell/FocusView.js.map +1 -0
  103. package/dist/core-web/src/components/shell/Toolbar.d.ts +35 -0
  104. package/dist/core-web/src/components/shell/Toolbar.d.ts.map +1 -0
  105. package/dist/core-web/src/components/shell/Toolbar.js +42 -0
  106. package/dist/core-web/src/components/shell/Toolbar.js.map +1 -0
  107. package/dist/core-web/src/components/shell/persistence.d.ts +8 -0
  108. package/dist/core-web/src/components/shell/persistence.d.ts.map +1 -0
  109. package/dist/core-web/src/components/shell/persistence.js +20 -0
  110. package/dist/core-web/src/components/shell/persistence.js.map +1 -0
  111. package/dist/core-web/src/components/shell/types.d.ts +13 -0
  112. package/dist/core-web/src/components/shell/types.d.ts.map +1 -0
  113. package/dist/core-web/src/components/shell/types.js +2 -0
  114. package/dist/core-web/src/components/shell/types.js.map +1 -0
  115. package/dist/core-web/src/components/ui/avatar.d.ts +5 -0
  116. package/dist/core-web/src/components/ui/avatar.d.ts.map +1 -0
  117. package/dist/core-web/src/components/ui/avatar.js +9 -0
  118. package/dist/core-web/src/components/ui/avatar.js.map +1 -0
  119. package/dist/core-web/src/components/ui/badge.d.ts +13 -0
  120. package/dist/core-web/src/components/ui/badge.d.ts.map +1 -0
  121. package/dist/core-web/src/components/ui/badge.js +13 -0
  122. package/dist/core-web/src/components/ui/badge.js.map +1 -0
  123. package/dist/core-web/src/components/ui/button.d.ts +22 -0
  124. package/dist/core-web/src/components/ui/button.d.ts.map +1 -0
  125. package/dist/core-web/src/components/ui/button.js +21 -0
  126. package/dist/core-web/src/components/ui/button.js.map +1 -0
  127. package/dist/core-web/src/components/ui/card.d.ts +7 -0
  128. package/dist/core-web/src/components/ui/card.d.ts.map +1 -0
  129. package/dist/core-web/src/components/ui/card.js +13 -0
  130. package/dist/core-web/src/components/ui/card.js.map +1 -0
  131. package/dist/core-web/src/components/ui/inline-edit.d.ts +20 -0
  132. package/dist/core-web/src/components/ui/inline-edit.d.ts.map +1 -0
  133. package/dist/core-web/src/components/ui/inline-edit.js +63 -0
  134. package/dist/core-web/src/components/ui/inline-edit.js.map +1 -0
  135. package/dist/core-web/src/components/ui/input.d.ts +4 -0
  136. package/dist/core-web/src/components/ui/input.d.ts.map +1 -0
  137. package/dist/core-web/src/components/ui/input.js +7 -0
  138. package/dist/core-web/src/components/ui/input.js.map +1 -0
  139. package/dist/core-web/src/components/ui/scroll-area.d.ts +4 -0
  140. package/dist/core-web/src/components/ui/scroll-area.d.ts.map +1 -0
  141. package/dist/core-web/src/components/ui/scroll-area.js +7 -0
  142. package/dist/core-web/src/components/ui/scroll-area.js.map +1 -0
  143. package/dist/core-web/src/components/ui/separator.d.ts +7 -0
  144. package/dist/core-web/src/components/ui/separator.d.ts.map +1 -0
  145. package/dist/core-web/src/components/ui/separator.js +7 -0
  146. package/dist/core-web/src/components/ui/separator.js.map +1 -0
  147. package/dist/core-web/src/components/ui/shell-dialog.d.ts +16 -0
  148. package/dist/core-web/src/components/ui/shell-dialog.d.ts.map +1 -0
  149. package/dist/core-web/src/components/ui/shell-dialog.js +36 -0
  150. package/dist/core-web/src/components/ui/shell-dialog.js.map +1 -0
  151. package/dist/core-web/src/components/ui/skeleton.d.ts +3 -0
  152. package/dist/core-web/src/components/ui/skeleton.d.ts.map +1 -0
  153. package/dist/core-web/src/components/ui/skeleton.js +7 -0
  154. package/dist/core-web/src/components/ui/skeleton.js.map +1 -0
  155. package/dist/core-web/src/components/ui/textarea.d.ts +4 -0
  156. package/dist/core-web/src/components/ui/textarea.d.ts.map +1 -0
  157. package/dist/core-web/src/components/ui/textarea.js +7 -0
  158. package/dist/core-web/src/components/ui/textarea.js.map +1 -0
  159. package/dist/core-web/src/components/widgets/agent/Generative.d.ts +13 -0
  160. package/dist/core-web/src/components/widgets/agent/Generative.d.ts.map +1 -0
  161. package/dist/core-web/src/components/widgets/agent/Generative.js +42 -0
  162. package/dist/core-web/src/components/widgets/agent/Generative.js.map +1 -0
  163. package/dist/core-web/src/components/widgets/agent/Primitives.d.ts +79 -0
  164. package/dist/core-web/src/components/widgets/agent/Primitives.d.ts.map +1 -0
  165. package/dist/core-web/src/components/widgets/agent/Primitives.js +116 -0
  166. package/dist/core-web/src/components/widgets/agent/Primitives.js.map +1 -0
  167. package/dist/core-web/src/components/widgets/agent/actions.d.ts +3 -0
  168. package/dist/core-web/src/components/widgets/agent/actions.d.ts.map +1 -0
  169. package/dist/core-web/src/components/widgets/agent/actions.js +33 -0
  170. package/dist/core-web/src/components/widgets/agent/actions.js.map +1 -0
  171. package/dist/core-web/src/hooks/useAuth.d.ts +25 -0
  172. package/dist/core-web/src/hooks/useAuth.d.ts.map +1 -0
  173. package/dist/core-web/src/hooks/useAuth.js +53 -0
  174. package/dist/core-web/src/hooks/useAuth.js.map +1 -0
  175. package/dist/core-web/src/hooks/useData.d.ts +10 -0
  176. package/dist/core-web/src/hooks/useData.d.ts.map +1 -0
  177. package/dist/core-web/src/hooks/useData.js +37 -0
  178. package/dist/core-web/src/hooks/useData.js.map +1 -0
  179. package/dist/core-web/src/hooks/useMountEffect.d.ts +6 -0
  180. package/dist/core-web/src/hooks/useMountEffect.d.ts.map +1 -0
  181. package/dist/core-web/src/hooks/useMountEffect.js +10 -0
  182. package/dist/core-web/src/hooks/useMountEffect.js.map +1 -0
  183. package/dist/core-web/src/hooks/useTheme.d.ts +6 -0
  184. package/dist/core-web/src/hooks/useTheme.d.ts.map +1 -0
  185. package/dist/core-web/src/hooks/useTheme.js +31 -0
  186. package/dist/core-web/src/hooks/useTheme.js.map +1 -0
  187. package/dist/core-web/src/index.d.ts +33 -0
  188. package/dist/core-web/src/index.d.ts.map +1 -0
  189. package/dist/core-web/src/index.js +38 -0
  190. package/dist/core-web/src/index.js.map +1 -0
  191. package/dist/core-web/src/lib/api.d.ts +60 -0
  192. package/dist/core-web/src/lib/api.d.ts.map +1 -0
  193. package/dist/core-web/src/lib/api.js +204 -0
  194. package/dist/core-web/src/lib/api.js.map +1 -0
  195. package/dist/core-web/src/lib/config.d.ts +10 -0
  196. package/dist/core-web/src/lib/config.d.ts.map +1 -0
  197. package/dist/core-web/src/lib/config.js +10 -0
  198. package/dist/core-web/src/lib/config.js.map +1 -0
  199. package/dist/core-web/src/lib/define-widget.d.ts +52 -0
  200. package/dist/core-web/src/lib/define-widget.d.ts.map +1 -0
  201. package/dist/core-web/src/lib/define-widget.js +14 -0
  202. package/dist/core-web/src/lib/define-widget.js.map +1 -0
  203. package/dist/core-web/src/lib/drag.d.ts +20 -0
  204. package/dist/core-web/src/lib/drag.d.ts.map +1 -0
  205. package/dist/core-web/src/lib/drag.js +33 -0
  206. package/dist/core-web/src/lib/drag.js.map +1 -0
  207. package/dist/core-web/src/lib/supabase.d.ts +2 -0
  208. package/dist/core-web/src/lib/supabase.d.ts.map +1 -0
  209. package/dist/core-web/src/lib/supabase.js +5 -0
  210. package/dist/core-web/src/lib/supabase.js.map +1 -0
  211. package/dist/core-web/src/lib/utils.d.ts +3 -0
  212. package/dist/core-web/src/lib/utils.d.ts.map +1 -0
  213. package/dist/core-web/src/lib/utils.js +6 -0
  214. package/dist/core-web/src/lib/utils.js.map +1 -0
  215. package/dist/core-web/src/lib/widgetCache.d.ts +18 -0
  216. package/dist/core-web/src/lib/widgetCache.d.ts.map +1 -0
  217. package/dist/core-web/src/lib/widgetCache.js +28 -0
  218. package/dist/core-web/src/lib/widgetCache.js.map +1 -0
  219. package/dist/mcp.d.ts +2 -0
  220. package/dist/mcp.d.ts.map +1 -0
  221. package/dist/mcp.js +2 -0
  222. package/dist/mcp.js.map +1 -0
  223. package/dist/shared.d.ts +2 -0
  224. package/dist/shared.d.ts.map +1 -0
  225. package/dist/shared.js +2 -0
  226. package/dist/shared.js.map +1 -0
  227. package/dist/web.d.ts +2 -0
  228. package/dist/web.d.ts.map +1 -0
  229. package/dist/web.js +2 -0
  230. package/dist/web.js.map +1 -0
  231. package/package.json +62 -0
@@ -0,0 +1,233 @@
1
+ import { useState, useCallback, type ReactNode } from 'react'
2
+ import { useMountEffect } from '../../hooks/useMountEffect'
3
+ import { Button } from '../ui/button'
4
+ import { PlusIcon, RotateCcwIcon, SunIcon, MoonIcon, MonitorIcon, UserIcon, LogOutIcon, Building2Icon, ChevronDownIcon, CheckIcon, XIcon, HomeIcon, SettingsIcon, LayoutGridIcon } from 'lucide-react'
5
+ import { useTheme, type Theme } from '../../hooks/useTheme'
6
+ import type { ActiveEntity, WidgetType } from './types'
7
+
8
+ interface CatalogEntry {
9
+ type: WidgetType
10
+ title: string
11
+ icon: string
12
+ }
13
+
14
+ interface Props {
15
+ widgetCount: number
16
+ cockpitMode: boolean
17
+ activeEntity: ActiveEntity | null | undefined
18
+ onDeactivateEntity: (() => void) | undefined
19
+ onReset: () => void
20
+ onAddWidget: (type: WidgetType) => void
21
+ widgetCatalog: CatalogEntry[]
22
+ onOpenSettings?: () => void
23
+ editingLayout?: boolean
24
+ onToggleEditLayout?: () => void
25
+ openEntities?: ActiveEntity[]
26
+ onSelectEntity?: (e: ActiveEntity) => void
27
+ onCloseTab?: (e: ActiveEntity) => void
28
+ role?: string | null
29
+ companies?: Array<{ id: string; name: string }>
30
+ effectiveCompanyId?: string
31
+ setCompanyId?: (id: string) => void
32
+ onSignOut?: () => void
33
+ userEmail?: string
34
+ rightActions?: ReactNode
35
+ }
36
+
37
+ export function Toolbar({
38
+ widgetCount, cockpitMode, activeEntity, onDeactivateEntity, onReset, onAddWidget,
39
+ widgetCatalog, onOpenSettings, editingLayout, onToggleEditLayout,
40
+ openEntities, onSelectEntity, onCloseTab,
41
+ role, companies, effectiveCompanyId, setCompanyId, onSignOut, userEmail,
42
+ rightActions,
43
+ }: Props) {
44
+ type Dropdown = 'catalog' | 'profile' | 'company' | null
45
+ const [openDropdown, setOpenDropdown] = useState<Dropdown>(null)
46
+ const showCatalog = openDropdown === 'catalog'
47
+ const showProfile = openDropdown === 'profile'
48
+ const showCompany = openDropdown === 'company'
49
+ const toggleDropdown = (d: Dropdown) => setOpenDropdown(prev => prev === d ? null : d)
50
+
51
+ useMountEffect(() => {
52
+ const handler = (e: KeyboardEvent) => {
53
+ if (e.key === 'Escape') setOpenDropdown(null)
54
+ }
55
+ document.addEventListener('keydown', handler)
56
+ return () => document.removeEventListener('keydown', handler)
57
+ })
58
+ const currentCompany = companies?.find(c => c.id === effectiveCompanyId)
59
+
60
+ return (
61
+ <div className="sticky top-0 z-20 bg-background/80 backdrop-blur border-b border-border px-3 py-1.5 flex items-center justify-between gap-2">
62
+ <div className="flex items-center gap-1 min-w-0 flex-1">
63
+ <button
64
+ onClick={onDeactivateEntity}
65
+ className={`p-1.5 rounded-md transition-colors shrink-0 ${!cockpitMode ? 'bg-accent text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-accent'}`}
66
+ aria-label="Inicio"
67
+ title="Inicio"
68
+ >
69
+ <HomeIcon className="w-3.5 h-3.5" />
70
+ </button>
71
+ {openEntities && openEntities.length > 0 ? (
72
+ <div className="flex items-center gap-0.5 min-w-0 overflow-x-auto scrollbar-thin">
73
+ {openEntities.map(e => {
74
+ const isActive = !!(cockpitMode && activeEntity && activeEntity.type === e.type && activeEntity.id === e.id)
75
+ return (
76
+ <button
77
+ key={`${e.type}-${e.id}`}
78
+ role="tab"
79
+ aria-selected={isActive}
80
+ className={`group flex items-center gap-1.5 pl-2 pr-1 py-1 rounded-md border text-[11px] cursor-pointer transition-colors shrink-0 max-w-[200px] ${
81
+ isActive
82
+ ? 'bg-primary/10 border-primary/40 text-foreground'
83
+ : 'bg-background border-border text-muted-foreground hover:text-foreground hover:bg-accent/50'
84
+ }`}
85
+ onClick={() => onSelectEntity?.(e)}
86
+ >
87
+ <div className={`w-1.5 h-1.5 rounded-full shrink-0 ${isActive ? 'bg-primary animate-pulse' : 'bg-muted-foreground/40'}`} />
88
+ <span className="truncate">{e.label}</span>
89
+ <span
90
+ role="button"
91
+ tabIndex={0}
92
+ onClick={(ev) => { ev.stopPropagation(); onCloseTab?.(e) }}
93
+ onKeyDown={(ev) => { if (ev.key === 'Enter' || ev.key === ' ') { ev.stopPropagation(); onCloseTab?.(e) } }}
94
+ className={`p-0.5 rounded-full hover:text-foreground hover:bg-accent shrink-0 transition-opacity ${isActive ? 'text-muted-foreground/60' : 'text-muted-foreground/50 opacity-0 group-hover:opacity-100'}`}
95
+ aria-label={`Close ${e.label}`}
96
+ >
97
+ <XIcon className="w-3 h-3" />
98
+ </span>
99
+ </button>
100
+ )
101
+ })}
102
+ </div>
103
+ ) : (
104
+ <span className="text-xs text-muted-foreground pl-1">{widgetCount} widgets</span>
105
+ )}
106
+ </div>
107
+
108
+ <div className="flex items-center gap-1">
109
+ <Button variant="ghost" size="sm" className="h-7 text-xs gap-1" onClick={onReset}>
110
+ <RotateCcwIcon className="w-3 h-3" /> Reset
111
+ </Button>
112
+ {onToggleEditLayout && !cockpitMode && (
113
+ <Button
114
+ variant={editingLayout ? 'default' : 'ghost'}
115
+ size="sm"
116
+ className={`h-7 text-xs gap-1 ${editingLayout ? 'bg-primary text-primary-foreground' : ''}`}
117
+ onClick={onToggleEditLayout}
118
+ >
119
+ {editingLayout ? <><CheckIcon className="w-3 h-3" /> Listo</> : <><LayoutGridIcon className="w-3 h-3" /> Editar</>}
120
+ </Button>
121
+ )}
122
+ {role === 'admin' && companies && companies.length > 1 && setCompanyId && (
123
+ <div className="relative">
124
+ <Button
125
+ variant="ghost"
126
+ size="sm"
127
+ className="h-7 text-xs gap-1.5 max-w-[180px]"
128
+ onClick={() => toggleDropdown('company')}
129
+ >
130
+ <Building2Icon className="w-3 h-3 shrink-0" />
131
+ <span className="truncate">{currentCompany?.name || 'Empresa'}</span>
132
+ <ChevronDownIcon className="w-3 h-3 shrink-0 opacity-60" />
133
+ </Button>
134
+ {showCompany && (
135
+ <>
136
+ <div className="fixed inset-0 z-20" onClick={() => setOpenDropdown(null)} />
137
+ <div className="absolute right-0 top-8 bg-card border border-border rounded-lg shadow-lg p-1 z-30 w-60 max-h-80 overflow-y-auto scrollbar-thin">
138
+ <div className="px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground/60">Empresas</div>
139
+ {companies.map(c => {
140
+ const active = c.id === effectiveCompanyId
141
+ return (
142
+ <button
143
+ key={c.id}
144
+ onClick={() => { setCompanyId(c.id); setOpenDropdown(null) }}
145
+ className={`w-full text-left px-2 py-1.5 text-sm rounded hover:bg-accent transition-colors flex items-center gap-2 ${active ? 'bg-accent/50' : ''}`}
146
+ >
147
+ <Building2Icon className="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
148
+ <span className="flex-1 truncate">{c.name}</span>
149
+ {active && <CheckIcon className="w-3.5 h-3.5 text-primary shrink-0" />}
150
+ </button>
151
+ )
152
+ })}
153
+ </div>
154
+ </>
155
+ )}
156
+ </div>
157
+ )}
158
+ {rightActions}
159
+ <div className="relative">
160
+ <Button variant="ghost" size="sm" className="h-7 text-xs gap-1" onClick={() => toggleDropdown('catalog')}>
161
+ <PlusIcon className="w-3 h-3" /> Agregar
162
+ </Button>
163
+ {showCatalog && (
164
+ <>
165
+ <div className="fixed inset-0 z-20" onClick={() => setOpenDropdown(null)} />
166
+ <div className="absolute right-0 top-8 bg-card border border-border rounded-lg shadow-lg p-1 z-30 w-40">
167
+ {widgetCatalog.map(w => (
168
+ <button
169
+ key={w.type}
170
+ onClick={() => { onAddWidget(w.type); setOpenDropdown(null) }}
171
+ className="w-full text-left px-3 py-1.5 text-sm rounded hover:bg-accent transition-colors flex items-center gap-2"
172
+ >
173
+ <span>{w.icon}</span> {w.title}
174
+ </button>
175
+ ))}
176
+ </div>
177
+ </>
178
+ )}
179
+ </div>
180
+ {onSignOut && (
181
+ <div className="relative ml-1">
182
+ <Button variant="ghost" size="sm" className="h-7 w-7 p-0" onClick={() => toggleDropdown('profile')} aria-label="Perfil">
183
+ <UserIcon className="w-3.5 h-3.5" />
184
+ </Button>
185
+ {showProfile && (
186
+ <>
187
+ <div className="fixed inset-0 z-20" onClick={() => setOpenDropdown(null)} />
188
+ <div className="absolute right-0 top-8 bg-card border border-border rounded-lg shadow-lg p-1 z-30 w-48">
189
+ {userEmail && (
190
+ <div className="px-3 py-1.5 text-[11px] text-muted-foreground truncate border-b border-border/50 mb-1">{userEmail}</div>
191
+ )}
192
+ {onOpenSettings && (
193
+ <button
194
+ onClick={() => { setOpenDropdown(null); onOpenSettings() }}
195
+ className="w-full text-left px-3 py-1.5 text-sm rounded hover:bg-accent transition-colors flex items-center gap-2"
196
+ >
197
+ <SettingsIcon className="w-3.5 h-3.5" /> Configuracion
198
+ </button>
199
+ )}
200
+ <ThemeToggleRow />
201
+ <div className="border-t border-border/50 mt-1 pt-1">
202
+ <button
203
+ onClick={() => { setOpenDropdown(null); onSignOut() }}
204
+ className="w-full text-left px-3 py-1.5 text-sm rounded hover:bg-accent transition-colors flex items-center gap-2"
205
+ >
206
+ <LogOutIcon className="w-3.5 h-3.5" /> Salir
207
+ </button>
208
+ </div>
209
+ </div>
210
+ </>
211
+ )}
212
+ </div>
213
+ )}
214
+ </div>
215
+ </div>
216
+ )
217
+ }
218
+
219
+ function ThemeToggleRow() {
220
+ const { theme, setTheme } = useTheme()
221
+ const next: Record<Theme, Theme> = { light: 'dark', dark: 'system', system: 'light' }
222
+ const Icon = theme === 'dark' ? MoonIcon : theme === 'light' ? SunIcon : MonitorIcon
223
+ const label = theme === 'dark' ? 'Oscuro' : theme === 'light' ? 'Claro' : 'Auto'
224
+
225
+ return (
226
+ <button
227
+ onClick={() => setTheme(next[theme])}
228
+ className="w-full text-left px-3 py-1.5 text-sm rounded hover:bg-accent transition-colors flex items-center gap-2"
229
+ >
230
+ <Icon className="w-3.5 h-3.5" /> Tema: {label}
231
+ </button>
232
+ )
233
+ }
@@ -0,0 +1,20 @@
1
+ import type { WidgetInstance } from './types'
2
+
3
+ const KEY = 'proto-shell'
4
+
5
+ export function loadShellState(): { widgets: WidgetInstance[]; layouts: any } | null {
6
+ try {
7
+ const raw = localStorage.getItem(KEY)
8
+ return raw ? JSON.parse(raw) : null
9
+ } catch { return null }
10
+ }
11
+
12
+ export function saveShellState(widgets: WidgetInstance[], layouts: any) {
13
+ try {
14
+ localStorage.setItem(KEY, JSON.stringify({ widgets, layouts }))
15
+ } catch {}
16
+ }
17
+
18
+ export function clearShellState() {
19
+ localStorage.removeItem(KEY)
20
+ }
@@ -0,0 +1,14 @@
1
+ export type WidgetType = string
2
+
3
+ export interface WidgetInstance {
4
+ id: string
5
+ type: WidgetType
6
+ title: string
7
+ props?: Record<string, any>
8
+ }
9
+
10
+ export interface ActiveEntity {
11
+ type: string
12
+ id: string
13
+ label: string
14
+ }
@@ -0,0 +1,18 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const Avatar = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div ref={ref} className={cn('relative flex h-8 w-8 shrink-0 overflow-hidden rounded-full', className)} {...props} />
7
+ )
8
+ )
9
+ Avatar.displayName = 'Avatar'
10
+
11
+ const AvatarFallback = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
12
+ ({ className, ...props }, ref) => (
13
+ <div ref={ref} className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)} {...props} />
14
+ )
15
+ )
16
+ AvatarFallback.displayName = 'AvatarFallback'
17
+
18
+ export { Avatar, AvatarFallback }
@@ -0,0 +1,28 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const variantStyles = {
5
+ default: 'bg-primary text-primary-foreground',
6
+ secondary: 'bg-secondary text-secondary-foreground',
7
+ destructive: 'bg-destructive/10 text-destructive',
8
+ outline: 'border border-border text-foreground',
9
+ }
10
+
11
+ export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
12
+ variant?: keyof typeof variantStyles
13
+ }
14
+
15
+ function Badge({ className, variant = 'default', ...props }: BadgeProps) {
16
+ return (
17
+ <span
18
+ className={cn(
19
+ 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',
20
+ variantStyles[variant],
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ export { Badge }
@@ -0,0 +1,40 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const variantStyles = {
5
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
6
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
7
+ outline: 'border border-border bg-background hover:bg-accent hover:text-accent-foreground',
8
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
9
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
10
+ link: 'text-primary underline-offset-4 hover:underline',
11
+ }
12
+
13
+ const sizeStyles = {
14
+ default: 'h-9 px-4 py-2',
15
+ sm: 'h-8 rounded-md px-3 text-xs',
16
+ lg: 'h-11 rounded-md px-8',
17
+ icon: 'h-9 w-9',
18
+ }
19
+
20
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
21
+ variant?: keyof typeof variantStyles
22
+ size?: keyof typeof sizeStyles
23
+ }
24
+
25
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
26
+ ({ className, variant = 'default', size = 'default', ...props }, ref) => (
27
+ <button
28
+ className={cn(
29
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:w-4 [&_svg]:h-4',
30
+ variantStyles[variant],
31
+ sizeStyles[size],
32
+ className
33
+ )}
34
+ ref={ref}
35
+ {...props}
36
+ />
37
+ )
38
+ )
39
+ Button.displayName = 'Button'
40
+ export { Button }
@@ -0,0 +1,32 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div ref={ref} className={cn('rounded-xl border bg-card text-card-foreground', className)} {...props} />
7
+ )
8
+ )
9
+ Card.displayName = 'Card'
10
+
11
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
12
+ ({ className, ...props }, ref) => (
13
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-4', className)} {...props} />
14
+ )
15
+ )
16
+ CardHeader.displayName = 'CardHeader'
17
+
18
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
19
+ ({ className, ...props }, ref) => (
20
+ <div ref={ref} className={cn('p-4 pt-0', className)} {...props} />
21
+ )
22
+ )
23
+ CardContent.displayName = 'CardContent'
24
+
25
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
26
+ ({ className, ...props }, ref) => (
27
+ <div ref={ref} className={cn('flex items-center p-4 pt-0', className)} {...props} />
28
+ )
29
+ )
30
+ CardFooter.displayName = 'CardFooter'
31
+
32
+ export { Card, CardHeader, CardContent, CardFooter }
@@ -0,0 +1,120 @@
1
+ import { useCallback, useRef, useState } from 'react'
2
+ import { Pencil, Check, X } from 'lucide-react'
3
+
4
+ interface InlineEditProps {
5
+ value: string | null | undefined
6
+ onSave: (next: string) => Promise<void> | void
7
+ placeholder?: string
8
+ className?: string
9
+ inputClassName?: string
10
+ /** Render the display value when not editing. Defaults to plain text. */
11
+ display?: (value: string) => React.ReactNode
12
+ /** Allow empty string to clear the field. Defaults to true. */
13
+ allowEmpty?: boolean
14
+ /** Input type (text, date, number). Defaults to text. */
15
+ type?: 'text' | 'date' | 'number'
16
+ }
17
+
18
+ /**
19
+ * Hover to reveal a pencil icon, click to edit inline. Enter or check icon
20
+ * saves, Escape or X discards. Keeps display markup flexible via `display`.
21
+ */
22
+ export function InlineEdit({
23
+ value,
24
+ onSave,
25
+ placeholder = '—',
26
+ className = '',
27
+ inputClassName = '',
28
+ display,
29
+ allowEmpty = true,
30
+ type = 'text',
31
+ }: InlineEditProps) {
32
+ const [editing, setEditing] = useState(false)
33
+ const [draft, setDraft] = useState(value ?? '')
34
+ const [saving, setSaving] = useState(false)
35
+ const inputRef = useRef<HTMLInputElement>(null)
36
+
37
+ // Sync draft when value changes externally while not editing
38
+ const prevValueRef = useRef(value)
39
+ if (prevValueRef.current !== value) {
40
+ prevValueRef.current = value
41
+ if (!editing) setDraft(value ?? '')
42
+ }
43
+
44
+ // Callback ref: auto-focus and select when the input mounts (editing starts)
45
+ const inputCallbackRef = useCallback((node: HTMLInputElement | null) => {
46
+ (inputRef as React.MutableRefObject<HTMLInputElement | null>).current = node
47
+ if (node) {
48
+ node.focus()
49
+ node.select()
50
+ }
51
+ }, [])
52
+
53
+ const commit = async () => {
54
+ const next = draft.trim()
55
+ if (!allowEmpty && next === '') { setEditing(false); return }
56
+ if (next === (value ?? '')) { setEditing(false); return }
57
+ try {
58
+ setSaving(true)
59
+ await onSave(next)
60
+ setEditing(false)
61
+ } finally {
62
+ setSaving(false)
63
+ }
64
+ }
65
+
66
+ const cancel = () => {
67
+ setDraft(value ?? '')
68
+ setEditing(false)
69
+ }
70
+
71
+ if (editing) {
72
+ return (
73
+ <span className={`inline-flex items-center gap-1 ${className}`}>
74
+ <input
75
+ ref={inputCallbackRef}
76
+ type={type}
77
+ value={draft}
78
+ disabled={saving}
79
+ onChange={(e) => setDraft(e.target.value)}
80
+ onKeyDown={(e) => {
81
+ if (e.key === 'Enter') commit()
82
+ if (e.key === 'Escape') cancel()
83
+ }}
84
+ onBlur={commit}
85
+ className={`bg-background border border-primary/50 rounded px-1.5 py-0.5 outline-none focus:border-primary ${inputClassName}`}
86
+ />
87
+ <button
88
+ onMouseDown={(e) => { e.preventDefault(); commit() }}
89
+ className="text-emerald-600 hover:text-emerald-500 shrink-0"
90
+ disabled={saving}
91
+ aria-label="Guardar"
92
+ >
93
+ <Check className="h-3 w-3" />
94
+ </button>
95
+ <button
96
+ onMouseDown={(e) => { e.preventDefault(); cancel() }}
97
+ className="text-muted-foreground hover:text-foreground shrink-0"
98
+ disabled={saving}
99
+ aria-label="Cancelar"
100
+ >
101
+ <X className="h-3 w-3" />
102
+ </button>
103
+ </span>
104
+ )
105
+ }
106
+
107
+ const shown = value && value.trim() !== '' ? value : placeholder
108
+ return (
109
+ <span
110
+ className={`group inline-flex items-center gap-1 cursor-text ${className}`}
111
+ onClick={() => setEditing(true)}
112
+ role="button"
113
+ tabIndex={0}
114
+ onKeyDown={(e) => { if (e.key === 'Enter') setEditing(true) }}
115
+ >
116
+ <span className="truncate">{display && value ? display(value) : shown}</span>
117
+ <Pencil className="h-3 w-3 opacity-0 group-hover:opacity-60 transition-opacity shrink-0" />
118
+ </span>
119
+ )
120
+ }
@@ -0,0 +1,18 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
5
+ ({ className, type, ...props }, ref) => (
6
+ <input
7
+ type={type}
8
+ className={cn(
9
+ 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
10
+ className
11
+ )}
12
+ ref={ref}
13
+ {...props}
14
+ />
15
+ )
16
+ )
17
+ Input.displayName = 'Input'
18
+ export { Input }
@@ -0,0 +1,12 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const ScrollArea = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, children, ...props }, ref) => (
6
+ <div ref={ref} className={cn('relative overflow-auto', className)} {...props}>
7
+ {children}
8
+ </div>
9
+ )
10
+ )
11
+ ScrollArea.displayName = 'ScrollArea'
12
+ export { ScrollArea }
@@ -0,0 +1,23 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ orientation?: 'horizontal' | 'vertical'
6
+ }
7
+
8
+ const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
9
+ ({ className, orientation = 'horizontal', ...props }, ref) => (
10
+ <div
11
+ ref={ref}
12
+ role="separator"
13
+ className={cn(
14
+ 'shrink-0 bg-border',
15
+ orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ )
22
+ Separator.displayName = 'Separator'
23
+ export { Separator }
@@ -0,0 +1,79 @@
1
+ import { useEffect, useState, type ReactNode } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import { XIcon } from 'lucide-react'
4
+ import { cn } from '../../lib/utils'
5
+
6
+ interface ShellDialogProps {
7
+ open: boolean
8
+ onClose: () => void
9
+ title?: ReactNode
10
+ description?: ReactNode
11
+ children: ReactNode
12
+ className?: string
13
+ }
14
+
15
+ /**
16
+ * Dialog scoped to the shell (#shell-root) instead of the full viewport,
17
+ * so the chat panel stays visible. Backdrop-blur + fade-in, shadcn vibes.
18
+ */
19
+ export function ShellDialog({ open, onClose, title, description, children, className }: ShellDialogProps) {
20
+ const [mounted, setMounted] = useState(false)
21
+ const [target, setTarget] = useState<HTMLElement | null>(null)
22
+
23
+ useEffect(() => {
24
+ setTarget(document.getElementById('shell-root'))
25
+ }, [open])
26
+
27
+ useEffect(() => {
28
+ if (!open) { setMounted(false); return }
29
+ const id = requestAnimationFrame(() => setMounted(true))
30
+ return () => cancelAnimationFrame(id)
31
+ }, [open])
32
+
33
+ useEffect(() => {
34
+ if (!open) return
35
+ const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
36
+ window.addEventListener('keydown', onKey)
37
+ return () => window.removeEventListener('keydown', onKey)
38
+ }, [open, onClose])
39
+
40
+ if (!open || !target) return null
41
+
42
+ return createPortal(
43
+ <div
44
+ className={cn(
45
+ 'absolute inset-0 z-50 flex items-center justify-center p-6',
46
+ 'bg-background/60 backdrop-blur-sm transition-opacity duration-150',
47
+ mounted ? 'opacity-100' : 'opacity-0'
48
+ )}
49
+ onClick={onClose}
50
+ >
51
+ <div
52
+ onClick={(e) => e.stopPropagation()}
53
+ className={cn(
54
+ 'relative bg-card border border-border rounded-lg shadow-lg',
55
+ 'w-full max-w-lg max-h-[90%] flex flex-col',
56
+ 'transition-all duration-150',
57
+ mounted ? 'translate-y-0 opacity-100' : 'translate-y-2 opacity-0',
58
+ className
59
+ )}
60
+ >
61
+ <button
62
+ onClick={onClose}
63
+ className="absolute right-3 top-3 text-muted-foreground/60 hover:text-foreground"
64
+ aria-label="Cerrar"
65
+ >
66
+ <XIcon className="w-4 h-4" />
67
+ </button>
68
+ {(title || description) && (
69
+ <div className="px-5 pt-5 pb-3 border-b border-border/50 shrink-0">
70
+ {title && <h2 className="text-base font-semibold">{title}</h2>}
71
+ {description && <p className="text-xs text-muted-foreground mt-1">{description}</p>}
72
+ </div>
73
+ )}
74
+ <div className="p-5 flex-1 min-h-0 overflow-y-auto scrollbar-thin">{children}</div>
75
+ </div>
76
+ </div>,
77
+ target
78
+ )
79
+ }
@@ -0,0 +1,9 @@
1
+ import { cn } from '../../lib/utils'
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return (
5
+ <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />
6
+ )
7
+ }
8
+
9
+ export { Skeleton }
@@ -0,0 +1,17 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../../lib/utils'
3
+
4
+ const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <textarea
7
+ className={cn(
8
+ 'flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
9
+ className
10
+ )}
11
+ ref={ref}
12
+ {...props}
13
+ />
14
+ )
15
+ )
16
+ Textarea.displayName = 'Textarea'
17
+ export { Textarea }