@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.
Files changed (133) hide show
  1. package/README.md +549 -549
  2. package/package.json +48 -48
  3. package/template/.claude/skills/anti-patterns.md +150 -0
  4. package/template/.claude/skills/drizzle-schema.md +178 -0
  5. package/template/.claude/skills/formatting.md +56 -0
  6. package/template/.claude/skills/module-architecture.md +143 -0
  7. package/template/.claude/skills/supabase-server-actions.md +199 -0
  8. package/template/.claude/skills/ui-patterns.md +161 -0
  9. package/template/CLAUDE.md +114 -1239
  10. package/template/drizzle.config.ts +12 -12
  11. package/template/eslint.config.mjs +16 -16
  12. package/template/gitignore +36 -36
  13. package/template/next.config.ts +7 -7
  14. package/template/package.json +86 -86
  15. package/template/postcss.config.mjs +7 -7
  16. package/template/proxy.ts +12 -12
  17. package/template/public/logolft.svg +11 -11
  18. package/template/src/app/(auth)/dashboard/dashboard-content.tsx +124 -124
  19. package/template/src/app/(auth)/dashboard/page.tsx +9 -9
  20. package/template/src/app/(auth)/layout.tsx +7 -7
  21. package/template/src/app/(auth)/users/page.tsx +9 -9
  22. package/template/src/app/(auth)/users/users-content.tsx +26 -26
  23. package/template/src/app/(public)/layout.tsx +7 -7
  24. package/template/src/app/(public)/login/page.tsx +17 -17
  25. package/template/src/app/api/webhooks/route.ts +20 -20
  26. package/template/src/app/globals.css +249 -249
  27. package/template/src/app/layout.tsx +37 -37
  28. package/template/src/app/page.tsx +5 -5
  29. package/template/src/app/providers.tsx +27 -27
  30. package/template/src/components/layout/main-content.tsx +28 -28
  31. package/template/src/components/layout/sidebar-context.tsx +33 -33
  32. package/template/src/components/layout/sidebar.tsx +141 -141
  33. package/template/src/components/tables/data-table-column-header.tsx +68 -68
  34. package/template/src/components/tables/data-table-date-filter.tsx +203 -203
  35. package/template/src/components/tables/data-table-faceted-filter.tsx +185 -185
  36. package/template/src/components/tables/data-table-filters-dropdown.tsx +130 -130
  37. package/template/src/components/tables/data-table-number-filter.tsx +295 -295
  38. package/template/src/components/tables/data-table-pagination.tsx +99 -99
  39. package/template/src/components/tables/data-table-toolbar.tsx +140 -140
  40. package/template/src/components/tables/data-table-view-options.tsx +63 -63
  41. package/template/src/components/tables/data-table.tsx +148 -148
  42. package/template/src/components/tables/index.ts +9 -9
  43. package/template/src/components/ui/accordion.tsx +58 -58
  44. package/template/src/components/ui/alert-dialog.tsx +165 -165
  45. package/template/src/components/ui/alert.tsx +66 -66
  46. package/template/src/components/ui/animations/index.ts +44 -44
  47. package/template/src/components/ui/avatar.tsx +55 -55
  48. package/template/src/components/ui/badge.tsx +50 -50
  49. package/template/src/components/ui/button.tsx +118 -118
  50. package/template/src/components/ui/calendar.tsx +220 -220
  51. package/template/src/components/ui/card.tsx +113 -113
  52. package/template/src/components/ui/checkbox.tsx +38 -38
  53. package/template/src/components/ui/collapsible.tsx +33 -33
  54. package/template/src/components/ui/command.tsx +196 -196
  55. package/template/src/components/ui/dialog.tsx +156 -156
  56. package/template/src/components/ui/dropdown-menu.tsx +280 -280
  57. package/template/src/components/ui/form.tsx +171 -171
  58. package/template/src/components/ui/icons.tsx +167 -167
  59. package/template/src/components/ui/input.tsx +28 -28
  60. package/template/src/components/ui/label.tsx +25 -25
  61. package/template/src/components/ui/motion.tsx +197 -197
  62. package/template/src/components/ui/page-transition.tsx +166 -166
  63. package/template/src/components/ui/popover.tsx +59 -59
  64. package/template/src/components/ui/progress.tsx +32 -32
  65. package/template/src/components/ui/radio-group.tsx +45 -45
  66. package/template/src/components/ui/scroll-area.tsx +63 -63
  67. package/template/src/components/ui/select.tsx +208 -208
  68. package/template/src/components/ui/separator.tsx +28 -28
  69. package/template/src/components/ui/sheet.tsx +170 -170
  70. package/template/src/components/ui/sidebar.tsx +726 -726
  71. package/template/src/components/ui/skeleton.tsx +15 -15
  72. package/template/src/components/ui/slider.tsx +58 -58
  73. package/template/src/components/ui/sonner.tsx +47 -47
  74. package/template/src/components/ui/spinner.tsx +27 -27
  75. package/template/src/components/ui/submit-button.tsx +47 -47
  76. package/template/src/components/ui/switch.tsx +31 -31
  77. package/template/src/components/ui/table.tsx +120 -120
  78. package/template/src/components/ui/tabs.tsx +75 -75
  79. package/template/src/components/ui/textarea.tsx +26 -26
  80. package/template/src/components/ui/tooltip.tsx +70 -70
  81. package/template/src/config/navigation.ts +59 -59
  82. package/template/src/config/roles.ts +27 -27
  83. package/template/src/config/site.ts +12 -12
  84. package/template/src/db/index.ts +12 -12
  85. package/template/src/db/schema/index.ts +1 -1
  86. package/template/src/db/schema/users.ts +16 -16
  87. package/template/src/db/seed.ts +39 -39
  88. package/template/src/hooks/index.ts +3 -3
  89. package/template/src/hooks/use-mobile.ts +21 -21
  90. package/template/src/hooks/useDataTable.ts +82 -82
  91. package/template/src/hooks/useDebounce.ts +49 -49
  92. package/template/src/hooks/useMediaQuery.ts +36 -36
  93. package/template/src/lib/date/config.ts +36 -36
  94. package/template/src/lib/date/formatters.ts +127 -127
  95. package/template/src/lib/date/index.ts +26 -26
  96. package/template/src/lib/excel/exporter.ts +89 -89
  97. package/template/src/lib/excel/index.ts +14 -14
  98. package/template/src/lib/excel/parser.ts +96 -96
  99. package/template/src/lib/query-client.ts +35 -35
  100. package/template/src/lib/supabase/admin.ts +23 -23
  101. package/template/src/lib/supabase/client.ts +11 -11
  102. package/template/src/lib/supabase/proxy.ts +67 -67
  103. package/template/src/lib/supabase/server.ts +38 -38
  104. package/template/src/lib/supabase/types.ts +53 -53
  105. package/template/src/lib/utils.ts +6 -6
  106. package/template/src/lib/validations/common.ts +75 -75
  107. package/template/src/lib/validations/index.ts +20 -20
  108. package/template/src/modules/auth/actions/auth-actions.ts +59 -59
  109. package/template/src/modules/auth/components/login-form.tsx +68 -68
  110. package/template/src/modules/auth/hooks/useAuth.ts +38 -38
  111. package/template/src/modules/auth/hooks/useAuthMutations.ts +43 -43
  112. package/template/src/modules/auth/hooks/useAuthQueries.ts +43 -43
  113. package/template/src/modules/auth/index.ts +12 -12
  114. package/template/src/modules/auth/schemas/auth.schema.ts +32 -32
  115. package/template/src/modules/auth/stores/useAuthStore.ts +37 -37
  116. package/template/src/modules/users/actions/users-actions.ts +166 -166
  117. package/template/src/modules/users/columns.tsx +106 -106
  118. package/template/src/modules/users/components/users-list.tsx +48 -48
  119. package/template/src/modules/users/hooks/useUsers.ts +39 -39
  120. package/template/src/modules/users/hooks/useUsersMutations.ts +55 -55
  121. package/template/src/modules/users/hooks/useUsersQueries.ts +35 -35
  122. package/template/src/modules/users/index.ts +30 -30
  123. package/template/src/modules/users/schemas/users.schema.ts +51 -51
  124. package/template/src/modules/users/stores/useUsersStore.ts +60 -60
  125. package/template/src/modules/users/types/auth-user.types.ts +42 -42
  126. package/template/src/modules/users/utils/user-mapper.ts +32 -32
  127. package/template/src/stores/index.ts +1 -1
  128. package/template/src/stores/useUiStore.ts +55 -55
  129. package/template/src/types/api.ts +28 -28
  130. package/template/src/types/index.ts +2 -2
  131. package/template/src/types/table.ts +34 -34
  132. package/template/supabase/config.toml +94 -94
  133. package/template/tsconfig.json +42 -42
