@create-lft-app/nextjs 3.2.0 → 3.3.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/README.md +549 -549
- package/package.json +48 -48
- package/template/.claude/skills/anti-patterns.md +150 -0
- package/template/.claude/skills/drizzle-schema.md +178 -0
- package/template/.claude/skills/formatting.md +56 -0
- package/template/.claude/skills/module-architecture.md +143 -0
- package/template/.claude/skills/supabase-server-actions.md +199 -0
- package/template/.claude/skills/ui-patterns.md +161 -0
- package/template/CLAUDE.md +114 -1239
- package/template/drizzle.config.ts +12 -12
- package/template/eslint.config.mjs +16 -16
- package/template/gitignore +36 -36
- package/template/next.config.ts +7 -7
- package/template/package.json +86 -86
- package/template/postcss.config.mjs +7 -7
- package/template/proxy.ts +12 -12
- package/template/public/logolft.svg +11 -11
- package/template/src/app/(auth)/dashboard/dashboard-content.tsx +124 -124
- package/template/src/app/(auth)/dashboard/page.tsx +9 -9
- package/template/src/app/(auth)/layout.tsx +7 -7
- package/template/src/app/(auth)/users/page.tsx +9 -9
- package/template/src/app/(auth)/users/users-content.tsx +26 -26
- package/template/src/app/(public)/layout.tsx +7 -7
- package/template/src/app/(public)/login/page.tsx +17 -17
- package/template/src/app/api/webhooks/route.ts +20 -20
- package/template/src/app/globals.css +249 -249
- package/template/src/app/layout.tsx +37 -37
- package/template/src/app/page.tsx +5 -5
- package/template/src/app/providers.tsx +27 -27
- package/template/src/components/layout/main-content.tsx +28 -28
- package/template/src/components/layout/sidebar-context.tsx +33 -33
- package/template/src/components/layout/sidebar.tsx +141 -141
- package/template/src/components/tables/data-table-column-header.tsx +68 -68
- package/template/src/components/tables/data-table-date-filter.tsx +203 -203
- package/template/src/components/tables/data-table-faceted-filter.tsx +185 -185
- package/template/src/components/tables/data-table-filters-dropdown.tsx +130 -130
- package/template/src/components/tables/data-table-number-filter.tsx +295 -295
- package/template/src/components/tables/data-table-pagination.tsx +99 -99
- package/template/src/components/tables/data-table-toolbar.tsx +140 -140
- package/template/src/components/tables/data-table-view-options.tsx +63 -63
- package/template/src/components/tables/data-table.tsx +148 -148
- package/template/src/components/tables/index.ts +9 -9
- package/template/src/components/ui/accordion.tsx +58 -58
- package/template/src/components/ui/alert-dialog.tsx +165 -165
- package/template/src/components/ui/alert.tsx +66 -66
- package/template/src/components/ui/animations/index.ts +44 -44
- package/template/src/components/ui/avatar.tsx +55 -55
- package/template/src/components/ui/badge.tsx +50 -50
- package/template/src/components/ui/button.tsx +118 -118
- package/template/src/components/ui/calendar.tsx +220 -220
- package/template/src/components/ui/card.tsx +113 -113
- package/template/src/components/ui/checkbox.tsx +38 -38
- package/template/src/components/ui/collapsible.tsx +33 -33
- package/template/src/components/ui/command.tsx +196 -196
- package/template/src/components/ui/dialog.tsx +156 -156
- package/template/src/components/ui/dropdown-menu.tsx +280 -280
- package/template/src/components/ui/form.tsx +171 -171
- package/template/src/components/ui/icons.tsx +167 -167
- package/template/src/components/ui/input.tsx +28 -28
- package/template/src/components/ui/label.tsx +25 -25
- package/template/src/components/ui/motion.tsx +197 -197
- package/template/src/components/ui/page-transition.tsx +166 -166
- package/template/src/components/ui/popover.tsx +59 -59
- package/template/src/components/ui/progress.tsx +32 -32
- package/template/src/components/ui/radio-group.tsx +45 -45
- package/template/src/components/ui/scroll-area.tsx +63 -63
- package/template/src/components/ui/select.tsx +208 -208
- package/template/src/components/ui/separator.tsx +28 -28
- package/template/src/components/ui/sheet.tsx +170 -170
- package/template/src/components/ui/sidebar.tsx +726 -726
- package/template/src/components/ui/skeleton.tsx +15 -15
- package/template/src/components/ui/slider.tsx +58 -58
- package/template/src/components/ui/sonner.tsx +47 -47
- package/template/src/components/ui/spinner.tsx +27 -27
- package/template/src/components/ui/submit-button.tsx +47 -47
- package/template/src/components/ui/switch.tsx +31 -31
- package/template/src/components/ui/table.tsx +120 -120
- package/template/src/components/ui/tabs.tsx +75 -75
- package/template/src/components/ui/textarea.tsx +26 -26
- package/template/src/components/ui/tooltip.tsx +70 -70
- package/template/src/config/navigation.ts +59 -59
- package/template/src/config/roles.ts +27 -27
- package/template/src/config/site.ts +12 -12
- package/template/src/db/index.ts +12 -12
- package/template/src/db/schema/index.ts +1 -1
- package/template/src/db/schema/users.ts +16 -16
- package/template/src/db/seed.ts +39 -39
- package/template/src/hooks/index.ts +3 -3
- package/template/src/hooks/use-mobile.ts +21 -21
- package/template/src/hooks/useDataTable.ts +82 -82
- package/template/src/hooks/useDebounce.ts +49 -49
- package/template/src/hooks/useMediaQuery.ts +36 -36
- package/template/src/lib/date/config.ts +36 -36
- package/template/src/lib/date/formatters.ts +127 -127
- package/template/src/lib/date/index.ts +26 -26
- package/template/src/lib/excel/exporter.ts +89 -89
- package/template/src/lib/excel/index.ts +14 -14
- package/template/src/lib/excel/parser.ts +96 -96
- package/template/src/lib/query-client.ts +35 -35
- package/template/src/lib/supabase/admin.ts +23 -23
- package/template/src/lib/supabase/client.ts +11 -11
- package/template/src/lib/supabase/proxy.ts +67 -67
- package/template/src/lib/supabase/server.ts +38 -38
- package/template/src/lib/supabase/types.ts +53 -53
- package/template/src/lib/utils.ts +6 -6
- package/template/src/lib/validations/common.ts +75 -75
- package/template/src/lib/validations/index.ts +20 -20
- package/template/src/modules/auth/actions/auth-actions.ts +59 -59
- package/template/src/modules/auth/components/login-form.tsx +68 -68
- package/template/src/modules/auth/hooks/useAuth.ts +38 -38
- package/template/src/modules/auth/hooks/useAuthMutations.ts +43 -43
- package/template/src/modules/auth/hooks/useAuthQueries.ts +43 -43
- package/template/src/modules/auth/index.ts +12 -12
- package/template/src/modules/auth/schemas/auth.schema.ts +32 -32
- package/template/src/modules/auth/stores/useAuthStore.ts +37 -37
- package/template/src/modules/users/actions/users-actions.ts +166 -166
- package/template/src/modules/users/columns.tsx +106 -106
- package/template/src/modules/users/components/users-list.tsx +48 -48
- package/template/src/modules/users/hooks/useUsers.ts +39 -39
- package/template/src/modules/users/hooks/useUsersMutations.ts +55 -55
- package/template/src/modules/users/hooks/useUsersQueries.ts +35 -35
- package/template/src/modules/users/index.ts +30 -30
- package/template/src/modules/users/schemas/users.schema.ts +51 -51
- package/template/src/modules/users/stores/useUsersStore.ts +60 -60
- package/template/src/modules/users/types/auth-user.types.ts +42 -42
- package/template/src/modules/users/utils/user-mapper.ts +32 -32
- package/template/src/stores/index.ts +1 -1
- package/template/src/stores/useUiStore.ts +55 -55
- package/template/src/types/api.ts +28 -28
- package/template/src/types/index.ts +2 -2
- package/template/src/types/table.ts +34 -34
- package/template/supabase/config.toml +94 -94
- package/template/tsconfig.json +42 -42
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { cn } from '@/lib/utils'
|
|
4
|
-
import { useSidebar } from './sidebar-context'
|
|
5
|
-
import { ReactNode } from 'react'
|
|
6
|
-
|
|
7
|
-
interface MainContentProps {
|
|
8
|
-
children: ReactNode
|
|
9
|
-
className?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function MainContent({ children, className }: MainContentProps) {
|
|
13
|
-
const { isExpanded } = useSidebar()
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<main
|
|
17
|
-
className={cn(
|
|
18
|
-
'min-h-screen bg-background',
|
|
19
|
-
'transition-all duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]',
|
|
20
|
-
isExpanded ? 'md:ml-[240px] md:w-[calc(100%-240px)]' : 'md:ml-[70px] md:w-[calc(100%-70px)]',
|
|
21
|
-
'w-full',
|
|
22
|
-
className
|
|
23
|
-
)}
|
|
24
|
-
>
|
|
25
|
-
{children}
|
|
26
|
-
</main>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { useSidebar } from './sidebar-context'
|
|
5
|
+
import { ReactNode } from 'react'
|
|
6
|
+
|
|
7
|
+
interface MainContentProps {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
className?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function MainContent({ children, className }: MainContentProps) {
|
|
13
|
+
const { isExpanded } = useSidebar()
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<main
|
|
17
|
+
className={cn(
|
|
18
|
+
'min-h-screen bg-background',
|
|
19
|
+
'transition-all duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]',
|
|
20
|
+
isExpanded ? 'md:ml-[240px] md:w-[calc(100%-240px)]' : 'md:ml-[70px] md:w-[calc(100%-70px)]',
|
|
21
|
+
'w-full',
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</main>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -1,33 +1,33 @@
|
|
|
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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import Link from 'next/link'
|
|
4
|
-
import { usePathname } from 'next/navigation'
|
|
5
|
-
import { useRef, useCallback } from 'react'
|
|
6
|
-
import { cn } from '@/lib/utils'
|
|
7
|
-
import { Icons } from '@/components/ui/icons'
|
|
8
|
-
import { useSidebar } from './sidebar-context'
|
|
9
|
-
|
|
10
|
-
interface NavItem {
|
|
11
|
-
path: string
|
|
12
|
-
name: string
|
|
13
|
-
icon: keyof typeof Icons
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const navItems: NavItem[] = [
|
|
17
|
-
{
|
|
18
|
-
path: '/dashboard',
|
|
19
|
-
name: 'Dashboard',
|
|
20
|
-
icon: 'Dashboard',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
path: '/users',
|
|
24
|
-
name: 'Usuarios',
|
|
25
|
-
icon: 'Users',
|
|
26
|
-
},
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
function MenuItem({
|
|
30
|
-
item,
|
|
31
|
-
isExpanded,
|
|
32
|
-
pathname,
|
|
33
|
-
}: {
|
|
34
|
-
item: NavItem
|
|
35
|
-
isExpanded: boolean
|
|
36
|
-
pathname: string
|
|
37
|
-
}) {
|
|
38
|
-
const IconComponent = Icons[item.icon]
|
|
39
|
-
const isActive = pathname === item.path || pathname.startsWith(item.path + '/')
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<li className="relative">
|
|
43
|
-
<Link
|
|
44
|
-
href={item.path}
|
|
45
|
-
className={cn(
|
|
46
|
-
'h-[40px] flex items-center gap-3',
|
|
47
|
-
'mx-[10px] px-[10px]',
|
|
48
|
-
'rounded-md',
|
|
49
|
-
'transition-all duration-150',
|
|
50
|
-
!isActive && 'hover:bg-accent',
|
|
51
|
-
isActive && [
|
|
52
|
-
'bg-accent',
|
|
53
|
-
'border border-border',
|
|
54
|
-
]
|
|
55
|
-
)}
|
|
56
|
-
>
|
|
57
|
-
<div className="w-5 h-5 flex items-center justify-center flex-shrink-0">
|
|
58
|
-
<IconComponent
|
|
59
|
-
size={20}
|
|
60
|
-
className={cn(
|
|
61
|
-
'transition-colors',
|
|
62
|
-
isActive ? 'text-foreground' : 'text-muted-foreground'
|
|
63
|
-
)}
|
|
64
|
-
/>
|
|
65
|
-
</div>
|
|
66
|
-
{isExpanded && (
|
|
67
|
-
<span
|
|
68
|
-
className={cn(
|
|
69
|
-
'text-sm whitespace-nowrap',
|
|
70
|
-
'transition-all duration-150',
|
|
71
|
-
isActive ? 'font-medium text-foreground' : 'text-muted-foreground'
|
|
72
|
-
)}
|
|
73
|
-
>
|
|
74
|
-
{item.name}
|
|
75
|
-
</span>
|
|
76
|
-
)}
|
|
77
|
-
</Link>
|
|
78
|
-
</li>
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function Sidebar() {
|
|
83
|
-
const { isExpanded, setIsExpanded } = useSidebar()
|
|
84
|
-
const pathname = usePathname()
|
|
85
|
-
const sidebarRef = useRef<HTMLDivElement>(null)
|
|
86
|
-
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
87
|
-
|
|
88
|
-
const handleMouseEnter = useCallback(() => {
|
|
89
|
-
if (hoverTimeoutRef.current) {
|
|
90
|
-
clearTimeout(hoverTimeoutRef.current)
|
|
91
|
-
}
|
|
92
|
-
setIsExpanded(true)
|
|
93
|
-
}, [setIsExpanded])
|
|
94
|
-
|
|
95
|
-
const handleMouseLeave = useCallback(() => {
|
|
96
|
-
hoverTimeoutRef.current = setTimeout(() => {
|
|
97
|
-
setIsExpanded(false)
|
|
98
|
-
}, 100)
|
|
99
|
-
}, [setIsExpanded])
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<aside
|
|
103
|
-
ref={sidebarRef}
|
|
104
|
-
className={cn(
|
|
105
|
-
'h-screen flex-shrink-0 flex-col justify-between fixed top-0 left-0 hidden md:flex z-50',
|
|
106
|
-
'bg-background border-r border-border',
|
|
107
|
-
'transition-[width] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]',
|
|
108
|
-
isExpanded ? 'w-[240px]' : 'w-[70px]'
|
|
109
|
-
)}
|
|
110
|
-
onMouseEnter={handleMouseEnter}
|
|
111
|
-
onMouseLeave={handleMouseLeave}
|
|
112
|
-
>
|
|
113
|
-
{/* Header / Logo */}
|
|
114
|
-
<div
|
|
115
|
-
className={cn(
|
|
116
|
-
"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)]",
|
|
117
|
-
isExpanded ? "w-full" : "w-[69px]"
|
|
118
|
-
)}
|
|
119
|
-
>
|
|
120
|
-
<Link href="/dashboard" className="absolute left-[22px] transition-none">
|
|
121
|
-
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
122
|
-
<img src="/logolft.svg" alt="Logo" className="h-7 w-7" />
|
|
123
|
-
</Link>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
{/* Navigation */}
|
|
127
|
-
<nav className="flex-1 pt-[70px] pb-4 overflow-y-auto scrollbar-hide">
|
|
128
|
-
<ul className="space-y-1">
|
|
129
|
-
{navItems.map((item) => (
|
|
130
|
-
<MenuItem
|
|
131
|
-
key={item.path}
|
|
132
|
-
item={item}
|
|
133
|
-
isExpanded={isExpanded}
|
|
134
|
-
pathname={pathname}
|
|
135
|
-
/>
|
|
136
|
-
))}
|
|
137
|
-
</ul>
|
|
138
|
-
</nav>
|
|
139
|
-
</aside>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
import { useRef, useCallback } from 'react'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Icons } from '@/components/ui/icons'
|
|
8
|
+
import { useSidebar } from './sidebar-context'
|
|
9
|
+
|
|
10
|
+
interface NavItem {
|
|
11
|
+
path: string
|
|
12
|
+
name: string
|
|
13
|
+
icon: keyof typeof Icons
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const navItems: NavItem[] = [
|
|
17
|
+
{
|
|
18
|
+
path: '/dashboard',
|
|
19
|
+
name: 'Dashboard',
|
|
20
|
+
icon: 'Dashboard',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
path: '/users',
|
|
24
|
+
name: 'Usuarios',
|
|
25
|
+
icon: 'Users',
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
function MenuItem({
|
|
30
|
+
item,
|
|
31
|
+
isExpanded,
|
|
32
|
+
pathname,
|
|
33
|
+
}: {
|
|
34
|
+
item: NavItem
|
|
35
|
+
isExpanded: boolean
|
|
36
|
+
pathname: string
|
|
37
|
+
}) {
|
|
38
|
+
const IconComponent = Icons[item.icon]
|
|
39
|
+
const isActive = pathname === item.path || pathname.startsWith(item.path + '/')
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<li className="relative">
|
|
43
|
+
<Link
|
|
44
|
+
href={item.path}
|
|
45
|
+
className={cn(
|
|
46
|
+
'h-[40px] flex items-center gap-3',
|
|
47
|
+
'mx-[10px] px-[10px]',
|
|
48
|
+
'rounded-md',
|
|
49
|
+
'transition-all duration-150',
|
|
50
|
+
!isActive && 'hover:bg-accent',
|
|
51
|
+
isActive && [
|
|
52
|
+
'bg-accent',
|
|
53
|
+
'border border-border',
|
|
54
|
+
]
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<div className="w-5 h-5 flex items-center justify-center flex-shrink-0">
|
|
58
|
+
<IconComponent
|
|
59
|
+
size={20}
|
|
60
|
+
className={cn(
|
|
61
|
+
'transition-colors',
|
|
62
|
+
isActive ? 'text-foreground' : 'text-muted-foreground'
|
|
63
|
+
)}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
{isExpanded && (
|
|
67
|
+
<span
|
|
68
|
+
className={cn(
|
|
69
|
+
'text-sm whitespace-nowrap',
|
|
70
|
+
'transition-all duration-150',
|
|
71
|
+
isActive ? 'font-medium text-foreground' : 'text-muted-foreground'
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{item.name}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
</Link>
|
|
78
|
+
</li>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function Sidebar() {
|
|
83
|
+
const { isExpanded, setIsExpanded } = useSidebar()
|
|
84
|
+
const pathname = usePathname()
|
|
85
|
+
const sidebarRef = useRef<HTMLDivElement>(null)
|
|
86
|
+
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
87
|
+
|
|
88
|
+
const handleMouseEnter = useCallback(() => {
|
|
89
|
+
if (hoverTimeoutRef.current) {
|
|
90
|
+
clearTimeout(hoverTimeoutRef.current)
|
|
91
|
+
}
|
|
92
|
+
setIsExpanded(true)
|
|
93
|
+
}, [setIsExpanded])
|
|
94
|
+
|
|
95
|
+
const handleMouseLeave = useCallback(() => {
|
|
96
|
+
hoverTimeoutRef.current = setTimeout(() => {
|
|
97
|
+
setIsExpanded(false)
|
|
98
|
+
}, 100)
|
|
99
|
+
}, [setIsExpanded])
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<aside
|
|
103
|
+
ref={sidebarRef}
|
|
104
|
+
className={cn(
|
|
105
|
+
'h-screen flex-shrink-0 flex-col justify-between fixed top-0 left-0 hidden md:flex z-50',
|
|
106
|
+
'bg-background border-r border-border',
|
|
107
|
+
'transition-[width] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]',
|
|
108
|
+
isExpanded ? 'w-[240px]' : 'w-[70px]'
|
|
109
|
+
)}
|
|
110
|
+
onMouseEnter={handleMouseEnter}
|
|
111
|
+
onMouseLeave={handleMouseLeave}
|
|
112
|
+
>
|
|
113
|
+
{/* Header / Logo */}
|
|
114
|
+
<div
|
|
115
|
+
className={cn(
|
|
116
|
+
"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)]",
|
|
117
|
+
isExpanded ? "w-full" : "w-[69px]"
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
<Link href="/dashboard" className="absolute left-[22px] transition-none">
|
|
121
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
122
|
+
<img src="/logolft.svg" alt="Logo" className="h-7 w-7" />
|
|
123
|
+
</Link>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Navigation */}
|
|
127
|
+
<nav className="flex-1 pt-[70px] pb-4 overflow-y-auto scrollbar-hide">
|
|
128
|
+
<ul className="space-y-1">
|
|
129
|
+
{navItems.map((item) => (
|
|
130
|
+
<MenuItem
|
|
131
|
+
key={item.path}
|
|
132
|
+
item={item}
|
|
133
|
+
isExpanded={isExpanded}
|
|
134
|
+
pathname={pathname}
|
|
135
|
+
/>
|
|
136
|
+
))}
|
|
137
|
+
</ul>
|
|
138
|
+
</nav>
|
|
139
|
+
</aside>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Column } from '@tanstack/react-table'
|
|
4
|
-
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from 'lucide-react'
|
|
5
|
-
|
|
6
|
-
import { cn } from '@/lib/utils'
|
|
7
|
-
import { Button } from '@/components/ui/button'
|
|
8
|
-
import {
|
|
9
|
-
DropdownMenu,
|
|
10
|
-
DropdownMenuContent,
|
|
11
|
-
DropdownMenuItem,
|
|
12
|
-
DropdownMenuSeparator,
|
|
13
|
-
DropdownMenuTrigger,
|
|
14
|
-
} from '@/components/ui/dropdown-menu'
|
|
15
|
-
|
|
16
|
-
interface DataTableColumnHeaderProps<TData, TValue>
|
|
17
|
-
extends React.HTMLAttributes<HTMLDivElement> {
|
|
18
|
-
column: Column<TData, TValue>
|
|
19
|
-
title: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function DataTableColumnHeader<TData, TValue>({
|
|
23
|
-
column,
|
|
24
|
-
title,
|
|
25
|
-
className,
|
|
26
|
-
}: DataTableColumnHeaderProps<TData, TValue>) {
|
|
27
|
-
if (!column.getCanSort()) {
|
|
28
|
-
return <div className={cn(className)}>{title}</div>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className={cn('flex items-center space-x-2', className)}>
|
|
33
|
-
<DropdownMenu>
|
|
34
|
-
<DropdownMenuTrigger asChild>
|
|
35
|
-
<Button
|
|
36
|
-
variant="ghost"
|
|
37
|
-
size="sm"
|
|
38
|
-
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
|
39
|
-
>
|
|
40
|
-
<span>{title}</span>
|
|
41
|
-
{column.getIsSorted() === 'desc' ? (
|
|
42
|
-
<ArrowDown className="ml-2 h-4 w-4" />
|
|
43
|
-
) : column.getIsSorted() === 'asc' ? (
|
|
44
|
-
<ArrowUp className="ml-2 h-4 w-4" />
|
|
45
|
-
) : (
|
|
46
|
-
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
|
47
|
-
)}
|
|
48
|
-
</Button>
|
|
49
|
-
</DropdownMenuTrigger>
|
|
50
|
-
<DropdownMenuContent align="start">
|
|
51
|
-
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
|
52
|
-
<ArrowUp className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
53
|
-
Ascendente
|
|
54
|
-
</DropdownMenuItem>
|
|
55
|
-
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
|
56
|
-
<ArrowDown className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
57
|
-
Descendente
|
|
58
|
-
</DropdownMenuItem>
|
|
59
|
-
<DropdownMenuSeparator />
|
|
60
|
-
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
|
61
|
-
<EyeOff className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
62
|
-
Ocultar
|
|
63
|
-
</DropdownMenuItem>
|
|
64
|
-
</DropdownMenuContent>
|
|
65
|
-
</DropdownMenu>
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Column } from '@tanstack/react-table'
|
|
4
|
+
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuSeparator,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from '@/components/ui/dropdown-menu'
|
|
15
|
+
|
|
16
|
+
interface DataTableColumnHeaderProps<TData, TValue>
|
|
17
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
18
|
+
column: Column<TData, TValue>
|
|
19
|
+
title: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DataTableColumnHeader<TData, TValue>({
|
|
23
|
+
column,
|
|
24
|
+
title,
|
|
25
|
+
className,
|
|
26
|
+
}: DataTableColumnHeaderProps<TData, TValue>) {
|
|
27
|
+
if (!column.getCanSort()) {
|
|
28
|
+
return <div className={cn(className)}>{title}</div>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className={cn('flex items-center space-x-2', className)}>
|
|
33
|
+
<DropdownMenu>
|
|
34
|
+
<DropdownMenuTrigger asChild>
|
|
35
|
+
<Button
|
|
36
|
+
variant="ghost"
|
|
37
|
+
size="sm"
|
|
38
|
+
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
|
39
|
+
>
|
|
40
|
+
<span>{title}</span>
|
|
41
|
+
{column.getIsSorted() === 'desc' ? (
|
|
42
|
+
<ArrowDown className="ml-2 h-4 w-4" />
|
|
43
|
+
) : column.getIsSorted() === 'asc' ? (
|
|
44
|
+
<ArrowUp className="ml-2 h-4 w-4" />
|
|
45
|
+
) : (
|
|
46
|
+
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
|
47
|
+
)}
|
|
48
|
+
</Button>
|
|
49
|
+
</DropdownMenuTrigger>
|
|
50
|
+
<DropdownMenuContent align="start">
|
|
51
|
+
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
|
52
|
+
<ArrowUp className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
53
|
+
Ascendente
|
|
54
|
+
</DropdownMenuItem>
|
|
55
|
+
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
|
56
|
+
<ArrowDown className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
57
|
+
Descendente
|
|
58
|
+
</DropdownMenuItem>
|
|
59
|
+
<DropdownMenuSeparator />
|
|
60
|
+
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
|
61
|
+
<EyeOff className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
62
|
+
Ocultar
|
|
63
|
+
</DropdownMenuItem>
|
|
64
|
+
</DropdownMenuContent>
|
|
65
|
+
</DropdownMenu>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|