@goplusvn/core 0.1.20 → 0.1.21
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/package.json
CHANGED
|
@@ -480,7 +480,7 @@ export function DataTable<TData extends Record<string, unknown>>({
|
|
|
480
480
|
|
|
481
481
|
{/* Pagination Section */}
|
|
482
482
|
{pagination && (
|
|
483
|
-
<div className="border-t border-border dark:border-slate-800 bg-card px-
|
|
483
|
+
<div className="border-t border-border dark:border-slate-800 bg-card px-2 sm:px-4 py-1 shrink-0">
|
|
484
484
|
<MemoizedPagination
|
|
485
485
|
table={table}
|
|
486
486
|
currentPage={pagination.page}
|
|
@@ -65,21 +65,21 @@ export function DataTablePagination<TData>({
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
|
-
<div className="flex flex-col sm:flex-row items-center justify-between gap-1
|
|
69
|
-
<div className="hidden sm:block flex-1 text-
|
|
68
|
+
<div className="flex flex-col sm:flex-row items-center justify-between gap-1 px-1 sm:px-2">
|
|
69
|
+
<div className="hidden sm:block flex-1 text-[11px] text-left text-muted-foreground w-full">
|
|
70
70
|
Hiển thị {startItem} - {endItem} trong tổng số {total} mục
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
|
-
<div className="flex items-center justify-center gap-2 w-full sm:w-auto overflow-x-auto
|
|
74
|
-
<div className="flex items-center gap-1
|
|
75
|
-
<p className="hidden sm:block text-
|
|
73
|
+
<div className="flex items-center justify-center gap-2 w-full sm:w-auto overflow-x-auto">
|
|
74
|
+
<div className="flex items-center gap-1 gap-1 px-1 border-r pr-1 sm:pr-1.5">
|
|
75
|
+
<p className="hidden sm:block text-[11px] font-medium whitespace-nowrap">
|
|
76
76
|
Số dòng
|
|
77
77
|
</p>
|
|
78
78
|
<Select
|
|
79
79
|
value={`${effPageSize}`}
|
|
80
80
|
onValueChange={(value) => table.setPageSize(Number(value))}
|
|
81
81
|
>
|
|
82
|
-
<SelectTrigger className="h-6 w-[
|
|
82
|
+
<SelectTrigger className="!h-6 !min-h-0 py-0 w-[60px] sm:w-[65px] text-[11px] px-1.5 focus:ring-inset focus:ring-offset-0">
|
|
83
83
|
<SelectValue placeholder={effPageSize} />
|
|
84
84
|
</SelectTrigger>
|
|
85
85
|
<SelectContent side="top">
|
|
@@ -113,11 +113,11 @@ export function DataTablePagination<TData>({
|
|
|
113
113
|
</Button>
|
|
114
114
|
|
|
115
115
|
<div className="flex items-center gap-1 sm:gap-2 px-1">
|
|
116
|
-
<span className="hidden sm:inline text-
|
|
116
|
+
<span className="hidden sm:inline text-[11px] font-medium whitespace-nowrap">
|
|
117
117
|
Trang
|
|
118
118
|
</span>
|
|
119
119
|
<input
|
|
120
|
-
className="h-6 w-10 sm:w-12 rounded-md border border-input bg-background px-1 text-
|
|
120
|
+
className="h-6 w-10 sm:w-12 rounded-md border border-input bg-background px-1 text-[11px] text-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
121
121
|
value={inputPage}
|
|
122
122
|
onChange={(e) => setInputPage(e.target.value)}
|
|
123
123
|
onBlur={handlePageInputBlur}
|
|
@@ -126,7 +126,7 @@ export function DataTablePagination<TData>({
|
|
|
126
126
|
}}
|
|
127
127
|
disabled={total === 0}
|
|
128
128
|
/>
|
|
129
|
-
<span className="text-
|
|
129
|
+
<span className="text-[11px] font-medium text-muted-foreground whitespace-nowrap">
|
|
130
130
|
/ {pageCount}
|
|
131
131
|
</span>
|
|
132
132
|
</div>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
Select,
|
|
8
|
+
SelectContent,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectTrigger,
|
|
11
|
+
SelectValue,
|
|
12
|
+
} from "../primitives"
|
|
13
|
+
import {
|
|
14
|
+
ChevronLeft,
|
|
15
|
+
ChevronRight,
|
|
16
|
+
ChevronsLeft,
|
|
17
|
+
ChevronsRight,
|
|
18
|
+
} from "lucide-react"
|
|
19
|
+
|
|
20
|
+
interface PaginationProps {
|
|
21
|
+
totalItems: number
|
|
22
|
+
currentPage: number
|
|
23
|
+
pageSize: number
|
|
24
|
+
disabled?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function UrlPagination({
|
|
28
|
+
totalItems,
|
|
29
|
+
currentPage,
|
|
30
|
+
pageSize,
|
|
31
|
+
disabled,
|
|
32
|
+
}: PaginationProps) {
|
|
33
|
+
const router = useRouter()
|
|
34
|
+
const pathname = usePathname()
|
|
35
|
+
const searchParams = useSearchParams()
|
|
36
|
+
|
|
37
|
+
const pageCount = Math.ceil(totalItems / pageSize)
|
|
38
|
+
const startItem = (currentPage - 1) * pageSize + 1
|
|
39
|
+
const endItem = Math.min(currentPage * pageSize, totalItems)
|
|
40
|
+
|
|
41
|
+
// Create a new URLSearchParams instance to update params
|
|
42
|
+
const createQueryString = React.useCallback(
|
|
43
|
+
(params: Record<string, string | number | null>) => {
|
|
44
|
+
const newSearchParams = new URLSearchParams(searchParams?.toString())
|
|
45
|
+
|
|
46
|
+
for (const [key, value] of Object.entries(params)) {
|
|
47
|
+
if (value === null) {
|
|
48
|
+
newSearchParams.delete(key)
|
|
49
|
+
} else {
|
|
50
|
+
newSearchParams.set(key, String(value))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return newSearchParams.toString()
|
|
55
|
+
},
|
|
56
|
+
[searchParams]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const handlePageChange = (page: number) => {
|
|
60
|
+
if (page < 1 || page > Math.max(1, pageCount)) return
|
|
61
|
+
router.push(`${pathname}?${createQueryString({ page })}`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handlePageSizeChange = (value: string) => {
|
|
65
|
+
const newPageSize = Number(value)
|
|
66
|
+
// When changing page size, reset to page 1 to avoid out of bounds
|
|
67
|
+
router.push(
|
|
68
|
+
`${pathname}?${createQueryString({ pageSize: newPageSize, page: 1 })}`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const [inputPage, setInputPage] = React.useState(String(currentPage))
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
setInputPage(String(currentPage))
|
|
76
|
+
}, [currentPage])
|
|
77
|
+
|
|
78
|
+
const handlePageInputBlur = () => {
|
|
79
|
+
const pageNum = Number(inputPage)
|
|
80
|
+
if (isNaN(pageNum) || pageNum < 1 || pageNum > pageCount) {
|
|
81
|
+
setInputPage(String(currentPage))
|
|
82
|
+
} else if (pageNum !== currentPage) {
|
|
83
|
+
handlePageChange(pageNum)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const handlePageInputKeyDown = (e: React.KeyboardEvent) => {
|
|
88
|
+
if (e.key === "Enter") {
|
|
89
|
+
handlePageInputBlur()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="flex flex-col sm:flex-row items-center justify-between gap-1 sm:gap-2 px-1 sm:px-2">
|
|
95
|
+
{/* Ẩn text "Hiển thị" trên mobile, chỉ hiện trên màn hình sm trở lên */}
|
|
96
|
+
<div className="hidden sm:block flex-1 text-[11px] text-left text-muted-foreground w-full">
|
|
97
|
+
Hiển thị {totalItems > 0 ? startItem : 0} -{" "}
|
|
98
|
+
{totalItems > 0 ? endItem : 0} trong tổng số {totalItems} mục
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="flex items-center justify-center gap-2 w-full sm:w-auto overflow-x-auto">
|
|
102
|
+
<div className="flex items-center gap-1 gap-1 px-1 border-r pr-1 sm:pr-1.5">
|
|
103
|
+
<p className="hidden sm:block text-[11px] font-medium whitespace-nowrap">Số dòng</p>
|
|
104
|
+
<Select
|
|
105
|
+
value={`${pageSize}`}
|
|
106
|
+
onValueChange={handlePageSizeChange}
|
|
107
|
+
disabled={disabled}
|
|
108
|
+
>
|
|
109
|
+
<SelectTrigger className="!h-6 !min-h-0 py-0 w-[60px] sm:w-[65px] text-[11px] px-1.5 focus:ring-inset focus:ring-offset-0">
|
|
110
|
+
<SelectValue placeholder={pageSize} />
|
|
111
|
+
</SelectTrigger>
|
|
112
|
+
<SelectContent side="top">
|
|
113
|
+
{[10, 20, 50, 100, 500, 1000].map((size) => (
|
|
114
|
+
<SelectItem key={size} value={`${size}`}>
|
|
115
|
+
{size}
|
|
116
|
+
</SelectItem>
|
|
117
|
+
))}
|
|
118
|
+
</SelectContent>
|
|
119
|
+
</Select>
|
|
120
|
+
</div>
|
|
121
|
+
<div className="flex items-center gap-1 pl-1">
|
|
122
|
+
<Button
|
|
123
|
+
variant="outline"
|
|
124
|
+
className="hidden h-6 w-6 p-0 lg:flex"
|
|
125
|
+
onClick={() => handlePageChange(1)}
|
|
126
|
+
disabled={currentPage <= 1 || disabled || totalItems === 0}
|
|
127
|
+
>
|
|
128
|
+
<span className="sr-only">Trang đầu</span>
|
|
129
|
+
<ChevronsLeft className="h-3 w-3 sm:h-3 sm:w-3" />
|
|
130
|
+
</Button>
|
|
131
|
+
<Button
|
|
132
|
+
variant="outline"
|
|
133
|
+
className="h-6 w-6 p-0"
|
|
134
|
+
onClick={() => handlePageChange(currentPage - 1)}
|
|
135
|
+
disabled={currentPage <= 1 || disabled || totalItems === 0}
|
|
136
|
+
>
|
|
137
|
+
<span className="sr-only">Trang trước</span>
|
|
138
|
+
<ChevronLeft className="h-3 w-3 sm:h-3 sm:w-3" />
|
|
139
|
+
</Button>
|
|
140
|
+
|
|
141
|
+
<div className="flex items-center gap-1 sm:gap-2 px-1">
|
|
142
|
+
<span className="hidden sm:inline text-[11px] font-medium whitespace-nowrap">Trang</span>
|
|
143
|
+
<input
|
|
144
|
+
className="h-6 w-10 sm:w-12 rounded-md border border-input bg-background px-1 text-[11px] text-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
145
|
+
value={inputPage}
|
|
146
|
+
onChange={(e) => setInputPage(e.target.value)}
|
|
147
|
+
onBlur={handlePageInputBlur}
|
|
148
|
+
onKeyDown={handlePageInputKeyDown}
|
|
149
|
+
disabled={disabled || totalItems === 0}
|
|
150
|
+
/>
|
|
151
|
+
<span className="text-sm font-medium text-muted-foreground whitespace-nowrap">
|
|
152
|
+
/ {Math.max(1, pageCount)}
|
|
153
|
+
</span>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<Button
|
|
157
|
+
variant="outline"
|
|
158
|
+
className="h-6 w-6 p-0"
|
|
159
|
+
onClick={() => handlePageChange(currentPage + 1)}
|
|
160
|
+
disabled={currentPage >= pageCount || disabled || totalItems === 0}
|
|
161
|
+
>
|
|
162
|
+
<span className="sr-only">Trang sau</span>
|
|
163
|
+
<ChevronRight className="h-3 w-3 sm:h-3 sm:w-3" />
|
|
164
|
+
</Button>
|
|
165
|
+
<Button
|
|
166
|
+
variant="outline"
|
|
167
|
+
className="hidden h-6 w-6 p-0 lg:flex"
|
|
168
|
+
onClick={() => handlePageChange(pageCount)}
|
|
169
|
+
disabled={currentPage >= pageCount || disabled || totalItems === 0}
|
|
170
|
+
>
|
|
171
|
+
<span className="sr-only">Trang cuối</span>
|
|
172
|
+
<ChevronsRight className="h-3 w-3 sm:h-3 sm:w-3" />
|
|
173
|
+
</Button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|