@create-lft-app/cli 1.0.14 → 1.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.
- package/dist/bin/cli.js +13 -146
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/index.js +11 -144
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/app/auth/login/page.tsx +0 -153
- package/templates/app/dashboard/page.tsx +0 -102
- package/templates/app/globals.css +0 -249
- package/templates/app/layout.tsx +0 -40
- package/templates/app/page.tsx +0 -5
- package/templates/components/dashboard/widget.tsx +0 -113
- package/templates/components/layout/admin-midday-sidebar.tsx +0 -247
- package/templates/components/layout/admin-sidebar.tsx +0 -146
- package/templates/components/layout/header.tsx +0 -71
- package/templates/components/layout/main-content.tsx +0 -28
- package/templates/components/layout/midday-sidebar.tsx +0 -381
- package/templates/components/layout/nav-user.tsx +0 -108
- package/templates/components/layout/page-header.tsx +0 -95
- package/templates/components/layout/sidebar-context.tsx +0 -33
- package/templates/components/layout/sidebar.tsx +0 -194
- package/templates/components/layout/suspension-banner.tsx +0 -21
- package/templates/components/ui/accordion.tsx +0 -58
- package/templates/components/ui/alert-dialog.tsx +0 -165
- package/templates/components/ui/alert.tsx +0 -66
- package/templates/components/ui/avatar.tsx +0 -55
- package/templates/components/ui/badge.tsx +0 -50
- package/templates/components/ui/button.tsx +0 -89
- package/templates/components/ui/calendar.tsx +0 -220
- package/templates/components/ui/card.tsx +0 -89
- package/templates/components/ui/checkbox.tsx +0 -38
- package/templates/components/ui/collapsible.tsx +0 -33
- package/templates/components/ui/command.tsx +0 -196
- package/templates/components/ui/dialog.tsx +0 -153
- package/templates/components/ui/dropdown-menu.tsx +0 -280
- package/templates/components/ui/form.tsx +0 -171
- package/templates/components/ui/icons.tsx +0 -167
- package/templates/components/ui/input.tsx +0 -28
- package/templates/components/ui/label.tsx +0 -25
- package/templates/components/ui/popover.tsx +0 -59
- package/templates/components/ui/progress.tsx +0 -32
- package/templates/components/ui/radio-group.tsx +0 -45
- package/templates/components/ui/scroll-area.tsx +0 -63
- package/templates/components/ui/select.tsx +0 -208
- package/templates/components/ui/separator.tsx +0 -28
- package/templates/components/ui/sheet.tsx +0 -146
- package/templates/components/ui/sidebar.tsx +0 -726
- package/templates/components/ui/skeleton.tsx +0 -15
- package/templates/components/ui/slider.tsx +0 -58
- package/templates/components/ui/sonner.tsx +0 -47
- package/templates/components/ui/spinner.tsx +0 -27
- package/templates/components/ui/submit-button.tsx +0 -47
- package/templates/components/ui/switch.tsx +0 -31
- package/templates/components/ui/table.tsx +0 -120
- package/templates/components/ui/tabs.tsx +0 -75
- package/templates/components/ui/textarea.tsx +0 -26
- package/templates/components/ui/tooltip.tsx +0 -70
- package/templates/hooks/use-mobile.ts +0 -21
- package/templates/lib/supabase/client.ts +0 -8
- package/templates/lib/supabase/server.ts +0 -29
- package/templates/lib/utils.ts +0 -6
- package/templates/modules/auth/actions/auth-actions.ts +0 -12
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import Link from 'next/link'
|
|
4
|
-
import { usePathname } from 'next/navigation'
|
|
5
|
-
import { useState, useRef, useCallback, useEffect } from 'react'
|
|
6
|
-
import { cn } from '@/lib/utils'
|
|
7
|
-
import { Icons } from '@/components/ui/icons'
|
|
8
|
-
import { useSidebar } from './sidebar-context'
|
|
9
|
-
import {
|
|
10
|
-
DropdownMenu,
|
|
11
|
-
DropdownMenuContent,
|
|
12
|
-
DropdownMenuItem,
|
|
13
|
-
DropdownMenuSeparator,
|
|
14
|
-
DropdownMenuTrigger,
|
|
15
|
-
} from '@/components/ui/dropdown-menu'
|
|
16
|
-
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
|
17
|
-
import { logoutAction } from '@/modules/auth/actions/auth-actions'
|
|
18
|
-
|
|
19
|
-
interface NavItem {
|
|
20
|
-
path: string
|
|
21
|
-
name: string
|
|
22
|
-
icon: keyof typeof Icons
|
|
23
|
-
children?: { path: string; name: string }[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const mainNavItems: NavItem[] = [
|
|
27
|
-
{
|
|
28
|
-
path: '/dashboard',
|
|
29
|
-
name: 'Dashboard',
|
|
30
|
-
icon: 'Dashboard',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
path: '/clientes',
|
|
34
|
-
name: 'Clientes',
|
|
35
|
-
icon: 'Users',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
path: '/onboarding',
|
|
39
|
-
name: 'Onboarding',
|
|
40
|
-
icon: 'UserPlus',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
path: '/documents',
|
|
44
|
-
name: 'Documentos',
|
|
45
|
-
icon: 'Documents',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
path: '/alerts',
|
|
49
|
-
name: 'Alertas',
|
|
50
|
-
icon: 'Alert',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
path: '/operations',
|
|
54
|
-
name: 'Operaciones',
|
|
55
|
-
icon: 'Operations',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
path: '/calendar',
|
|
59
|
-
name: 'Calendario',
|
|
60
|
-
icon: 'Calendar',
|
|
61
|
-
},
|
|
62
|
-
]
|
|
63
|
-
|
|
64
|
-
const getConfigNavItems = (isSuperAdmin: boolean): NavItem[] => [
|
|
65
|
-
{
|
|
66
|
-
path: '/settings',
|
|
67
|
-
name: 'Configuración',
|
|
68
|
-
icon: 'Settings',
|
|
69
|
-
children: [
|
|
70
|
-
{ path: '/settings', name: 'General' },
|
|
71
|
-
{ path: '/settings/organization', name: 'Organización' },
|
|
72
|
-
{ path: '/settings/documents', name: 'Documentos' },
|
|
73
|
-
{ path: '/settings/onboarding', name: 'Onboarding' },
|
|
74
|
-
{ path: '/settings/integrations', name: 'Integraciones' },
|
|
75
|
-
...(isSuperAdmin ? [{ path: '/admin', name: 'Super Admin' }] : []),
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
interface MiddaySidebarProps {
|
|
81
|
-
user: {
|
|
82
|
-
email: string
|
|
83
|
-
full_name: string | null
|
|
84
|
-
role: string
|
|
85
|
-
organization?: {
|
|
86
|
-
name: string
|
|
87
|
-
} | null
|
|
88
|
-
} | null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function MenuItem({
|
|
92
|
-
item,
|
|
93
|
-
isExpanded,
|
|
94
|
-
pathname,
|
|
95
|
-
onNavigate,
|
|
96
|
-
}: {
|
|
97
|
-
item: NavItem
|
|
98
|
-
isExpanded: boolean
|
|
99
|
-
pathname: string
|
|
100
|
-
onNavigate?: () => void
|
|
101
|
-
}) {
|
|
102
|
-
const [isSubmenuOpen, setIsSubmenuOpen] = useState(false)
|
|
103
|
-
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
104
|
-
const IconComponent = Icons[item.icon]
|
|
105
|
-
|
|
106
|
-
const isExactMatch = pathname === item.path
|
|
107
|
-
const isChildMatch = item.children?.some(child => pathname === child.path)
|
|
108
|
-
const isActive = isExactMatch || isChildMatch
|
|
109
|
-
const hasChildren = item.children && item.children.length > 0
|
|
110
|
-
|
|
111
|
-
const handleMouseEnter = useCallback(() => {
|
|
112
|
-
if (timeoutRef.current) {
|
|
113
|
-
clearTimeout(timeoutRef.current)
|
|
114
|
-
}
|
|
115
|
-
if (hasChildren) {
|
|
116
|
-
setIsSubmenuOpen(true)
|
|
117
|
-
}
|
|
118
|
-
}, [hasChildren])
|
|
119
|
-
|
|
120
|
-
const handleMouseLeave = useCallback(() => {
|
|
121
|
-
timeoutRef.current = setTimeout(() => {
|
|
122
|
-
setIsSubmenuOpen(false)
|
|
123
|
-
}, 150)
|
|
124
|
-
}, [])
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<li
|
|
128
|
-
className="relative"
|
|
129
|
-
onMouseEnter={handleMouseEnter}
|
|
130
|
-
onMouseLeave={handleMouseLeave}
|
|
131
|
-
>
|
|
132
|
-
<Link
|
|
133
|
-
href={hasChildren && item.children ? item.children[0].path : item.path}
|
|
134
|
-
onClick={onNavigate}
|
|
135
|
-
className={cn(
|
|
136
|
-
'h-[40px] flex items-center gap-3',
|
|
137
|
-
'mx-[10px] px-[10px]',
|
|
138
|
-
'rounded-md',
|
|
139
|
-
'transition-all duration-150',
|
|
140
|
-
!isActive && 'hover:bg-[#f7f7f7] dark:hover:bg-[#1a1a1a]',
|
|
141
|
-
isActive && [
|
|
142
|
-
'bg-[#f7f7f7] dark:bg-[#131313]',
|
|
143
|
-
'border border-border',
|
|
144
|
-
]
|
|
145
|
-
)}
|
|
146
|
-
>
|
|
147
|
-
<div className="w-5 h-5 flex items-center justify-center flex-shrink-0">
|
|
148
|
-
<IconComponent
|
|
149
|
-
size={20}
|
|
150
|
-
className={cn(
|
|
151
|
-
'transition-colors',
|
|
152
|
-
isActive ? 'text-foreground' : 'text-[#878787]'
|
|
153
|
-
)}
|
|
154
|
-
/>
|
|
155
|
-
</div>
|
|
156
|
-
{isExpanded && (
|
|
157
|
-
<>
|
|
158
|
-
<span
|
|
159
|
-
className={cn(
|
|
160
|
-
'text-sm whitespace-nowrap',
|
|
161
|
-
'transition-all duration-150',
|
|
162
|
-
isActive ? 'font-medium text-foreground' : 'text-[#878787]'
|
|
163
|
-
)}
|
|
164
|
-
>
|
|
165
|
-
{item.name}
|
|
166
|
-
</span>
|
|
167
|
-
{hasChildren && (
|
|
168
|
-
<Icons.ChevronRight
|
|
169
|
-
size={14}
|
|
170
|
-
className={cn(
|
|
171
|
-
'ml-auto text-[#878787]',
|
|
172
|
-
'transition-transform duration-200',
|
|
173
|
-
isSubmenuOpen && 'rotate-90'
|
|
174
|
-
)}
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
</>
|
|
178
|
-
)}
|
|
179
|
-
</Link>
|
|
180
|
-
|
|
181
|
-
{/* Submenu - Midday style with staggered animation */}
|
|
182
|
-
{hasChildren && isExpanded && isSubmenuOpen && (
|
|
183
|
-
<ul className="mt-1 space-y-0.5">
|
|
184
|
-
{item.children?.map((child, index) => {
|
|
185
|
-
const isChildActive = pathname === child.path
|
|
186
|
-
return (
|
|
187
|
-
<li
|
|
188
|
-
key={child.path}
|
|
189
|
-
className="animate-in fade-in-0 slide-in-from-left-1"
|
|
190
|
-
style={{
|
|
191
|
-
animationDuration: '150ms',
|
|
192
|
-
animationDelay: `${40 + index * 20}ms`,
|
|
193
|
-
animationFillMode: 'backwards'
|
|
194
|
-
}}
|
|
195
|
-
>
|
|
196
|
-
<div className="ml-[35px] border-l border-[#e6e6e6] dark:border-[#2c2c2c]">
|
|
197
|
-
<Link
|
|
198
|
-
href={child.path}
|
|
199
|
-
onClick={onNavigate}
|
|
200
|
-
className={cn(
|
|
201
|
-
'block py-1.5 pl-3 pr-4',
|
|
202
|
-
'text-xs',
|
|
203
|
-
'transition-colors duration-150',
|
|
204
|
-
isChildActive
|
|
205
|
-
? 'text-foreground font-medium'
|
|
206
|
-
: 'text-[#888] hover:text-foreground'
|
|
207
|
-
)}
|
|
208
|
-
>
|
|
209
|
-
{child.name}
|
|
210
|
-
</Link>
|
|
211
|
-
</div>
|
|
212
|
-
</li>
|
|
213
|
-
)
|
|
214
|
-
})}
|
|
215
|
-
</ul>
|
|
216
|
-
)}
|
|
217
|
-
</li>
|
|
218
|
-
)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function UserDropdown({
|
|
222
|
-
user,
|
|
223
|
-
isExpanded,
|
|
224
|
-
}: {
|
|
225
|
-
user: MiddaySidebarProps['user']
|
|
226
|
-
isExpanded: boolean
|
|
227
|
-
}) {
|
|
228
|
-
const initials = user?.full_name
|
|
229
|
-
? user.full_name
|
|
230
|
-
.split(' ')
|
|
231
|
-
.map((n) => n[0])
|
|
232
|
-
.join('')
|
|
233
|
-
.toUpperCase()
|
|
234
|
-
.slice(0, 2)
|
|
235
|
-
: user?.email?.slice(0, 2).toUpperCase() || 'U'
|
|
236
|
-
|
|
237
|
-
return (
|
|
238
|
-
<DropdownMenu>
|
|
239
|
-
<DropdownMenuTrigger asChild>
|
|
240
|
-
<button
|
|
241
|
-
className={cn(
|
|
242
|
-
'flex items-center gap-3',
|
|
243
|
-
'w-[calc(100%-20px)] mx-[10px] px-[10px] py-2',
|
|
244
|
-
'rounded-md',
|
|
245
|
-
'hover:bg-[#f7f7f7] dark:hover:bg-[#1a1a1a]',
|
|
246
|
-
'transition-colors duration-150',
|
|
247
|
-
'focus:outline-none focus-visible:ring-1 focus-visible:ring-ring'
|
|
248
|
-
)}
|
|
249
|
-
>
|
|
250
|
-
<Avatar className="h-8 w-8 flex-shrink-0">
|
|
251
|
-
<AvatarFallback className="bg-primary text-primary-foreground text-xs font-medium">
|
|
252
|
-
{initials}
|
|
253
|
-
</AvatarFallback>
|
|
254
|
-
</Avatar>
|
|
255
|
-
{isExpanded && (
|
|
256
|
-
<div className="flex-1 text-left overflow-hidden min-w-0">
|
|
257
|
-
<p className="text-sm font-medium truncate leading-tight">
|
|
258
|
-
{user?.full_name || 'Usuario'}
|
|
259
|
-
</p>
|
|
260
|
-
<p className="text-xs text-[#878787] truncate leading-tight">
|
|
261
|
-
{user?.email}
|
|
262
|
-
</p>
|
|
263
|
-
</div>
|
|
264
|
-
)}
|
|
265
|
-
</button>
|
|
266
|
-
</DropdownMenuTrigger>
|
|
267
|
-
<DropdownMenuContent
|
|
268
|
-
align="start"
|
|
269
|
-
side="top"
|
|
270
|
-
sideOffset={8}
|
|
271
|
-
className="w-56"
|
|
272
|
-
>
|
|
273
|
-
<div className="px-2 py-1.5">
|
|
274
|
-
<p className="text-sm font-medium">{user?.full_name || 'Usuario'}</p>
|
|
275
|
-
<p className="text-xs text-[#878787]">{user?.email}</p>
|
|
276
|
-
</div>
|
|
277
|
-
<DropdownMenuSeparator />
|
|
278
|
-
<DropdownMenuItem asChild>
|
|
279
|
-
<Link href="/settings" className="cursor-pointer">
|
|
280
|
-
<Icons.Settings size={16} className="mr-2 text-[#878787]" />
|
|
281
|
-
<span>Configuración</span>
|
|
282
|
-
</Link>
|
|
283
|
-
</DropdownMenuItem>
|
|
284
|
-
<DropdownMenuSeparator />
|
|
285
|
-
<DropdownMenuItem
|
|
286
|
-
className="text-destructive focus:text-destructive cursor-pointer"
|
|
287
|
-
onClick={() => logoutAction()}
|
|
288
|
-
>
|
|
289
|
-
<Icons.LogOut size={16} className="mr-2" />
|
|
290
|
-
<span>Cerrar sesión</span>
|
|
291
|
-
</DropdownMenuItem>
|
|
292
|
-
</DropdownMenuContent>
|
|
293
|
-
</DropdownMenu>
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export function MiddaySidebar({ user }: MiddaySidebarProps) {
|
|
298
|
-
const { isExpanded, setIsExpanded } = useSidebar()
|
|
299
|
-
const pathname = usePathname()
|
|
300
|
-
const sidebarRef = useRef<HTMLDivElement>(null)
|
|
301
|
-
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
302
|
-
|
|
303
|
-
const isAdmin = user?.role && ['admin', 'super_admin'].includes(user.role)
|
|
304
|
-
const isSuperAdmin = user?.role === 'super_admin'
|
|
305
|
-
const configNavItems = getConfigNavItems(isSuperAdmin)
|
|
306
|
-
|
|
307
|
-
const handleMouseEnter = useCallback(() => {
|
|
308
|
-
if (hoverTimeoutRef.current) {
|
|
309
|
-
clearTimeout(hoverTimeoutRef.current)
|
|
310
|
-
}
|
|
311
|
-
setIsExpanded(true)
|
|
312
|
-
}, [setIsExpanded])
|
|
313
|
-
|
|
314
|
-
const handleMouseLeave = useCallback(() => {
|
|
315
|
-
hoverTimeoutRef.current = setTimeout(() => {
|
|
316
|
-
setIsExpanded(false)
|
|
317
|
-
}, 100)
|
|
318
|
-
}, [setIsExpanded])
|
|
319
|
-
|
|
320
|
-
return (
|
|
321
|
-
<aside
|
|
322
|
-
ref={sidebarRef}
|
|
323
|
-
className={cn(
|
|
324
|
-
'h-screen flex-shrink-0 flex-col justify-between fixed top-0 left-0 hidden md:flex z-50',
|
|
325
|
-
'bg-background border-r border-border',
|
|
326
|
-
'transition-[width] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]',
|
|
327
|
-
isExpanded ? 'w-[240px]' : 'w-[70px]'
|
|
328
|
-
)}
|
|
329
|
-
onMouseEnter={handleMouseEnter}
|
|
330
|
-
onMouseLeave={handleMouseLeave}
|
|
331
|
-
>
|
|
332
|
-
{/* Header / Logo - Midday style */}
|
|
333
|
-
<div
|
|
334
|
-
className={cn(
|
|
335
|
-
"absolute top-0 left-0 h-[70px] flex items-center justify-center bg-background border-b border-border transition-all duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]",
|
|
336
|
-
isExpanded ? "w-full" : "w-[69px]"
|
|
337
|
-
)}
|
|
338
|
-
>
|
|
339
|
-
<Link href="/dashboard" className="absolute left-[22px] transition-none">
|
|
340
|
-
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
341
|
-
<img src="/logolft.svg" alt="Logo" className="h-7 w-7" />
|
|
342
|
-
</Link>
|
|
343
|
-
</div>
|
|
344
|
-
|
|
345
|
-
{/* Navigation - Midday style */}
|
|
346
|
-
<nav className="flex-1 pt-[70px] pb-4 overflow-y-auto scrollbar-hide border-b border-border">
|
|
347
|
-
<ul className="space-y-1">
|
|
348
|
-
{mainNavItems.map((item) => (
|
|
349
|
-
<MenuItem
|
|
350
|
-
key={item.path}
|
|
351
|
-
item={item}
|
|
352
|
-
isExpanded={isExpanded}
|
|
353
|
-
pathname={pathname}
|
|
354
|
-
/>
|
|
355
|
-
))}
|
|
356
|
-
</ul>
|
|
357
|
-
|
|
358
|
-
{/* Admin section with separator */}
|
|
359
|
-
{isAdmin && (
|
|
360
|
-
<div className="mt-6 pt-4 mx-[10px] border-t border-border">
|
|
361
|
-
<ul className="space-y-1">
|
|
362
|
-
{configNavItems.map((item) => (
|
|
363
|
-
<MenuItem
|
|
364
|
-
key={item.path}
|
|
365
|
-
item={item}
|
|
366
|
-
isExpanded={isExpanded}
|
|
367
|
-
pathname={pathname}
|
|
368
|
-
/>
|
|
369
|
-
))}
|
|
370
|
-
</ul>
|
|
371
|
-
</div>
|
|
372
|
-
)}
|
|
373
|
-
</nav>
|
|
374
|
-
|
|
375
|
-
{/* User section - Midday style */}
|
|
376
|
-
<div className="border-t border-border py-3">
|
|
377
|
-
<UserDropdown user={user} isExpanded={isExpanded} />
|
|
378
|
-
</div>
|
|
379
|
-
</aside>
|
|
380
|
-
)
|
|
381
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { logout } from '@/modules/auth/actions/auth-actions'
|
|
4
|
-
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
|
5
|
-
import {
|
|
6
|
-
DropdownMenu,
|
|
7
|
-
DropdownMenuContent,
|
|
8
|
-
DropdownMenuItem,
|
|
9
|
-
DropdownMenuLabel,
|
|
10
|
-
DropdownMenuSeparator,
|
|
11
|
-
DropdownMenuTrigger,
|
|
12
|
-
} from '@/components/ui/dropdown-menu'
|
|
13
|
-
import { SidebarMenuButton } from '@/components/ui/sidebar'
|
|
14
|
-
import { ChevronsUpDown, LogOut, User } from 'lucide-react'
|
|
15
|
-
import { Badge } from '@/components/ui/badge'
|
|
16
|
-
|
|
17
|
-
interface NavUserProps {
|
|
18
|
-
user: {
|
|
19
|
-
email: string
|
|
20
|
-
full_name: string | null
|
|
21
|
-
role: string
|
|
22
|
-
} | null
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const roleLabels: Record<string, string> = {
|
|
26
|
-
super_admin: 'Super Admin',
|
|
27
|
-
admin: 'Administrador',
|
|
28
|
-
compliance: 'Compliance',
|
|
29
|
-
analyst: 'Analista',
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function NavUser({ user }: NavUserProps) {
|
|
33
|
-
if (!user) return null
|
|
34
|
-
|
|
35
|
-
const initials = user.full_name
|
|
36
|
-
? user.full_name
|
|
37
|
-
.split(' ')
|
|
38
|
-
.map((n) => n[0])
|
|
39
|
-
.join('')
|
|
40
|
-
.toUpperCase()
|
|
41
|
-
.slice(0, 2)
|
|
42
|
-
: user.email.slice(0, 2).toUpperCase()
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<DropdownMenu>
|
|
46
|
-
<DropdownMenuTrigger asChild>
|
|
47
|
-
<SidebarMenuButton
|
|
48
|
-
size="lg"
|
|
49
|
-
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
50
|
-
>
|
|
51
|
-
<Avatar className="h-8 w-8">
|
|
52
|
-
<AvatarFallback className="bg-primary text-primary-foreground text-xs">
|
|
53
|
-
{initials}
|
|
54
|
-
</AvatarFallback>
|
|
55
|
-
</Avatar>
|
|
56
|
-
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
57
|
-
<span className="truncate font-semibold">
|
|
58
|
-
{user.full_name || user.email}
|
|
59
|
-
</span>
|
|
60
|
-
<span className="truncate text-xs text-muted-foreground">
|
|
61
|
-
{user.email}
|
|
62
|
-
</span>
|
|
63
|
-
</div>
|
|
64
|
-
<ChevronsUpDown className="ml-auto size-4" />
|
|
65
|
-
</SidebarMenuButton>
|
|
66
|
-
</DropdownMenuTrigger>
|
|
67
|
-
<DropdownMenuContent
|
|
68
|
-
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
|
69
|
-
side="bottom"
|
|
70
|
-
align="end"
|
|
71
|
-
sideOffset={4}
|
|
72
|
-
>
|
|
73
|
-
<DropdownMenuLabel className="p-0 font-normal">
|
|
74
|
-
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
75
|
-
<Avatar className="h-8 w-8">
|
|
76
|
-
<AvatarFallback className="bg-primary text-primary-foreground text-xs">
|
|
77
|
-
{initials}
|
|
78
|
-
</AvatarFallback>
|
|
79
|
-
</Avatar>
|
|
80
|
-
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
81
|
-
<span className="truncate font-semibold">
|
|
82
|
-
{user.full_name || user.email}
|
|
83
|
-
</span>
|
|
84
|
-
<Badge variant="secondary" className="w-fit text-xs">
|
|
85
|
-
{roleLabels[user.role] || user.role}
|
|
86
|
-
</Badge>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
</DropdownMenuLabel>
|
|
90
|
-
<DropdownMenuSeparator />
|
|
91
|
-
<DropdownMenuItem asChild>
|
|
92
|
-
<a href="/settings/profile" className="cursor-pointer">
|
|
93
|
-
<User className="mr-2 h-4 w-4" />
|
|
94
|
-
Mi Perfil
|
|
95
|
-
</a>
|
|
96
|
-
</DropdownMenuItem>
|
|
97
|
-
<DropdownMenuSeparator />
|
|
98
|
-
<DropdownMenuItem
|
|
99
|
-
className="cursor-pointer text-destructive focus:text-destructive"
|
|
100
|
-
onClick={() => logout()}
|
|
101
|
-
>
|
|
102
|
-
<LogOut className="mr-2 h-4 w-4" />
|
|
103
|
-
Cerrar sesión
|
|
104
|
-
</DropdownMenuItem>
|
|
105
|
-
</DropdownMenuContent>
|
|
106
|
-
</DropdownMenu>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { cn } from '@/lib/utils'
|
|
2
|
-
|
|
3
|
-
interface PageHeaderProps {
|
|
4
|
-
title: string
|
|
5
|
-
description?: string
|
|
6
|
-
children?: React.ReactNode
|
|
7
|
-
className?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function PageHeader({ title, description, children, className }: PageHeaderProps) {
|
|
11
|
-
return (
|
|
12
|
-
<header className={cn('h-[70px] flex items-center border-b border-border px-6', className)}>
|
|
13
|
-
<div className="flex items-center justify-between w-full">
|
|
14
|
-
<div>
|
|
15
|
-
<h1 className="text-lg font-medium">{title}</h1>
|
|
16
|
-
{description && (
|
|
17
|
-
<p className="text-xs text-[#878787]">{description}</p>
|
|
18
|
-
)}
|
|
19
|
-
</div>
|
|
20
|
-
{children && (
|
|
21
|
-
<div className="flex items-center gap-2">
|
|
22
|
-
{children}
|
|
23
|
-
</div>
|
|
24
|
-
)}
|
|
25
|
-
</div>
|
|
26
|
-
</header>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface PageContentProps {
|
|
31
|
-
children: React.ReactNode
|
|
32
|
-
className?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function PageContent({ children, className }: PageContentProps) {
|
|
36
|
-
return (
|
|
37
|
-
<div className={cn('flex-1 overflow-auto p-6', className)}>
|
|
38
|
-
{children}
|
|
39
|
-
</div>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface PageContainerProps {
|
|
44
|
-
children: React.ReactNode
|
|
45
|
-
className?: string
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function PageContainer({ children, className }: PageContainerProps) {
|
|
49
|
-
return (
|
|
50
|
-
<div className={cn('flex flex-col h-screen', className)}>
|
|
51
|
-
{children}
|
|
52
|
-
</div>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface StatItemProps {
|
|
57
|
-
label: string
|
|
58
|
-
value: string | number
|
|
59
|
-
trend?: {
|
|
60
|
-
value: number
|
|
61
|
-
positive?: boolean
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function StatItem({ label, value, trend }: StatItemProps) {
|
|
66
|
-
return (
|
|
67
|
-
<div className="border border-border bg-background p-6">
|
|
68
|
-
<p className="text-sm text-[#878787] mb-3">{label}</p>
|
|
69
|
-
<div className="flex items-baseline gap-2">
|
|
70
|
-
<span className="text-2xl font-normal tabular-nums">{value}</span>
|
|
71
|
-
{trend && (
|
|
72
|
-
<span className={cn(
|
|
73
|
-
'text-xs',
|
|
74
|
-
trend.positive ? 'text-[#00C48C]' : 'text-[#FF3B3B]'
|
|
75
|
-
)}>
|
|
76
|
-
{trend.positive ? '+' : ''}{trend.value}%
|
|
77
|
-
</span>
|
|
78
|
-
)}
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
interface StatsRowProps {
|
|
85
|
-
children: React.ReactNode
|
|
86
|
-
className?: string
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function StatsRow({ children, className }: StatsRowProps) {
|
|
90
|
-
return (
|
|
91
|
-
<div className={cn('grid grid-cols-4 gap-6', className)}>
|
|
92
|
-
{children}
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
|
|
4
|
-
|
|
5
|
-
interface SidebarContextType {
|
|
6
|
-
isExpanded: boolean
|
|
7
|
-
setIsExpanded: (value: boolean) => void
|
|
8
|
-
expand: () => void
|
|
9
|
-
collapse: () => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const SidebarContext = createContext<SidebarContextType | undefined>(undefined)
|
|
13
|
-
|
|
14
|
-
export function SidebarProvider({ children }: { children: ReactNode }) {
|
|
15
|
-
const [isExpanded, setIsExpanded] = useState(false)
|
|
16
|
-
|
|
17
|
-
const expand = useCallback(() => setIsExpanded(true), [])
|
|
18
|
-
const collapse = useCallback(() => setIsExpanded(false), [])
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<SidebarContext.Provider value={{ isExpanded, setIsExpanded, expand, collapse }}>
|
|
22
|
-
{children}
|
|
23
|
-
</SidebarContext.Provider>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function useSidebar() {
|
|
28
|
-
const context = useContext(SidebarContext)
|
|
29
|
-
if (context === undefined) {
|
|
30
|
-
throw new Error('useSidebar must be used within a SidebarProvider')
|
|
31
|
-
}
|
|
32
|
-
return context
|
|
33
|
-
}
|