@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,59 +1,59 @@
|
|
|
1
|
-
import { LucideIcon, LayoutDashboard, Users, FileText } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
export interface NavItem {
|
|
4
|
-
title: string
|
|
5
|
-
href: string
|
|
6
|
-
icon?: LucideIcon
|
|
7
|
-
disabled?: boolean
|
|
8
|
-
external?: boolean
|
|
9
|
-
badge?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface NavSection {
|
|
13
|
-
title?: string
|
|
14
|
-
items: NavItem[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const mainNav: NavItem[] = [
|
|
18
|
-
{
|
|
19
|
-
title: 'Dashboard',
|
|
20
|
-
href: '/dashboard',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
title: 'Usuarios',
|
|
24
|
-
href: '/users',
|
|
25
|
-
},
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
export const sidebarNav: NavSection[] = [
|
|
29
|
-
{
|
|
30
|
-
items: [
|
|
31
|
-
{
|
|
32
|
-
title: 'Dashboard',
|
|
33
|
-
href: '/dashboard',
|
|
34
|
-
icon: LayoutDashboard,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
title: 'Usuarios',
|
|
38
|
-
href: '/users',
|
|
39
|
-
icon: Users,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
title: 'Reportes',
|
|
43
|
-
href: '/reports',
|
|
44
|
-
icon: FileText,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
export const footerNav: NavItem[] = [
|
|
51
|
-
{
|
|
52
|
-
title: 'Términos',
|
|
53
|
-
href: '/terms',
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
title: 'Privacidad',
|
|
57
|
-
href: '/privacy',
|
|
58
|
-
},
|
|
59
|
-
]
|
|
1
|
+
import { LucideIcon, LayoutDashboard, Users, FileText } from 'lucide-react'
|
|
2
|
+
|
|
3
|
+
export interface NavItem {
|
|
4
|
+
title: string
|
|
5
|
+
href: string
|
|
6
|
+
icon?: LucideIcon
|
|
7
|
+
disabled?: boolean
|
|
8
|
+
external?: boolean
|
|
9
|
+
badge?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface NavSection {
|
|
13
|
+
title?: string
|
|
14
|
+
items: NavItem[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const mainNav: NavItem[] = [
|
|
18
|
+
{
|
|
19
|
+
title: 'Dashboard',
|
|
20
|
+
href: '/dashboard',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: 'Usuarios',
|
|
24
|
+
href: '/users',
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
export const sidebarNav: NavSection[] = [
|
|
29
|
+
{
|
|
30
|
+
items: [
|
|
31
|
+
{
|
|
32
|
+
title: 'Dashboard',
|
|
33
|
+
href: '/dashboard',
|
|
34
|
+
icon: LayoutDashboard,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: 'Usuarios',
|
|
38
|
+
href: '/users',
|
|
39
|
+
icon: Users,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: 'Reportes',
|
|
43
|
+
href: '/reports',
|
|
44
|
+
icon: FileText,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
export const footerNav: NavItem[] = [
|
|
51
|
+
{
|
|
52
|
+
title: 'Términos',
|
|
53
|
+
href: '/terms',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: 'Privacidad',
|
|
57
|
+
href: '/privacy',
|
|
58
|
+
},
|
|
59
|
+
]
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuración centralizada de roles de usuario.
|
|
3
|
-
* Modificar aquí para agregar/quitar roles en toda la aplicación.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const USER_ROLES = ['admin', 'user', 'viewer'] as const
|
|
7
|
-
|
|
8
|
-
export type UserRole = (typeof USER_ROLES)[number]
|
|
9
|
-
|
|
10
|
-
export const DEFAULT_ROLE: UserRole = 'user'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Labels para mostrar en UI (traducidos al español)
|
|
14
|
-
*/
|
|
15
|
-
export const ROLE_LABELS: Record<UserRole, string> = {
|
|
16
|
-
admin: 'Administrador',
|
|
17
|
-
user: 'Usuario',
|
|
18
|
-
viewer: 'Visualizador',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Opciones para selects/filtros de roles
|
|
23
|
-
*/
|
|
24
|
-
export const ROLE_OPTIONS = USER_ROLES.map((role) => ({
|
|
25
|
-
value: role,
|
|
26
|
-
label: ROLE_LABELS[role],
|
|
27
|
-
}))
|
|
1
|
+
/**
|
|
2
|
+
* Configuración centralizada de roles de usuario.
|
|
3
|
+
* Modificar aquí para agregar/quitar roles en toda la aplicación.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const USER_ROLES = ['admin', 'user', 'viewer'] as const
|
|
7
|
+
|
|
8
|
+
export type UserRole = (typeof USER_ROLES)[number]
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_ROLE: UserRole = 'user'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Labels para mostrar en UI (traducidos al español)
|
|
14
|
+
*/
|
|
15
|
+
export const ROLE_LABELS: Record<UserRole, string> = {
|
|
16
|
+
admin: 'Administrador',
|
|
17
|
+
user: 'Usuario',
|
|
18
|
+
viewer: 'Visualizador',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Opciones para selects/filtros de roles
|
|
23
|
+
*/
|
|
24
|
+
export const ROLE_OPTIONS = USER_ROLES.map((role) => ({
|
|
25
|
+
value: role,
|
|
26
|
+
label: ROLE_LABELS[role],
|
|
27
|
+
}))
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export const siteConfig = {
|
|
2
|
-
name: 'LFT App',
|
|
3
|
-
description: 'Next.js application with LFT stack',
|
|
4
|
-
url: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
|
|
5
|
-
ogImage: '/og.png',
|
|
6
|
-
links: {
|
|
7
|
-
github: 'https://github.com',
|
|
8
|
-
},
|
|
9
|
-
creator: 'LFT Team',
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type SiteConfig = typeof siteConfig
|
|
1
|
+
export const siteConfig = {
|
|
2
|
+
name: 'LFT App',
|
|
3
|
+
description: 'Next.js application with LFT stack',
|
|
4
|
+
url: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
|
|
5
|
+
ogImage: '/og.png',
|
|
6
|
+
links: {
|
|
7
|
+
github: 'https://github.com',
|
|
8
|
+
},
|
|
9
|
+
creator: 'LFT Team',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SiteConfig = typeof siteConfig
|
package/template/src/db/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
2
|
-
import postgres from 'postgres'
|
|
3
|
-
import * as schema from './schema'
|
|
4
|
-
|
|
5
|
-
const connectionString = process.env.DATABASE_URL!
|
|
6
|
-
|
|
7
|
-
// Disable prefetch as it is not supported for "Transaction" pool mode
|
|
8
|
-
const client = postgres(connectionString, { prepare: false })
|
|
9
|
-
|
|
10
|
-
export const db = drizzle(client, { schema })
|
|
11
|
-
|
|
12
|
-
export type Database = typeof db
|
|
1
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
2
|
+
import postgres from 'postgres'
|
|
3
|
+
import * as schema from './schema'
|
|
4
|
+
|
|
5
|
+
const connectionString = process.env.DATABASE_URL!
|
|
6
|
+
|
|
7
|
+
// Disable prefetch as it is not supported for "Transaction" pool mode
|
|
8
|
+
const client = postgres(connectionString, { prepare: false })
|
|
9
|
+
|
|
10
|
+
export const db = drizzle(client, { schema })
|
|
11
|
+
|
|
12
|
+
export type Database = typeof db
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './users'
|
|
1
|
+
export * from './users'
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { pgTable, uuid, varchar, text, timestamp, pgEnum } from 'drizzle-orm/pg-core'
|
|
2
|
-
|
|
3
|
-
export const userRoleEnum = pgEnum('user_role', ['admin', 'user', 'viewer'])
|
|
4
|
-
|
|
5
|
-
export const users = pgTable('users', {
|
|
6
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
7
|
-
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
8
|
-
name: varchar('name', { length: 255 }).notNull(),
|
|
9
|
-
role: userRoleEnum('role').default('user').notNull(),
|
|
10
|
-
avatarUrl: text('avatar_url'),
|
|
11
|
-
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
12
|
-
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
export type User = typeof users.$inferSelect
|
|
16
|
-
export type NewUser = typeof users.$inferInsert
|
|
1
|
+
import { pgTable, uuid, varchar, text, timestamp, pgEnum } from 'drizzle-orm/pg-core'
|
|
2
|
+
|
|
3
|
+
export const userRoleEnum = pgEnum('user_role', ['admin', 'user', 'viewer'])
|
|
4
|
+
|
|
5
|
+
export const users = pgTable('users', {
|
|
6
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
7
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
8
|
+
name: varchar('name', { length: 255 }).notNull(),
|
|
9
|
+
role: userRoleEnum('role').default('user').notNull(),
|
|
10
|
+
avatarUrl: text('avatar_url'),
|
|
11
|
+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
12
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export type User = typeof users.$inferSelect
|
|
16
|
+
export type NewUser = typeof users.$inferInsert
|
package/template/src/db/seed.ts
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { db } from './index'
|
|
2
|
-
import { users } from './schema'
|
|
3
|
-
|
|
4
|
-
async function seed() {
|
|
5
|
-
console.log('🌱 Seeding database...')
|
|
6
|
-
|
|
7
|
-
// Clear existing data
|
|
8
|
-
await db.delete(users)
|
|
9
|
-
|
|
10
|
-
// Insert seed data
|
|
11
|
-
await db.insert(users).values([
|
|
12
|
-
{
|
|
13
|
-
email: 'admin@example.com',
|
|
14
|
-
name: 'Admin User',
|
|
15
|
-
role: 'admin',
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
email: 'user@example.com',
|
|
19
|
-
name: 'Regular User',
|
|
20
|
-
role: 'user',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
email: 'viewer@example.com',
|
|
24
|
-
name: 'Viewer User',
|
|
25
|
-
role: 'viewer',
|
|
26
|
-
},
|
|
27
|
-
])
|
|
28
|
-
|
|
29
|
-
console.log('✅ Database seeded successfully!')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
seed()
|
|
33
|
-
.catch((error) => {
|
|
34
|
-
console.error('❌ Seed failed:', error)
|
|
35
|
-
process.exit(1)
|
|
36
|
-
})
|
|
37
|
-
.finally(() => {
|
|
38
|
-
process.exit(0)
|
|
39
|
-
})
|
|
1
|
+
import { db } from './index'
|
|
2
|
+
import { users } from './schema'
|
|
3
|
+
|
|
4
|
+
async function seed() {
|
|
5
|
+
console.log('🌱 Seeding database...')
|
|
6
|
+
|
|
7
|
+
// Clear existing data
|
|
8
|
+
await db.delete(users)
|
|
9
|
+
|
|
10
|
+
// Insert seed data
|
|
11
|
+
await db.insert(users).values([
|
|
12
|
+
{
|
|
13
|
+
email: 'admin@example.com',
|
|
14
|
+
name: 'Admin User',
|
|
15
|
+
role: 'admin',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
email: 'user@example.com',
|
|
19
|
+
name: 'Regular User',
|
|
20
|
+
role: 'user',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
email: 'viewer@example.com',
|
|
24
|
+
name: 'Viewer User',
|
|
25
|
+
role: 'viewer',
|
|
26
|
+
},
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
console.log('✅ Database seeded successfully!')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
seed()
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
console.error('❌ Seed failed:', error)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
})
|
|
37
|
+
.finally(() => {
|
|
38
|
+
process.exit(0)
|
|
39
|
+
})
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { useMediaQuery, useIsMobile, useIsTablet, useIsDesktop } from './useMediaQuery'
|
|
2
|
-
export { useDebounce, useDebouncedCallback } from './useDebounce'
|
|
3
|
-
export { useDataTable } from './useDataTable'
|
|
1
|
+
export { useMediaQuery, useIsMobile, useIsTablet, useIsDesktop } from './useMediaQuery'
|
|
2
|
+
export { useDebounce, useDebouncedCallback } from './useDebounce'
|
|
3
|
+
export { useDataTable } from './useDataTable'
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
|
|
5
|
-
const MOBILE_BREAKPOINT = 768
|
|
6
|
-
|
|
7
|
-
export function useIsMobile() {
|
|
8
|
-
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
9
|
-
|
|
10
|
-
React.useEffect(() => {
|
|
11
|
-
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
12
|
-
const onChange = () => {
|
|
13
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
14
|
-
}
|
|
15
|
-
mql.addEventListener("change", onChange)
|
|
16
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
17
|
-
return () => mql.removeEventListener("change", onChange)
|
|
18
|
-
}, [])
|
|
19
|
-
|
|
20
|
-
return !!isMobile
|
|
21
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
const MOBILE_BREAKPOINT = 768
|
|
6
|
+
|
|
7
|
+
export function useIsMobile() {
|
|
8
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
9
|
+
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
12
|
+
const onChange = () => {
|
|
13
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
14
|
+
}
|
|
15
|
+
mql.addEventListener("change", onChange)
|
|
16
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
17
|
+
return () => mql.removeEventListener("change", onChange)
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
return !!isMobile
|
|
21
|
+
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useMemo } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
ColumnDef,
|
|
6
|
-
ColumnFiltersState,
|
|
7
|
-
SortingState,
|
|
8
|
-
VisibilityState,
|
|
9
|
-
getCoreRowModel,
|
|
10
|
-
getFilteredRowModel,
|
|
11
|
-
getPaginationRowModel,
|
|
12
|
-
getSortedRowModel,
|
|
13
|
-
useReactTable,
|
|
14
|
-
TableOptions,
|
|
15
|
-
} from '@tanstack/react-table'
|
|
16
|
-
|
|
17
|
-
interface UseDataTableOptions<TData> {
|
|
18
|
-
data: TData[]
|
|
19
|
-
columns: ColumnDef<TData, unknown>[]
|
|
20
|
-
initialPageSize?: number
|
|
21
|
-
enableRowSelection?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function useDataTable<TData>({
|
|
25
|
-
data,
|
|
26
|
-
columns,
|
|
27
|
-
initialPageSize = 10,
|
|
28
|
-
enableRowSelection = false,
|
|
29
|
-
}: UseDataTableOptions<TData>) {
|
|
30
|
-
const [sorting, setSorting] = useState<SortingState>([])
|
|
31
|
-
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
32
|
-
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
33
|
-
const [rowSelection, setRowSelection] = useState({})
|
|
34
|
-
|
|
35
|
-
const tableOptions: TableOptions<TData> = useMemo(
|
|
36
|
-
() => ({
|
|
37
|
-
data,
|
|
38
|
-
columns,
|
|
39
|
-
getCoreRowModel: getCoreRowModel(),
|
|
40
|
-
getPaginationRowModel: getPaginationRowModel(),
|
|
41
|
-
onSortingChange: setSorting,
|
|
42
|
-
getSortedRowModel: getSortedRowModel(),
|
|
43
|
-
onColumnFiltersChange: setColumnFilters,
|
|
44
|
-
getFilteredRowModel: getFilteredRowModel(),
|
|
45
|
-
onColumnVisibilityChange: setColumnVisibility,
|
|
46
|
-
onRowSelectionChange: setRowSelection,
|
|
47
|
-
enableRowSelection,
|
|
48
|
-
state: {
|
|
49
|
-
sorting,
|
|
50
|
-
columnFilters,
|
|
51
|
-
columnVisibility,
|
|
52
|
-
rowSelection,
|
|
53
|
-
},
|
|
54
|
-
initialState: {
|
|
55
|
-
pagination: {
|
|
56
|
-
pageSize: initialPageSize,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
}),
|
|
60
|
-
[data, columns, sorting, columnFilters, columnVisibility, rowSelection, enableRowSelection, initialPageSize]
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
const table = useReactTable(tableOptions)
|
|
64
|
-
|
|
65
|
-
const selectedRows = useMemo(
|
|
66
|
-
() => table.getFilteredSelectedRowModel().rows.map((row) => row.original),
|
|
67
|
-
[table]
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
table,
|
|
72
|
-
sorting,
|
|
73
|
-
columnFilters,
|
|
74
|
-
columnVisibility,
|
|
75
|
-
rowSelection,
|
|
76
|
-
selectedRows,
|
|
77
|
-
setSorting,
|
|
78
|
-
setColumnFilters,
|
|
79
|
-
setColumnVisibility,
|
|
80
|
-
setRowSelection,
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
ColumnDef,
|
|
6
|
+
ColumnFiltersState,
|
|
7
|
+
SortingState,
|
|
8
|
+
VisibilityState,
|
|
9
|
+
getCoreRowModel,
|
|
10
|
+
getFilteredRowModel,
|
|
11
|
+
getPaginationRowModel,
|
|
12
|
+
getSortedRowModel,
|
|
13
|
+
useReactTable,
|
|
14
|
+
TableOptions,
|
|
15
|
+
} from '@tanstack/react-table'
|
|
16
|
+
|
|
17
|
+
interface UseDataTableOptions<TData> {
|
|
18
|
+
data: TData[]
|
|
19
|
+
columns: ColumnDef<TData, unknown>[]
|
|
20
|
+
initialPageSize?: number
|
|
21
|
+
enableRowSelection?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useDataTable<TData>({
|
|
25
|
+
data,
|
|
26
|
+
columns,
|
|
27
|
+
initialPageSize = 10,
|
|
28
|
+
enableRowSelection = false,
|
|
29
|
+
}: UseDataTableOptions<TData>) {
|
|
30
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
31
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
32
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
33
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
34
|
+
|
|
35
|
+
const tableOptions: TableOptions<TData> = useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
data,
|
|
38
|
+
columns,
|
|
39
|
+
getCoreRowModel: getCoreRowModel(),
|
|
40
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
41
|
+
onSortingChange: setSorting,
|
|
42
|
+
getSortedRowModel: getSortedRowModel(),
|
|
43
|
+
onColumnFiltersChange: setColumnFilters,
|
|
44
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
45
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
46
|
+
onRowSelectionChange: setRowSelection,
|
|
47
|
+
enableRowSelection,
|
|
48
|
+
state: {
|
|
49
|
+
sorting,
|
|
50
|
+
columnFilters,
|
|
51
|
+
columnVisibility,
|
|
52
|
+
rowSelection,
|
|
53
|
+
},
|
|
54
|
+
initialState: {
|
|
55
|
+
pagination: {
|
|
56
|
+
pageSize: initialPageSize,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
[data, columns, sorting, columnFilters, columnVisibility, rowSelection, enableRowSelection, initialPageSize]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const table = useReactTable(tableOptions)
|
|
64
|
+
|
|
65
|
+
const selectedRows = useMemo(
|
|
66
|
+
() => table.getFilteredSelectedRowModel().rows.map((row) => row.original),
|
|
67
|
+
[table]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
table,
|
|
72
|
+
sorting,
|
|
73
|
+
columnFilters,
|
|
74
|
+
columnVisibility,
|
|
75
|
+
rowSelection,
|
|
76
|
+
selectedRows,
|
|
77
|
+
setSorting,
|
|
78
|
+
setColumnFilters,
|
|
79
|
+
setColumnVisibility,
|
|
80
|
+
setRowSelection,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState, useCallback, useRef } from 'react'
|
|
4
|
-
|
|
5
|
-
export function useDebounce<T>(value: T, delay: number): T {
|
|
6
|
-
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
const timer = setTimeout(() => {
|
|
10
|
-
setDebouncedValue(value)
|
|
11
|
-
}, delay)
|
|
12
|
-
|
|
13
|
-
return () => {
|
|
14
|
-
clearTimeout(timer)
|
|
15
|
-
}
|
|
16
|
-
}, [value, delay])
|
|
17
|
-
|
|
18
|
-
return debouncedValue
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useDebouncedCallback<T extends (...args: Parameters<T>) => ReturnType<T>>(
|
|
22
|
-
callback: T,
|
|
23
|
-
delay: number
|
|
24
|
-
): T {
|
|
25
|
-
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
26
|
-
|
|
27
|
-
const debouncedCallback = useCallback(
|
|
28
|
-
(...args: Parameters<T>) => {
|
|
29
|
-
if (timeoutRef.current) {
|
|
30
|
-
clearTimeout(timeoutRef.current)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
timeoutRef.current = setTimeout(() => {
|
|
34
|
-
callback(...args)
|
|
35
|
-
}, delay)
|
|
36
|
-
},
|
|
37
|
-
[callback, delay]
|
|
38
|
-
) as T
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
return () => {
|
|
42
|
-
if (timeoutRef.current) {
|
|
43
|
-
clearTimeout(timeoutRef.current)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}, [])
|
|
47
|
-
|
|
48
|
-
return debouncedCallback
|
|
49
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback, useRef } from 'react'
|
|
4
|
+
|
|
5
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
6
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const timer = setTimeout(() => {
|
|
10
|
+
setDebouncedValue(value)
|
|
11
|
+
}, delay)
|
|
12
|
+
|
|
13
|
+
return () => {
|
|
14
|
+
clearTimeout(timer)
|
|
15
|
+
}
|
|
16
|
+
}, [value, delay])
|
|
17
|
+
|
|
18
|
+
return debouncedValue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useDebouncedCallback<T extends (...args: Parameters<T>) => ReturnType<T>>(
|
|
22
|
+
callback: T,
|
|
23
|
+
delay: number
|
|
24
|
+
): T {
|
|
25
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
26
|
+
|
|
27
|
+
const debouncedCallback = useCallback(
|
|
28
|
+
(...args: Parameters<T>) => {
|
|
29
|
+
if (timeoutRef.current) {
|
|
30
|
+
clearTimeout(timeoutRef.current)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
timeoutRef.current = setTimeout(() => {
|
|
34
|
+
callback(...args)
|
|
35
|
+
}, delay)
|
|
36
|
+
},
|
|
37
|
+
[callback, delay]
|
|
38
|
+
) as T
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
return () => {
|
|
42
|
+
if (timeoutRef.current) {
|
|
43
|
+
clearTimeout(timeoutRef.current)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}, [])
|
|
47
|
+
|
|
48
|
+
return debouncedCallback
|
|
49
|
+
}
|