@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,106 +1,106 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { ColumnDef } from '@tanstack/react-table'
|
|
4
|
-
import { MoreHorizontal } from 'lucide-react'
|
|
5
|
-
import { Button } from '@/components/ui/button'
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu,
|
|
8
|
-
DropdownMenuContent,
|
|
9
|
-
DropdownMenuItem,
|
|
10
|
-
DropdownMenuLabel,
|
|
11
|
-
DropdownMenuSeparator,
|
|
12
|
-
DropdownMenuTrigger,
|
|
13
|
-
} from '@/components/ui/dropdown-menu'
|
|
14
|
-
import { DataTableColumnHeader } from '@/components/tables/data-table-column-header'
|
|
15
|
-
import { dateRangeFilterFn } from '@/components/tables/data-table-date-filter'
|
|
16
|
-
// numberRangeFilterFn disponible para campos numéricos: import { numberRangeFilterFn } from '@/components/tables/data-table-number-filter'
|
|
17
|
-
import { formatDate } from '@/lib/date'
|
|
18
|
-
import { ROLE_LABELS } from '@/config/roles'
|
|
19
|
-
import type { User } from './schemas/users.schema'
|
|
20
|
-
import type { UserRole } from '@/config/roles'
|
|
21
|
-
|
|
22
|
-
export const columns: ColumnDef<User>[] = [
|
|
23
|
-
{
|
|
24
|
-
accessorKey: 'name',
|
|
25
|
-
header: ({ column }) => (
|
|
26
|
-
<DataTableColumnHeader column={column} title="Nombre" />
|
|
27
|
-
),
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
accessorKey: 'email',
|
|
31
|
-
header: ({ column }) => (
|
|
32
|
-
<DataTableColumnHeader column={column} title="Email" />
|
|
33
|
-
),
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
accessorKey: 'role',
|
|
37
|
-
header: ({ column }) => (
|
|
38
|
-
<DataTableColumnHeader column={column} title="Rol" />
|
|
39
|
-
),
|
|
40
|
-
cell: ({ row }) => {
|
|
41
|
-
const role = row.getValue('role') as UserRole
|
|
42
|
-
return ROLE_LABELS[role] ?? role
|
|
43
|
-
},
|
|
44
|
-
filterFn: (row, id, value) => {
|
|
45
|
-
return value.includes(row.getValue(id))
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
accessorKey: 'created_at',
|
|
50
|
-
header: ({ column }) => (
|
|
51
|
-
<DataTableColumnHeader column={column} title="Creado" />
|
|
52
|
-
),
|
|
53
|
-
cell: ({ row }) => {
|
|
54
|
-
return formatDate(row.getValue('created_at'))
|
|
55
|
-
},
|
|
56
|
-
filterFn: dateRangeFilterFn,
|
|
57
|
-
},
|
|
58
|
-
// Ejemplo de columna numérica con filtro de rango:
|
|
59
|
-
// import { DEFAULT_LOCALE, DEFAULT_CURRENCY } from '@/lib/date'
|
|
60
|
-
// {
|
|
61
|
-
// accessorKey: 'balance',
|
|
62
|
-
// header: ({ column }) => (
|
|
63
|
-
// <DataTableColumnHeader column={column} title="Balance" />
|
|
64
|
-
// ),
|
|
65
|
-
// cell: ({ row }) => {
|
|
66
|
-
// const balance = row.getValue('balance') as number | undefined
|
|
67
|
-
// if (balance === undefined || balance === null) return '-'
|
|
68
|
-
// return new Intl.NumberFormat(DEFAULT_LOCALE, {
|
|
69
|
-
// style: 'currency',
|
|
70
|
-
// currency: DEFAULT_CURRENCY,
|
|
71
|
-
// }).format(balance)
|
|
72
|
-
// },
|
|
73
|
-
// filterFn: numberRangeFilterFn,
|
|
74
|
-
// },
|
|
75
|
-
{
|
|
76
|
-
id: 'actions',
|
|
77
|
-
cell: ({ row }) => {
|
|
78
|
-
const user = row.original
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<DropdownMenu>
|
|
82
|
-
<DropdownMenuTrigger asChild>
|
|
83
|
-
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
84
|
-
<span className="sr-only">Abrir menú</span>
|
|
85
|
-
<MoreHorizontal className="h-4 w-4" />
|
|
86
|
-
</Button>
|
|
87
|
-
</DropdownMenuTrigger>
|
|
88
|
-
<DropdownMenuContent align="end">
|
|
89
|
-
<DropdownMenuLabel>Acciones</DropdownMenuLabel>
|
|
90
|
-
<DropdownMenuItem
|
|
91
|
-
onClick={() => navigator.clipboard.writeText(user.id)}
|
|
92
|
-
>
|
|
93
|
-
Copiar ID
|
|
94
|
-
</DropdownMenuItem>
|
|
95
|
-
<DropdownMenuSeparator />
|
|
96
|
-
<DropdownMenuItem>Ver detalles</DropdownMenuItem>
|
|
97
|
-
<DropdownMenuItem>Editar</DropdownMenuItem>
|
|
98
|
-
<DropdownMenuItem className="text-destructive">
|
|
99
|
-
Eliminar
|
|
100
|
-
</DropdownMenuItem>
|
|
101
|
-
</DropdownMenuContent>
|
|
102
|
-
</DropdownMenu>
|
|
103
|
-
)
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
]
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ColumnDef } from '@tanstack/react-table'
|
|
4
|
+
import { MoreHorizontal } from 'lucide-react'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuLabel,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from '@/components/ui/dropdown-menu'
|
|
14
|
+
import { DataTableColumnHeader } from '@/components/tables/data-table-column-header'
|
|
15
|
+
import { dateRangeFilterFn } from '@/components/tables/data-table-date-filter'
|
|
16
|
+
// numberRangeFilterFn disponible para campos numéricos: import { numberRangeFilterFn } from '@/components/tables/data-table-number-filter'
|
|
17
|
+
import { formatDate } from '@/lib/date'
|
|
18
|
+
import { ROLE_LABELS } from '@/config/roles'
|
|
19
|
+
import type { User } from './schemas/users.schema'
|
|
20
|
+
import type { UserRole } from '@/config/roles'
|
|
21
|
+
|
|
22
|
+
export const columns: ColumnDef<User>[] = [
|
|
23
|
+
{
|
|
24
|
+
accessorKey: 'name',
|
|
25
|
+
header: ({ column }) => (
|
|
26
|
+
<DataTableColumnHeader column={column} title="Nombre" />
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
accessorKey: 'email',
|
|
31
|
+
header: ({ column }) => (
|
|
32
|
+
<DataTableColumnHeader column={column} title="Email" />
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
accessorKey: 'role',
|
|
37
|
+
header: ({ column }) => (
|
|
38
|
+
<DataTableColumnHeader column={column} title="Rol" />
|
|
39
|
+
),
|
|
40
|
+
cell: ({ row }) => {
|
|
41
|
+
const role = row.getValue('role') as UserRole
|
|
42
|
+
return ROLE_LABELS[role] ?? role
|
|
43
|
+
},
|
|
44
|
+
filterFn: (row, id, value) => {
|
|
45
|
+
return value.includes(row.getValue(id))
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
accessorKey: 'created_at',
|
|
50
|
+
header: ({ column }) => (
|
|
51
|
+
<DataTableColumnHeader column={column} title="Creado" />
|
|
52
|
+
),
|
|
53
|
+
cell: ({ row }) => {
|
|
54
|
+
return formatDate(row.getValue('created_at'))
|
|
55
|
+
},
|
|
56
|
+
filterFn: dateRangeFilterFn,
|
|
57
|
+
},
|
|
58
|
+
// Ejemplo de columna numérica con filtro de rango:
|
|
59
|
+
// import { DEFAULT_LOCALE, DEFAULT_CURRENCY } from '@/lib/date'
|
|
60
|
+
// {
|
|
61
|
+
// accessorKey: 'balance',
|
|
62
|
+
// header: ({ column }) => (
|
|
63
|
+
// <DataTableColumnHeader column={column} title="Balance" />
|
|
64
|
+
// ),
|
|
65
|
+
// cell: ({ row }) => {
|
|
66
|
+
// const balance = row.getValue('balance') as number | undefined
|
|
67
|
+
// if (balance === undefined || balance === null) return '-'
|
|
68
|
+
// return new Intl.NumberFormat(DEFAULT_LOCALE, {
|
|
69
|
+
// style: 'currency',
|
|
70
|
+
// currency: DEFAULT_CURRENCY,
|
|
71
|
+
// }).format(balance)
|
|
72
|
+
// },
|
|
73
|
+
// filterFn: numberRangeFilterFn,
|
|
74
|
+
// },
|
|
75
|
+
{
|
|
76
|
+
id: 'actions',
|
|
77
|
+
cell: ({ row }) => {
|
|
78
|
+
const user = row.original
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<DropdownMenu>
|
|
82
|
+
<DropdownMenuTrigger asChild>
|
|
83
|
+
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
84
|
+
<span className="sr-only">Abrir menú</span>
|
|
85
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
86
|
+
</Button>
|
|
87
|
+
</DropdownMenuTrigger>
|
|
88
|
+
<DropdownMenuContent align="end">
|
|
89
|
+
<DropdownMenuLabel>Acciones</DropdownMenuLabel>
|
|
90
|
+
<DropdownMenuItem
|
|
91
|
+
onClick={() => navigator.clipboard.writeText(user.id)}
|
|
92
|
+
>
|
|
93
|
+
Copiar ID
|
|
94
|
+
</DropdownMenuItem>
|
|
95
|
+
<DropdownMenuSeparator />
|
|
96
|
+
<DropdownMenuItem>Ver detalles</DropdownMenuItem>
|
|
97
|
+
<DropdownMenuItem>Editar</DropdownMenuItem>
|
|
98
|
+
<DropdownMenuItem className="text-destructive">
|
|
99
|
+
Eliminar
|
|
100
|
+
</DropdownMenuItem>
|
|
101
|
+
</DropdownMenuContent>
|
|
102
|
+
</DropdownMenu>
|
|
103
|
+
)
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
]
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { DataTable, type FilterConfig } from '@/components/tables/data-table'
|
|
4
|
-
import { ROLE_OPTIONS } from '@/config/roles'
|
|
5
|
-
import { useUsers } from '../hooks/useUsers'
|
|
6
|
-
import { columns } from '../columns'
|
|
7
|
-
|
|
8
|
-
// Configuración de filtros para la tabla de usuarios
|
|
9
|
-
const filters: FilterConfig[] = [
|
|
10
|
-
{
|
|
11
|
-
columnId: 'role',
|
|
12
|
-
type: 'faceted',
|
|
13
|
-
title: 'Rol',
|
|
14
|
-
options: ROLE_OPTIONS,
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
columnId: 'created_at',
|
|
18
|
-
type: 'date-range',
|
|
19
|
-
title: 'Fecha',
|
|
20
|
-
},
|
|
21
|
-
// Ejemplo de filtro numérico (rango):
|
|
22
|
-
// {
|
|
23
|
-
// columnId: 'balance',
|
|
24
|
-
// type: 'number-range',
|
|
25
|
-
// title: 'Balance',
|
|
26
|
-
// format: 'currency',
|
|
27
|
-
// currencySymbol: '$',
|
|
28
|
-
// },
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
export function UsersList() {
|
|
32
|
-
const { users, isLoading } = useUsers()
|
|
33
|
-
|
|
34
|
-
if (isLoading) {
|
|
35
|
-
return <div>Cargando usuarios...</div>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<DataTable
|
|
40
|
-
columns={columns}
|
|
41
|
-
data={users}
|
|
42
|
-
searchKey="name"
|
|
43
|
-
searchPlaceholder="Buscar por nombre..."
|
|
44
|
-
filters={filters}
|
|
45
|
-
filterMode="inline"
|
|
46
|
-
/>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { DataTable, type FilterConfig } from '@/components/tables/data-table'
|
|
4
|
+
import { ROLE_OPTIONS } from '@/config/roles'
|
|
5
|
+
import { useUsers } from '../hooks/useUsers'
|
|
6
|
+
import { columns } from '../columns'
|
|
7
|
+
|
|
8
|
+
// Configuración de filtros para la tabla de usuarios
|
|
9
|
+
const filters: FilterConfig[] = [
|
|
10
|
+
{
|
|
11
|
+
columnId: 'role',
|
|
12
|
+
type: 'faceted',
|
|
13
|
+
title: 'Rol',
|
|
14
|
+
options: ROLE_OPTIONS,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
columnId: 'created_at',
|
|
18
|
+
type: 'date-range',
|
|
19
|
+
title: 'Fecha',
|
|
20
|
+
},
|
|
21
|
+
// Ejemplo de filtro numérico (rango):
|
|
22
|
+
// {
|
|
23
|
+
// columnId: 'balance',
|
|
24
|
+
// type: 'number-range',
|
|
25
|
+
// title: 'Balance',
|
|
26
|
+
// format: 'currency',
|
|
27
|
+
// currencySymbol: '$',
|
|
28
|
+
// },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
export function UsersList() {
|
|
32
|
+
const { users, isLoading } = useUsers()
|
|
33
|
+
|
|
34
|
+
if (isLoading) {
|
|
35
|
+
return <div>Cargando usuarios...</div>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<DataTable
|
|
40
|
+
columns={columns}
|
|
41
|
+
data={users}
|
|
42
|
+
searchKey="name"
|
|
43
|
+
searchPlaceholder="Buscar por nombre..."
|
|
44
|
+
filters={filters}
|
|
45
|
+
filterMode="inline"
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useShallow } from 'zustand/react/shallow'
|
|
4
|
-
import { useUsersStore } from '../stores/useUsersStore'
|
|
5
|
-
import { useUsersQueries } from './useUsersQueries'
|
|
6
|
-
import { useUsersMutations } from './useUsersMutations'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Hook unificado para usuarios.
|
|
10
|
-
* Los componentes SOLO deben importar este hook, nunca useUsersQueries o useUsersMutations directamente.
|
|
11
|
-
*/
|
|
12
|
-
export function useUsers() {
|
|
13
|
-
const state = useUsersStore(useShallow((s) => s.state))
|
|
14
|
-
const actions = useUsersStore(useShallow((s) => s.actions))
|
|
15
|
-
|
|
16
|
-
const queries = useUsersQueries(state.selectedUserId ?? undefined)
|
|
17
|
-
const mutations = useUsersMutations()
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
// State from store (UI state)
|
|
21
|
-
...state,
|
|
22
|
-
...actions,
|
|
23
|
-
|
|
24
|
-
// Queries
|
|
25
|
-
users: queries.users,
|
|
26
|
-
user: queries.user,
|
|
27
|
-
isLoading: queries.isLoading,
|
|
28
|
-
isLoadingUser: queries.isLoadingUser,
|
|
29
|
-
error: queries.error,
|
|
30
|
-
|
|
31
|
-
// Mutations
|
|
32
|
-
createUser: mutations.create,
|
|
33
|
-
updateUser: mutations.update,
|
|
34
|
-
deleteUser: mutations.delete,
|
|
35
|
-
isCreating: mutations.isCreating,
|
|
36
|
-
isUpdating: mutations.isUpdating,
|
|
37
|
-
isDeleting: mutations.isDeleting,
|
|
38
|
-
}
|
|
39
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
4
|
+
import { useUsersStore } from '../stores/useUsersStore'
|
|
5
|
+
import { useUsersQueries } from './useUsersQueries'
|
|
6
|
+
import { useUsersMutations } from './useUsersMutations'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook unificado para usuarios.
|
|
10
|
+
* Los componentes SOLO deben importar este hook, nunca useUsersQueries o useUsersMutations directamente.
|
|
11
|
+
*/
|
|
12
|
+
export function useUsers() {
|
|
13
|
+
const state = useUsersStore(useShallow((s) => s.state))
|
|
14
|
+
const actions = useUsersStore(useShallow((s) => s.actions))
|
|
15
|
+
|
|
16
|
+
const queries = useUsersQueries(state.selectedUserId ?? undefined)
|
|
17
|
+
const mutations = useUsersMutations()
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
// State from store (UI state)
|
|
21
|
+
...state,
|
|
22
|
+
...actions,
|
|
23
|
+
|
|
24
|
+
// Queries
|
|
25
|
+
users: queries.users,
|
|
26
|
+
user: queries.user,
|
|
27
|
+
isLoading: queries.isLoading,
|
|
28
|
+
isLoadingUser: queries.isLoadingUser,
|
|
29
|
+
error: queries.error,
|
|
30
|
+
|
|
31
|
+
// Mutations
|
|
32
|
+
createUser: mutations.create,
|
|
33
|
+
updateUser: mutations.update,
|
|
34
|
+
deleteUser: mutations.delete,
|
|
35
|
+
isCreating: mutations.isCreating,
|
|
36
|
+
isUpdating: mutations.isUpdating,
|
|
37
|
+
isDeleting: mutations.isDeleting,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
-
import { toast } from 'sonner'
|
|
5
|
-
import { createUser, updateUser, deleteUser } from '../actions/users-actions'
|
|
6
|
-
import { usersKeys } from './useUsersQueries'
|
|
7
|
-
import type { CreateAuthUserInput, UpdateAuthUserInput } from '../schemas/users.schema'
|
|
8
|
-
|
|
9
|
-
export function useUsersMutations() {
|
|
10
|
-
const queryClient = useQueryClient()
|
|
11
|
-
|
|
12
|
-
const createMutation = useMutation({
|
|
13
|
-
mutationFn: (input: CreateAuthUserInput) => createUser(input),
|
|
14
|
-
onSuccess: () => {
|
|
15
|
-
queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
|
|
16
|
-
toast.success('Usuario creado correctamente')
|
|
17
|
-
},
|
|
18
|
-
onError: (error) => {
|
|
19
|
-
toast.error(error.message)
|
|
20
|
-
},
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
const updateMutation = useMutation({
|
|
24
|
-
mutationFn: ({ id, input }: { id: string; input: UpdateAuthUserInput }) =>
|
|
25
|
-
updateUser(id, input),
|
|
26
|
-
onSuccess: (_, { id }) => {
|
|
27
|
-
queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
|
|
28
|
-
queryClient.invalidateQueries({ queryKey: usersKeys.detail(id) })
|
|
29
|
-
toast.success('Usuario actualizado correctamente')
|
|
30
|
-
},
|
|
31
|
-
onError: (error) => {
|
|
32
|
-
toast.error(error.message)
|
|
33
|
-
},
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const deleteMutation = useMutation({
|
|
37
|
-
mutationFn: (id: string) => deleteUser(id),
|
|
38
|
-
onSuccess: () => {
|
|
39
|
-
queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
|
|
40
|
-
toast.success('Usuario eliminado correctamente')
|
|
41
|
-
},
|
|
42
|
-
onError: (error) => {
|
|
43
|
-
toast.error(error.message)
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
create: createMutation.mutateAsync,
|
|
49
|
-
update: updateMutation.mutateAsync,
|
|
50
|
-
delete: deleteMutation.mutateAsync,
|
|
51
|
-
isCreating: createMutation.isPending,
|
|
52
|
-
isUpdating: updateMutation.isPending,
|
|
53
|
-
isDeleting: deleteMutation.isPending,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
+
import { toast } from 'sonner'
|
|
5
|
+
import { createUser, updateUser, deleteUser } from '../actions/users-actions'
|
|
6
|
+
import { usersKeys } from './useUsersQueries'
|
|
7
|
+
import type { CreateAuthUserInput, UpdateAuthUserInput } from '../schemas/users.schema'
|
|
8
|
+
|
|
9
|
+
export function useUsersMutations() {
|
|
10
|
+
const queryClient = useQueryClient()
|
|
11
|
+
|
|
12
|
+
const createMutation = useMutation({
|
|
13
|
+
mutationFn: (input: CreateAuthUserInput) => createUser(input),
|
|
14
|
+
onSuccess: () => {
|
|
15
|
+
queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
|
|
16
|
+
toast.success('Usuario creado correctamente')
|
|
17
|
+
},
|
|
18
|
+
onError: (error) => {
|
|
19
|
+
toast.error(error.message)
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const updateMutation = useMutation({
|
|
24
|
+
mutationFn: ({ id, input }: { id: string; input: UpdateAuthUserInput }) =>
|
|
25
|
+
updateUser(id, input),
|
|
26
|
+
onSuccess: (_, { id }) => {
|
|
27
|
+
queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
|
|
28
|
+
queryClient.invalidateQueries({ queryKey: usersKeys.detail(id) })
|
|
29
|
+
toast.success('Usuario actualizado correctamente')
|
|
30
|
+
},
|
|
31
|
+
onError: (error) => {
|
|
32
|
+
toast.error(error.message)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const deleteMutation = useMutation({
|
|
37
|
+
mutationFn: (id: string) => deleteUser(id),
|
|
38
|
+
onSuccess: () => {
|
|
39
|
+
queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
|
|
40
|
+
toast.success('Usuario eliminado correctamente')
|
|
41
|
+
},
|
|
42
|
+
onError: (error) => {
|
|
43
|
+
toast.error(error.message)
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
create: createMutation.mutateAsync,
|
|
49
|
+
update: updateMutation.mutateAsync,
|
|
50
|
+
delete: deleteMutation.mutateAsync,
|
|
51
|
+
isCreating: createMutation.isPending,
|
|
52
|
+
isUpdating: updateMutation.isPending,
|
|
53
|
+
isDeleting: deleteMutation.isPending,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useQuery } from '@tanstack/react-query'
|
|
4
|
-
import { getUsers, getUserById } from '../actions/users-actions'
|
|
5
|
-
|
|
6
|
-
export const usersKeys = {
|
|
7
|
-
all: ['users'] as const,
|
|
8
|
-
lists: () => [...usersKeys.all, 'list'] as const,
|
|
9
|
-
list: (filters: Record<string, unknown>) => [...usersKeys.lists(), filters] as const,
|
|
10
|
-
details: () => [...usersKeys.all, 'detail'] as const,
|
|
11
|
-
detail: (id: string) => [...usersKeys.details(), id] as const,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useUsersQueries(userId?: string) {
|
|
15
|
-
const usersQuery = useQuery({
|
|
16
|
-
queryKey: usersKeys.lists(),
|
|
17
|
-
queryFn: () => getUsers(),
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const userQuery = useQuery({
|
|
21
|
-
queryKey: usersKeys.detail(userId!),
|
|
22
|
-
queryFn: () => getUserById(userId!),
|
|
23
|
-
enabled: !!userId,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
users: usersQuery.data ?? [],
|
|
28
|
-
user: userQuery.data,
|
|
29
|
-
isLoading: usersQuery.isLoading,
|
|
30
|
-
isLoadingUser: userQuery.isLoading,
|
|
31
|
-
error: usersQuery.error,
|
|
32
|
-
usersQuery,
|
|
33
|
-
userQuery,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery } from '@tanstack/react-query'
|
|
4
|
+
import { getUsers, getUserById } from '../actions/users-actions'
|
|
5
|
+
|
|
6
|
+
export const usersKeys = {
|
|
7
|
+
all: ['users'] as const,
|
|
8
|
+
lists: () => [...usersKeys.all, 'list'] as const,
|
|
9
|
+
list: (filters: Record<string, unknown>) => [...usersKeys.lists(), filters] as const,
|
|
10
|
+
details: () => [...usersKeys.all, 'detail'] as const,
|
|
11
|
+
detail: (id: string) => [...usersKeys.details(), id] as const,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useUsersQueries(userId?: string) {
|
|
15
|
+
const usersQuery = useQuery({
|
|
16
|
+
queryKey: usersKeys.lists(),
|
|
17
|
+
queryFn: () => getUsers(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const userQuery = useQuery({
|
|
21
|
+
queryKey: usersKeys.detail(userId!),
|
|
22
|
+
queryFn: () => getUserById(userId!),
|
|
23
|
+
enabled: !!userId,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
users: usersQuery.data ?? [],
|
|
28
|
+
user: userQuery.data,
|
|
29
|
+
isLoading: usersQuery.isLoading,
|
|
30
|
+
isLoadingUser: userQuery.isLoading,
|
|
31
|
+
error: usersQuery.error,
|
|
32
|
+
usersQuery,
|
|
33
|
+
userQuery,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
// Components
|
|
2
|
-
export { UsersList } from './components/users-list'
|
|
3
|
-
|
|
4
|
-
// Hooks - Solo exportar el hook unificado
|
|
5
|
-
export { useUsers } from './hooks/useUsers'
|
|
6
|
-
|
|
7
|
-
// Schemas
|
|
8
|
-
export {
|
|
9
|
-
userSchema,
|
|
10
|
-
createUserSchema,
|
|
11
|
-
updateUserSchema,
|
|
12
|
-
createAuthUserSchema,
|
|
13
|
-
updateAuthUserSchema,
|
|
14
|
-
} from './schemas/users.schema'
|
|
15
|
-
export type {
|
|
16
|
-
User,
|
|
17
|
-
CreateUserInput,
|
|
18
|
-
UpdateUserInput,
|
|
19
|
-
CreateAuthUserInput,
|
|
20
|
-
UpdateAuthUserInput,
|
|
21
|
-
} from './schemas/users.schema'
|
|
22
|
-
|
|
23
|
-
// Types - Auth User types
|
|
24
|
-
export type { UserRole, UserAppMetadata, UserMetadata } from './types/auth-user.types'
|
|
25
|
-
|
|
26
|
-
// Utils - Mappers
|
|
27
|
-
export { mapAuthUserToUser, mapAuthUsersToUsers } from './utils/user-mapper'
|
|
28
|
-
|
|
29
|
-
// Columns
|
|
30
|
-
export { columns as usersColumns } from './columns'
|
|
1
|
+
// Components
|
|
2
|
+
export { UsersList } from './components/users-list'
|
|
3
|
+
|
|
4
|
+
// Hooks - Solo exportar el hook unificado
|
|
5
|
+
export { useUsers } from './hooks/useUsers'
|
|
6
|
+
|
|
7
|
+
// Schemas
|
|
8
|
+
export {
|
|
9
|
+
userSchema,
|
|
10
|
+
createUserSchema,
|
|
11
|
+
updateUserSchema,
|
|
12
|
+
createAuthUserSchema,
|
|
13
|
+
updateAuthUserSchema,
|
|
14
|
+
} from './schemas/users.schema'
|
|
15
|
+
export type {
|
|
16
|
+
User,
|
|
17
|
+
CreateUserInput,
|
|
18
|
+
UpdateUserInput,
|
|
19
|
+
CreateAuthUserInput,
|
|
20
|
+
UpdateAuthUserInput,
|
|
21
|
+
} from './schemas/users.schema'
|
|
22
|
+
|
|
23
|
+
// Types - Auth User types
|
|
24
|
+
export type { UserRole, UserAppMetadata, UserMetadata } from './types/auth-user.types'
|
|
25
|
+
|
|
26
|
+
// Utils - Mappers
|
|
27
|
+
export { mapAuthUserToUser, mapAuthUsersToUsers } from './utils/user-mapper'
|
|
28
|
+
|
|
29
|
+
// Columns
|
|
30
|
+
export { columns as usersColumns } from './columns'
|