@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,99 +1,99 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Table } from '@tanstack/react-table'
|
|
4
|
-
import {
|
|
5
|
-
ChevronLeft,
|
|
6
|
-
ChevronRight,
|
|
7
|
-
ChevronsLeft,
|
|
8
|
-
ChevronsRight,
|
|
9
|
-
} from 'lucide-react'
|
|
10
|
-
|
|
11
|
-
import { Button } from '@/components/ui/button'
|
|
12
|
-
import {
|
|
13
|
-
Select,
|
|
14
|
-
SelectContent,
|
|
15
|
-
SelectItem,
|
|
16
|
-
SelectTrigger,
|
|
17
|
-
SelectValue,
|
|
18
|
-
} from '@/components/ui/select'
|
|
19
|
-
|
|
20
|
-
interface DataTablePaginationProps<TData> {
|
|
21
|
-
table: Table<TData>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function DataTablePagination<TData>({
|
|
25
|
-
table,
|
|
26
|
-
}: DataTablePaginationProps<TData>) {
|
|
27
|
-
return (
|
|
28
|
-
<div className="flex items-center justify-between px-2">
|
|
29
|
-
<div className="flex-1 text-sm text-muted-foreground">
|
|
30
|
-
{table.getFilteredSelectedRowModel().rows.length} de{' '}
|
|
31
|
-
{table.getFilteredRowModel().rows.length} fila(s) seleccionada(s).
|
|
32
|
-
</div>
|
|
33
|
-
<div className="flex items-center space-x-6 lg:space-x-8">
|
|
34
|
-
<div className="flex items-center space-x-2">
|
|
35
|
-
<p className="text-sm font-medium">Filas por página</p>
|
|
36
|
-
<Select
|
|
37
|
-
value={`${table.getState().pagination.pageSize}`}
|
|
38
|
-
onValueChange={(value) => {
|
|
39
|
-
table.setPageSize(Number(value))
|
|
40
|
-
}}
|
|
41
|
-
>
|
|
42
|
-
<SelectTrigger className="h-8 w-[70px]">
|
|
43
|
-
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
|
44
|
-
</SelectTrigger>
|
|
45
|
-
<SelectContent side="top">
|
|
46
|
-
{[10, 20, 30, 40, 50].map((pageSize) => (
|
|
47
|
-
<SelectItem key={pageSize} value={`${pageSize}`}>
|
|
48
|
-
{pageSize}
|
|
49
|
-
</SelectItem>
|
|
50
|
-
))}
|
|
51
|
-
</SelectContent>
|
|
52
|
-
</Select>
|
|
53
|
-
</div>
|
|
54
|
-
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
|
55
|
-
Página {table.getState().pagination.pageIndex + 1} de{' '}
|
|
56
|
-
{table.getPageCount()}
|
|
57
|
-
</div>
|
|
58
|
-
<div className="flex items-center space-x-2">
|
|
59
|
-
<Button
|
|
60
|
-
variant="outline"
|
|
61
|
-
className="hidden h-8 w-8 p-0 lg:flex"
|
|
62
|
-
onClick={() => table.setPageIndex(0)}
|
|
63
|
-
disabled={!table.getCanPreviousPage()}
|
|
64
|
-
>
|
|
65
|
-
<span className="sr-only">Ir a primera página</span>
|
|
66
|
-
<ChevronsLeft className="h-4 w-4" />
|
|
67
|
-
</Button>
|
|
68
|
-
<Button
|
|
69
|
-
variant="outline"
|
|
70
|
-
className="h-8 w-8 p-0"
|
|
71
|
-
onClick={() => table.previousPage()}
|
|
72
|
-
disabled={!table.getCanPreviousPage()}
|
|
73
|
-
>
|
|
74
|
-
<span className="sr-only">Ir a página anterior</span>
|
|
75
|
-
<ChevronLeft className="h-4 w-4" />
|
|
76
|
-
</Button>
|
|
77
|
-
<Button
|
|
78
|
-
variant="outline"
|
|
79
|
-
className="h-8 w-8 p-0"
|
|
80
|
-
onClick={() => table.nextPage()}
|
|
81
|
-
disabled={!table.getCanNextPage()}
|
|
82
|
-
>
|
|
83
|
-
<span className="sr-only">Ir a página siguiente</span>
|
|
84
|
-
<ChevronRight className="h-4 w-4" />
|
|
85
|
-
</Button>
|
|
86
|
-
<Button
|
|
87
|
-
variant="outline"
|
|
88
|
-
className="hidden h-8 w-8 p-0 lg:flex"
|
|
89
|
-
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
90
|
-
disabled={!table.getCanNextPage()}
|
|
91
|
-
>
|
|
92
|
-
<span className="sr-only">Ir a última página</span>
|
|
93
|
-
<ChevronsRight className="h-4 w-4" />
|
|
94
|
-
</Button>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Table } from '@tanstack/react-table'
|
|
4
|
+
import {
|
|
5
|
+
ChevronLeft,
|
|
6
|
+
ChevronRight,
|
|
7
|
+
ChevronsLeft,
|
|
8
|
+
ChevronsRight,
|
|
9
|
+
} from 'lucide-react'
|
|
10
|
+
|
|
11
|
+
import { Button } from '@/components/ui/button'
|
|
12
|
+
import {
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
} from '@/components/ui/select'
|
|
19
|
+
|
|
20
|
+
interface DataTablePaginationProps<TData> {
|
|
21
|
+
table: Table<TData>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function DataTablePagination<TData>({
|
|
25
|
+
table,
|
|
26
|
+
}: DataTablePaginationProps<TData>) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex items-center justify-between px-2">
|
|
29
|
+
<div className="flex-1 text-sm text-muted-foreground">
|
|
30
|
+
{table.getFilteredSelectedRowModel().rows.length} de{' '}
|
|
31
|
+
{table.getFilteredRowModel().rows.length} fila(s) seleccionada(s).
|
|
32
|
+
</div>
|
|
33
|
+
<div className="flex items-center space-x-6 lg:space-x-8">
|
|
34
|
+
<div className="flex items-center space-x-2">
|
|
35
|
+
<p className="text-sm font-medium">Filas por página</p>
|
|
36
|
+
<Select
|
|
37
|
+
value={`${table.getState().pagination.pageSize}`}
|
|
38
|
+
onValueChange={(value) => {
|
|
39
|
+
table.setPageSize(Number(value))
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<SelectTrigger className="h-8 w-[70px]">
|
|
43
|
+
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
|
44
|
+
</SelectTrigger>
|
|
45
|
+
<SelectContent side="top">
|
|
46
|
+
{[10, 20, 30, 40, 50].map((pageSize) => (
|
|
47
|
+
<SelectItem key={pageSize} value={`${pageSize}`}>
|
|
48
|
+
{pageSize}
|
|
49
|
+
</SelectItem>
|
|
50
|
+
))}
|
|
51
|
+
</SelectContent>
|
|
52
|
+
</Select>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
|
55
|
+
Página {table.getState().pagination.pageIndex + 1} de{' '}
|
|
56
|
+
{table.getPageCount()}
|
|
57
|
+
</div>
|
|
58
|
+
<div className="flex items-center space-x-2">
|
|
59
|
+
<Button
|
|
60
|
+
variant="outline"
|
|
61
|
+
className="hidden h-8 w-8 p-0 lg:flex"
|
|
62
|
+
onClick={() => table.setPageIndex(0)}
|
|
63
|
+
disabled={!table.getCanPreviousPage()}
|
|
64
|
+
>
|
|
65
|
+
<span className="sr-only">Ir a primera página</span>
|
|
66
|
+
<ChevronsLeft className="h-4 w-4" />
|
|
67
|
+
</Button>
|
|
68
|
+
<Button
|
|
69
|
+
variant="outline"
|
|
70
|
+
className="h-8 w-8 p-0"
|
|
71
|
+
onClick={() => table.previousPage()}
|
|
72
|
+
disabled={!table.getCanPreviousPage()}
|
|
73
|
+
>
|
|
74
|
+
<span className="sr-only">Ir a página anterior</span>
|
|
75
|
+
<ChevronLeft className="h-4 w-4" />
|
|
76
|
+
</Button>
|
|
77
|
+
<Button
|
|
78
|
+
variant="outline"
|
|
79
|
+
className="h-8 w-8 p-0"
|
|
80
|
+
onClick={() => table.nextPage()}
|
|
81
|
+
disabled={!table.getCanNextPage()}
|
|
82
|
+
>
|
|
83
|
+
<span className="sr-only">Ir a página siguiente</span>
|
|
84
|
+
<ChevronRight className="h-4 w-4" />
|
|
85
|
+
</Button>
|
|
86
|
+
<Button
|
|
87
|
+
variant="outline"
|
|
88
|
+
className="hidden h-8 w-8 p-0 lg:flex"
|
|
89
|
+
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
90
|
+
disabled={!table.getCanNextPage()}
|
|
91
|
+
>
|
|
92
|
+
<span className="sr-only">Ir a última página</span>
|
|
93
|
+
<ChevronsRight className="h-4 w-4" />
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import { Table } from '@tanstack/react-table'
|
|
5
|
-
import { Filter, X } from 'lucide-react'
|
|
6
|
-
|
|
7
|
-
import { Button } from '@/components/ui/button'
|
|
8
|
-
import { Input } from '@/components/ui/input'
|
|
9
|
-
import { DataTableFacetedFilter } from './data-table-faceted-filter'
|
|
10
|
-
import { DataTableDateFilter } from './data-table-date-filter'
|
|
11
|
-
import { DataTableNumberFilter } from './data-table-number-filter'
|
|
12
|
-
import { DataTableFiltersDropdown } from './data-table-filters-dropdown'
|
|
13
|
-
import type { FilterConfig } from './data-table'
|
|
14
|
-
|
|
15
|
-
interface DataTableToolbarProps<TData> {
|
|
16
|
-
table: Table<TData>
|
|
17
|
-
searchKey?: string
|
|
18
|
-
searchPlaceholder?: string
|
|
19
|
-
filters?: FilterConfig[]
|
|
20
|
-
/** Modo de visualización: 'dropdown' agrupa filtros, 'inline' los muestra en línea, 'inline-collapsible' los muestra en línea pero ocultables */
|
|
21
|
-
filterMode?: 'dropdown' | 'inline' | 'inline-collapsible'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function DataTableToolbar<TData>({
|
|
25
|
-
table,
|
|
26
|
-
searchKey,
|
|
27
|
-
searchPlaceholder = 'Buscar...',
|
|
28
|
-
filters,
|
|
29
|
-
filterMode = 'dropdown',
|
|
30
|
-
}: DataTableToolbarProps<TData>) {
|
|
31
|
-
const isFiltered = table.getState().columnFilters.length > 0
|
|
32
|
-
const [showFilters, setShowFilters] = React.useState(false)
|
|
33
|
-
|
|
34
|
-
const renderFilters = () => {
|
|
35
|
-
return filters?.map((filter) => {
|
|
36
|
-
const column = table.getColumn(filter.columnId)
|
|
37
|
-
if (!column) return null
|
|
38
|
-
|
|
39
|
-
if (filter.type === 'faceted' && filter.options) {
|
|
40
|
-
return (
|
|
41
|
-
<DataTableFacetedFilter
|
|
42
|
-
key={filter.columnId}
|
|
43
|
-
column={column}
|
|
44
|
-
title={filter.title}
|
|
45
|
-
options={filter.options}
|
|
46
|
-
/>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (filter.type === 'date-range') {
|
|
51
|
-
return (
|
|
52
|
-
<DataTableDateFilter
|
|
53
|
-
key={filter.columnId}
|
|
54
|
-
column={column}
|
|
55
|
-
title={filter.title}
|
|
56
|
-
/>
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (filter.type === 'number-range') {
|
|
61
|
-
return (
|
|
62
|
-
<DataTableNumberFilter
|
|
63
|
-
key={filter.columnId}
|
|
64
|
-
column={column}
|
|
65
|
-
title={filter.title}
|
|
66
|
-
format={filter.format}
|
|
67
|
-
currencySymbol={filter.currencySymbol}
|
|
68
|
-
step={filter.step}
|
|
69
|
-
/>
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return null
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className="flex flex-col gap-2">
|
|
79
|
-
<div className="flex items-center justify-between">
|
|
80
|
-
<div className="flex flex-1 flex-wrap items-center gap-2">
|
|
81
|
-
{searchKey && (
|
|
82
|
-
<Input
|
|
83
|
-
placeholder={searchPlaceholder}
|
|
84
|
-
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ''}
|
|
85
|
-
onChange={(event) =>
|
|
86
|
-
table.getColumn(searchKey)?.setFilterValue(event.target.value)
|
|
87
|
-
}
|
|
88
|
-
className="h-8 w-[150px] lg:w-[250px]"
|
|
89
|
-
/>
|
|
90
|
-
)}
|
|
91
|
-
|
|
92
|
-
{/* Modo dropdown: un solo botón que agrupa filtros */}
|
|
93
|
-
{filterMode === 'dropdown' && filters && filters.length > 0 && (
|
|
94
|
-
<DataTableFiltersDropdown table={table} filters={filters} />
|
|
95
|
-
)}
|
|
96
|
-
|
|
97
|
-
{/* Modo inline: filtros visibles individualmente */}
|
|
98
|
-
{filterMode === 'inline' && renderFilters()}
|
|
99
|
-
|
|
100
|
-
{/* Modo inline-collapsible: botón para mostrar/ocultar filtros */}
|
|
101
|
-
{filterMode === 'inline-collapsible' && filters && filters.length > 0 && (
|
|
102
|
-
<Button
|
|
103
|
-
variant="outline"
|
|
104
|
-
size="sm"
|
|
105
|
-
className="h-8 border-dashed"
|
|
106
|
-
onClick={() => setShowFilters(!showFilters)}
|
|
107
|
-
>
|
|
108
|
-
<Filter className="mr-2 h-4 w-4" />
|
|
109
|
-
Filtros
|
|
110
|
-
{isFiltered && (
|
|
111
|
-
<span className="ml-2 rounded-full bg-primary px-1.5 py-0.5 text-xs text-primary-foreground">
|
|
112
|
-
{table.getState().columnFilters.length}
|
|
113
|
-
</span>
|
|
114
|
-
)}
|
|
115
|
-
</Button>
|
|
116
|
-
)}
|
|
117
|
-
|
|
118
|
-
{/* Botón limpiar para modos inline */}
|
|
119
|
-
{(filterMode === 'inline' || (filterMode === 'inline-collapsible' && showFilters)) && isFiltered && (
|
|
120
|
-
<Button
|
|
121
|
-
variant="ghost"
|
|
122
|
-
onClick={() => table.resetColumnFilters()}
|
|
123
|
-
className="h-8 px-2 lg:px-3"
|
|
124
|
-
>
|
|
125
|
-
Limpiar
|
|
126
|
-
<X className="ml-2 h-4 w-4" />
|
|
127
|
-
</Button>
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
{/* Filtros inline colapsables - fila separada */}
|
|
133
|
-
{filterMode === 'inline-collapsible' && showFilters && filters && filters.length > 0 && (
|
|
134
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
135
|
-
{renderFilters()}
|
|
136
|
-
</div>
|
|
137
|
-
)}
|
|
138
|
-
</div>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Table } from '@tanstack/react-table'
|
|
5
|
+
import { Filter, X } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import { Input } from '@/components/ui/input'
|
|
9
|
+
import { DataTableFacetedFilter } from './data-table-faceted-filter'
|
|
10
|
+
import { DataTableDateFilter } from './data-table-date-filter'
|
|
11
|
+
import { DataTableNumberFilter } from './data-table-number-filter'
|
|
12
|
+
import { DataTableFiltersDropdown } from './data-table-filters-dropdown'
|
|
13
|
+
import type { FilterConfig } from './data-table'
|
|
14
|
+
|
|
15
|
+
interface DataTableToolbarProps<TData> {
|
|
16
|
+
table: Table<TData>
|
|
17
|
+
searchKey?: string
|
|
18
|
+
searchPlaceholder?: string
|
|
19
|
+
filters?: FilterConfig[]
|
|
20
|
+
/** Modo de visualización: 'dropdown' agrupa filtros, 'inline' los muestra en línea, 'inline-collapsible' los muestra en línea pero ocultables */
|
|
21
|
+
filterMode?: 'dropdown' | 'inline' | 'inline-collapsible'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function DataTableToolbar<TData>({
|
|
25
|
+
table,
|
|
26
|
+
searchKey,
|
|
27
|
+
searchPlaceholder = 'Buscar...',
|
|
28
|
+
filters,
|
|
29
|
+
filterMode = 'dropdown',
|
|
30
|
+
}: DataTableToolbarProps<TData>) {
|
|
31
|
+
const isFiltered = table.getState().columnFilters.length > 0
|
|
32
|
+
const [showFilters, setShowFilters] = React.useState(false)
|
|
33
|
+
|
|
34
|
+
const renderFilters = () => {
|
|
35
|
+
return filters?.map((filter) => {
|
|
36
|
+
const column = table.getColumn(filter.columnId)
|
|
37
|
+
if (!column) return null
|
|
38
|
+
|
|
39
|
+
if (filter.type === 'faceted' && filter.options) {
|
|
40
|
+
return (
|
|
41
|
+
<DataTableFacetedFilter
|
|
42
|
+
key={filter.columnId}
|
|
43
|
+
column={column}
|
|
44
|
+
title={filter.title}
|
|
45
|
+
options={filter.options}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (filter.type === 'date-range') {
|
|
51
|
+
return (
|
|
52
|
+
<DataTableDateFilter
|
|
53
|
+
key={filter.columnId}
|
|
54
|
+
column={column}
|
|
55
|
+
title={filter.title}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (filter.type === 'number-range') {
|
|
61
|
+
return (
|
|
62
|
+
<DataTableNumberFilter
|
|
63
|
+
key={filter.columnId}
|
|
64
|
+
column={column}
|
|
65
|
+
title={filter.title}
|
|
66
|
+
format={filter.format}
|
|
67
|
+
currencySymbol={filter.currencySymbol}
|
|
68
|
+
step={filter.step}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="flex flex-col gap-2">
|
|
79
|
+
<div className="flex items-center justify-between">
|
|
80
|
+
<div className="flex flex-1 flex-wrap items-center gap-2">
|
|
81
|
+
{searchKey && (
|
|
82
|
+
<Input
|
|
83
|
+
placeholder={searchPlaceholder}
|
|
84
|
+
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ''}
|
|
85
|
+
onChange={(event) =>
|
|
86
|
+
table.getColumn(searchKey)?.setFilterValue(event.target.value)
|
|
87
|
+
}
|
|
88
|
+
className="h-8 w-[150px] lg:w-[250px]"
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{/* Modo dropdown: un solo botón que agrupa filtros */}
|
|
93
|
+
{filterMode === 'dropdown' && filters && filters.length > 0 && (
|
|
94
|
+
<DataTableFiltersDropdown table={table} filters={filters} />
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* Modo inline: filtros visibles individualmente */}
|
|
98
|
+
{filterMode === 'inline' && renderFilters()}
|
|
99
|
+
|
|
100
|
+
{/* Modo inline-collapsible: botón para mostrar/ocultar filtros */}
|
|
101
|
+
{filterMode === 'inline-collapsible' && filters && filters.length > 0 && (
|
|
102
|
+
<Button
|
|
103
|
+
variant="outline"
|
|
104
|
+
size="sm"
|
|
105
|
+
className="h-8 border-dashed"
|
|
106
|
+
onClick={() => setShowFilters(!showFilters)}
|
|
107
|
+
>
|
|
108
|
+
<Filter className="mr-2 h-4 w-4" />
|
|
109
|
+
Filtros
|
|
110
|
+
{isFiltered && (
|
|
111
|
+
<span className="ml-2 rounded-full bg-primary px-1.5 py-0.5 text-xs text-primary-foreground">
|
|
112
|
+
{table.getState().columnFilters.length}
|
|
113
|
+
</span>
|
|
114
|
+
)}
|
|
115
|
+
</Button>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{/* Botón limpiar para modos inline */}
|
|
119
|
+
{(filterMode === 'inline' || (filterMode === 'inline-collapsible' && showFilters)) && isFiltered && (
|
|
120
|
+
<Button
|
|
121
|
+
variant="ghost"
|
|
122
|
+
onClick={() => table.resetColumnFilters()}
|
|
123
|
+
className="h-8 px-2 lg:px-3"
|
|
124
|
+
>
|
|
125
|
+
Limpiar
|
|
126
|
+
<X className="ml-2 h-4 w-4" />
|
|
127
|
+
</Button>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Filtros inline colapsables - fila separada */}
|
|
133
|
+
{filterMode === 'inline-collapsible' && showFilters && filters && filters.length > 0 && (
|
|
134
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
135
|
+
{renderFilters()}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import { Table } from '@tanstack/react-table'
|
|
5
|
-
import { Settings2 } from 'lucide-react'
|
|
6
|
-
|
|
7
|
-
import { Button } from '@/components/ui/button'
|
|
8
|
-
import {
|
|
9
|
-
DropdownMenu,
|
|
10
|
-
DropdownMenuCheckboxItem,
|
|
11
|
-
DropdownMenuContent,
|
|
12
|
-
DropdownMenuLabel,
|
|
13
|
-
DropdownMenuSeparator,
|
|
14
|
-
DropdownMenuTrigger,
|
|
15
|
-
} from '@/components/ui/dropdown-menu'
|
|
16
|
-
|
|
17
|
-
interface DataTableViewOptionsProps<TData> {
|
|
18
|
-
table: Table<TData>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function DataTableViewOptions<TData>({
|
|
22
|
-
table,
|
|
23
|
-
}: DataTableViewOptionsProps<TData>) {
|
|
24
|
-
const [open, setOpen] = React.useState(false)
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
28
|
-
<DropdownMenuTrigger asChild>
|
|
29
|
-
<Button
|
|
30
|
-
variant="ghost"
|
|
31
|
-
size="icon"
|
|
32
|
-
className="h-8 w-8"
|
|
33
|
-
>
|
|
34
|
-
<Settings2 className="h-4 w-4" />
|
|
35
|
-
<span className="sr-only">Columnas</span>
|
|
36
|
-
</Button>
|
|
37
|
-
</DropdownMenuTrigger>
|
|
38
|
-
<DropdownMenuContent align="end" className="w-[150px]">
|
|
39
|
-
<DropdownMenuLabel>Mostrar columnas</DropdownMenuLabel>
|
|
40
|
-
<DropdownMenuSeparator />
|
|
41
|
-
{table
|
|
42
|
-
.getAllColumns()
|
|
43
|
-
.filter(
|
|
44
|
-
(column) =>
|
|
45
|
-
typeof column.accessorFn !== 'undefined' && column.getCanHide()
|
|
46
|
-
)
|
|
47
|
-
.map((column) => {
|
|
48
|
-
return (
|
|
49
|
-
<DropdownMenuCheckboxItem
|
|
50
|
-
key={column.id}
|
|
51
|
-
className="capitalize"
|
|
52
|
-
checked={column.getIsVisible()}
|
|
53
|
-
onSelect={(e) => e.preventDefault()}
|
|
54
|
-
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
|
55
|
-
>
|
|
56
|
-
{column.id}
|
|
57
|
-
</DropdownMenuCheckboxItem>
|
|
58
|
-
)
|
|
59
|
-
})}
|
|
60
|
-
</DropdownMenuContent>
|
|
61
|
-
</DropdownMenu>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Table } from '@tanstack/react-table'
|
|
5
|
+
import { Settings2 } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuCheckboxItem,
|
|
11
|
+
DropdownMenuContent,
|
|
12
|
+
DropdownMenuLabel,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuTrigger,
|
|
15
|
+
} from '@/components/ui/dropdown-menu'
|
|
16
|
+
|
|
17
|
+
interface DataTableViewOptionsProps<TData> {
|
|
18
|
+
table: Table<TData>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function DataTableViewOptions<TData>({
|
|
22
|
+
table,
|
|
23
|
+
}: DataTableViewOptionsProps<TData>) {
|
|
24
|
+
const [open, setOpen] = React.useState(false)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
28
|
+
<DropdownMenuTrigger asChild>
|
|
29
|
+
<Button
|
|
30
|
+
variant="ghost"
|
|
31
|
+
size="icon"
|
|
32
|
+
className="h-8 w-8"
|
|
33
|
+
>
|
|
34
|
+
<Settings2 className="h-4 w-4" />
|
|
35
|
+
<span className="sr-only">Columnas</span>
|
|
36
|
+
</Button>
|
|
37
|
+
</DropdownMenuTrigger>
|
|
38
|
+
<DropdownMenuContent align="end" className="w-[150px]">
|
|
39
|
+
<DropdownMenuLabel>Mostrar columnas</DropdownMenuLabel>
|
|
40
|
+
<DropdownMenuSeparator />
|
|
41
|
+
{table
|
|
42
|
+
.getAllColumns()
|
|
43
|
+
.filter(
|
|
44
|
+
(column) =>
|
|
45
|
+
typeof column.accessorFn !== 'undefined' && column.getCanHide()
|
|
46
|
+
)
|
|
47
|
+
.map((column) => {
|
|
48
|
+
return (
|
|
49
|
+
<DropdownMenuCheckboxItem
|
|
50
|
+
key={column.id}
|
|
51
|
+
className="capitalize"
|
|
52
|
+
checked={column.getIsVisible()}
|
|
53
|
+
onSelect={(e) => e.preventDefault()}
|
|
54
|
+
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
|
55
|
+
>
|
|
56
|
+
{column.id}
|
|
57
|
+
</DropdownMenuCheckboxItem>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
60
|
+
</DropdownMenuContent>
|
|
61
|
+
</DropdownMenu>
|
|
62
|
+
)
|
|
63
|
+
}
|