@@ -1,203 +1,203 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import { Column } from '@tanstack/react-table'
5
- import { CalendarIcon, X } from 'lucide-react'
6
- import { DateRange } from 'react-day-picker'
7
-
8
- import { cn } from '@/lib/utils'
9
- import { formatDateShort } from '@/lib/date'
10
- import { Button } from '@/components/ui/button'
11
- import { Calendar } from '@/components/ui/calendar'
12
- import {
13
- Popover,
14
- PopoverContent,
15
- PopoverTrigger,
16
- } from '@/components/ui/popover'
17
-
18
- interface DataTableDateFilterProps<TData> {
19
- column?: Column<TData, unknown>
20
- title?: string
21
- }
22
-
23
- export function DataTableDateFilter<TData>({
24
- column,
25
- title = 'Fecha',
26
- }: DataTableDateFilterProps<TData>) {
27
- const filterValue = column?.getFilterValue() as DateRange | undefined
28
- const [open, setOpen] = React.useState(false)
29
-
30
- // Sincronizar estado local con el filtro de la columna
31
- // Esto permite que "Limpiar" del toolbar resetee este filtro
32
- React.useEffect(() => {
33
- if (!filterValue) {
34
- setDate(undefined)
35
- }
36
- }, [filterValue])
37
-
38
- const [date, setDate] = React.useState<DateRange | undefined>(filterValue)
39
-
40
- const handleSelect = (range: DateRange | undefined) => {
41
- setDate(range)
42
- // No aplicamos el filtro inmediatamente, esperamos al botón Aplicar
43
- }
44
-
45
- const handleApply = () => {
46
- column?.setFilterValue(date)
47
- setOpen(false)
48
- }
49
-
50
- const handleClear = () => {
51
- setDate(undefined)
52
- column?.setFilterValue(undefined)
53
- }
54
-
55
- const handleOpenChange = (isOpen: boolean) => {
56
- if (!isOpen) {
57
- // Al cerrar sin aplicar, resetear al valor actual del filtro
58
- setDate(filterValue)
59
- }
60
- setOpen(isOpen)
61
- }
62
-
63
- const hasFilter = date?.from || date?.to
64
-
65
- return (
66
- <Popover open={open} onOpenChange={handleOpenChange}>
67
- <PopoverTrigger asChild>
68
- <Button
69
- variant="outline"
70
- size="sm"
71
- className={cn(
72
- 'h-8 border-dashed',
73
- hasFilter && 'border-solid'
74
- )}
75
- >
76
- <CalendarIcon className="mr-2 h-4 w-4" />
77
- {hasFilter ? (
78
- <span className="flex items-center gap-1">
79
- {date?.from && formatDateShort(date.from)}
80
- {date?.to && ` - ${formatDateShort(date.to)}`}
81
- </span>
82
- ) : (
83
- title
84
- )}
85
- {hasFilter && (
86
- <span
87
- role="button"
88
- tabIndex={0}
89
- onClick={(e) => {
90
- e.stopPropagation()
91
- handleClear()
92
- }}
93
- onKeyDown={(e) => {
94
- if (e.key === 'Enter' || e.key === ' ') {
95
- e.stopPropagation()
96
- handleClear()
97
- }
98
- }}
99
- className="ml-2 rounded-full p-0.5 hover:bg-muted"
100
- >
101
- <X className="h-3 w-3" />
102
- </span>
103
- )}
104
- </Button>
105
- </PopoverTrigger>
106
- <PopoverContent
107
- className="w-auto p-0"
108
- side="bottom"
109
- align="start"
110
- sideOffset={4}
111
- >
112
- <div className="flex flex-col">
113
- <div className="flex items-center gap-2 border-b border-border p-3">
114
- <Button
115
- variant="ghost"
116
- size="sm"
117
- className="h-7 text-xs"
118
- onClick={() => {
119
- const today = new Date()
120
- setDate({ from: today, to: today })
121
- }}
122
- >
123
- Hoy
124
- </Button>
125
- <Button
126
- variant="ghost"
127
- size="sm"
128
- className="h-7 text-xs"
129
- onClick={() => {
130
- const today = new Date()
131
- const weekAgo = new Date(today)
132
- weekAgo.setDate(today.getDate() - 7)
133
- setDate({ from: weekAgo, to: today })
134
- }}
135
- >
136
- Última semana
137
- </Button>
138
- <Button
139
- variant="ghost"
140
- size="sm"
141
- className="h-7 text-xs"
142
- onClick={() => {
143
- const today = new Date()
144
- const monthAgo = new Date(today)
145
- monthAgo.setMonth(today.getMonth() - 1)
146
- setDate({ from: monthAgo, to: today })
147
- }}
148
- >
149
- Último mes
150
- </Button>
151
- </div>
152
- <Calendar
153
- mode="range"
154
- defaultMonth={date?.from}
155
- selected={date}
156
- onSelect={handleSelect}
157
- numberOfMonths={2}
158
- />
159
- <div className="flex gap-2 border-t border-border p-3">
160
- <Button
161
- variant="outline"
162
- size="sm"
163
- className="flex-1"
164
- onClick={handleClear}
165
- >
166
- Limpiar
167
- </Button>
168
- <Button
169
- size="sm"
170
- className="flex-1"
171
- onClick={handleApply}
172
- >
173
- Aplicar
174
- </Button>
175
- </div>
176
- </div>
177
- </PopoverContent>
178
- </Popover>
179
- )
180
- }
181
-
182
- // Filter function para usar con TanStack Table
183
- export function dateRangeFilterFn<TData>(
184
- row: { getValue: (id: string) => unknown },
185
- columnId: string,
186
- filterValue: DateRange | undefined
187
- ): boolean {
188
- if (!filterValue?.from) return true
189
-
190
- const cellValue = row.getValue(columnId)
191
- if (!cellValue) return false
192
-
193
- const date = new Date(cellValue as string)
194
- const from = filterValue.from
195
- const to = filterValue.to || from
196
-
197
- // Reset hours for comparison
198
- const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate())
199
- const fromOnly = new Date(from.getFullYear(), from.getMonth(), from.getDate())
200
- const toOnly = new Date(to.getFullYear(), to.getMonth(), to.getDate())
201
-
202
- return dateOnly >= fromOnly && dateOnly <= toOnly
203
- }
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Column } from '@tanstack/react-table'
5
+ import { CalendarIcon, X } from 'lucide-react'
6
+ import { DateRange } from 'react-day-picker'
7
+
8
+ import { cn } from '@/lib/utils'
9
+ import { formatDateShort } from '@/lib/date'
10
+ import { Button } from '@/components/ui/button'
11
+ import { Calendar } from '@/components/ui/calendar'
12
+ import {
13
+ Popover,
14
+ PopoverContent,
15
+ PopoverTrigger,
16
+ } from '@/components/ui/popover'
17
+
18
+ interface DataTableDateFilterProps<TData> {
19
+ column?: Column<TData, unknown>
20
+ title?: string
21
+ }
22
+
23
+ export function DataTableDateFilter<TData>({
24
+ column,
25
+ title = 'Fecha',
26
+ }: DataTableDateFilterProps<TData>) {
27
+ const filterValue = column?.getFilterValue() as DateRange | undefined
28
+ const [open, setOpen] = React.useState(false)
29
+
30
+ // Sincronizar estado local con el filtro de la columna
31
+ // Esto permite que "Limpiar" del toolbar resetee este filtro
32
+ React.useEffect(() => {
33
+ if (!filterValue) {
34
+ setDate(undefined)
35
+ }
36
+ }, [filterValue])
37
+
38
+ const [date, setDate] = React.useState<DateRange | undefined>(filterValue)
39
+
40
+ const handleSelect = (range: DateRange | undefined) => {
41
+ setDate(range)
42
+ // No aplicamos el filtro inmediatamente, esperamos al botón Aplicar
43
+ }
44
+
45
+ const handleApply = () => {
46
+ column?.setFilterValue(date)
47
+ setOpen(false)
48
+ }
49
+
50
+ const handleClear = () => {
51
+ setDate(undefined)
52
+ column?.setFilterValue(undefined)
53
+ }
54
+
55
+ const handleOpenChange = (isOpen: boolean) => {
56
+ if (!isOpen) {
57
+ // Al cerrar sin aplicar, resetear al valor actual del filtro
58
+ setDate(filterValue)
59
+ }
60
+ setOpen(isOpen)
61
+ }
62
+
63
+ const hasFilter = date?.from || date?.to
64
+
65
+ return (
66
+ <Popover open={open} onOpenChange={handleOpenChange}>
67
+ <PopoverTrigger asChild>
68
+ <Button
69
+ variant="outline"
70
+ size="sm"
71
+ className={cn(
72
+ 'h-8 border-dashed',
73
+ hasFilter && 'border-solid'
74
+ )}
75
+ >
76
+ <CalendarIcon className="mr-2 h-4 w-4" />
77
+ {hasFilter ? (
78
+ <span className="flex items-center gap-1">
79
+ {date?.from && formatDateShort(date.from)}
80
+ {date?.to && ` - ${formatDateShort(date.to)}`}
81
+ </span>
82
+ ) : (
83
+ title
84
+ )}
85
+ {hasFilter && (
86
+ <span
87
+ role="button"
88
+ tabIndex={0}
89
+ onClick={(e) => {
90
+ e.stopPropagation()
91
+ handleClear()
92
+ }}
93
+ onKeyDown={(e) => {
94
+ if (e.key === 'Enter' || e.key === ' ') {
95
+ e.stopPropagation()
96
+ handleClear()
97
+ }
98
+ }}
99
+ className="ml-2 rounded-full p-0.5 hover:bg-muted"
100
+ >
101
+ <X className="h-3 w-3" />
102
+ </span>
103
+ )}
104
+ </Button>
105
+ </PopoverTrigger>
106
+ <PopoverContent
107
+ className="w-auto p-0"
108
+ side="bottom"
109
+ align="start"
110
+ sideOffset={4}
111
+ >
112
+ <div className="flex flex-col">
113
+ <div className="flex items-center gap-2 border-b border-border p-3">
114
+ <Button
115
+ variant="ghost"
116
+ size="sm"
117
+ className="h-7 text-xs"
118
+ onClick={() => {
119
+ const today = new Date()
120
+ setDate({ from: today, to: today })
121
+ }}
122
+ >
123
+ Hoy
124
+ </Button>
125
+ <Button
126
+ variant="ghost"
127
+ size="sm"
128
+ className="h-7 text-xs"
129
+ onClick={() => {
130
+ const today = new Date()
131
+ const weekAgo = new Date(today)
132
+ weekAgo.setDate(today.getDate() - 7)
133
+ setDate({ from: weekAgo, to: today })
134
+ }}
135
+ >
136
+ Última semana
137
+ </Button>
138
+ <Button
139
+ variant="ghost"
140
+ size="sm"
141
+ className="h-7 text-xs"
142
+ onClick={() => {
143
+ const today = new Date()
144
+ const monthAgo = new Date(today)
145
+ monthAgo.setMonth(today.getMonth() - 1)
146
+ setDate({ from: monthAgo, to: today })
147
+ }}
148
+ >
149
+ Último mes
150
+ </Button>
151
+ </div>
152
+ <Calendar
153
+ mode="range"
154
+ defaultMonth={date?.from}
155
+ selected={date}
156
+ onSelect={handleSelect}
157
+ numberOfMonths={2}
158
+ />
159
+ <div className="flex gap-2 border-t border-border p-3">
160
+ <Button
161
+ variant="outline"
162
+ size="sm"
163
+ className="flex-1"
164
+ onClick={handleClear}
165
+ >
166
+ Limpiar
167
+ </Button>
168
+ <Button
169
+ size="sm"
170
+ className="flex-1"
171
+ onClick={handleApply}
172
+ >
173
+ Aplicar
174
+ </Button>
175
+ </div>
176
+ </div>
177
+ </PopoverContent>
178
+ </Popover>
179
+ )
180
+ }
181
+
182
+ // Filter function para usar con TanStack Table
183
+ export function dateRangeFilterFn<TData>(
184
+ row: { getValue: (id: string) => unknown },
185
+ columnId: string,
186
+ filterValue: DateRange | undefined
187
+ ): boolean {
188
+ if (!filterValue?.from) return true
189
+
190
+ const cellValue = row.getValue(columnId)
191
+ if (!cellValue) return false
192
+
193
+ const date = new Date(cellValue as string)
194
+ const from = filterValue.from
195
+ const to = filterValue.to || from
196
+
197
+ // Reset hours for comparison
198
+ const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate())
199
+ const fromOnly = new Date(from.getFullYear(), from.getMonth(), from.getDate())
200
+ const toOnly = new Date(to.getFullYear(), to.getMonth(), to.getDate())
201
+
202
+ return dateOnly >= fromOnly && dateOnly <= toOnly
203
+ }