@imksh/create 1.0.30 → 1.0.31
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 +1 -1
- package/src/utils/askTemplate.js +2 -1
- package/templates/layers/electron/base/electron/main.js +262 -0
- package/templates/layers/electron/base/electron/preload.js +37 -0
- package/templates/layers/electron/base/electron-builder.json5 +68 -0
- package/templates/layers/electron/javascript/_gitignore +38 -0
- package/templates/layers/electron/javascript/package.json +49 -0
- package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
- package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
- package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
- package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
- package/templates/layers/electron/javascript/src/main.jsx +17 -0
- package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
- package/templates/layers/electron/javascript/vite.config.js +29 -0
- package/templates/layers/electron/typescript/_gitignore +38 -0
- package/templates/layers/electron/typescript/electron/main.ts +213 -0
- package/templates/layers/electron/typescript/electron/preload.ts +52 -0
- package/templates/layers/electron/typescript/package.json +52 -0
- package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
- package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
- package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
- package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
- package/templates/layers/electron/typescript/src/main.tsx +17 -0
- package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
- package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
- package/templates/layers/electron/typescript/tsconfig.json +25 -0
- package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
- package/templates/layers/electron/typescript/vite.config.ts +29 -0
- package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
- package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
- package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
- package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
- package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
- package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
- package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
- package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
- package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
- package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
- package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
- package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
- package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
- package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
- package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
- package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
- package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
- package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
- package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
- package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
- package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
- package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
- package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
- package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
- package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
- package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
- package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
- package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
- package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
- package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
- package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
- package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
- package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
- package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
- package/templates/layers/node/typescript/package-lock.json +2 -2
- package/templates/layers/node/typescript/package.json +1 -1
- package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
- package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
- package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
- package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
- package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
- package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
- package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
- package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
- package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
- package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
- package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
- package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
- package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
- package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
- package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
- package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
- package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
- package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
- package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
- package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
- package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
- package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
- package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
- package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
- package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
- package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
- package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
- package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
- package/templates/presets/electron/preset.json +9 -0
- package/templates/presets/electron-ts/preset.json +9 -0
- package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useEffect, useRef } from "react";
|
|
3
|
+
import { Pagination } from "./Pagination";
|
|
3
4
|
|
|
4
5
|
const Table = ({
|
|
5
6
|
columns = [],
|
|
@@ -10,39 +11,126 @@ const Table = ({
|
|
|
10
11
|
currentPage = 1,
|
|
11
12
|
totalPages = 1,
|
|
12
13
|
onPageChange = null,
|
|
14
|
+
wrapperClassName = "",
|
|
15
|
+
renderCard,
|
|
16
|
+
pagination = false,
|
|
17
|
+
header,
|
|
13
18
|
}) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const observerTarget = useRef(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!pagination || !onPageChange || currentPage >= totalPages) return;
|
|
23
|
+
|
|
24
|
+
const observer = new IntersectionObserver(
|
|
25
|
+
(entries) => {
|
|
26
|
+
if (entries[0].isIntersecting) {
|
|
27
|
+
onPageChange(currentPage + 1);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{ threshold: 0.1 },
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (observerTarget.current) {
|
|
34
|
+
observer.observe(observerTarget.current);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return () => observer.disconnect();
|
|
38
|
+
}, [pagination, onPageChange, currentPage, totalPages]);
|
|
17
39
|
|
|
18
40
|
return (
|
|
19
|
-
<div
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
<div
|
|
42
|
+
className={`flex flex-col w-full h-full md:border md:border-base-300 rounded-xl overflow-hidden bg-base-100 ${wrapperClassName}`}
|
|
43
|
+
>
|
|
44
|
+
{/* Optional Custom Header Area */}
|
|
45
|
+
{header && (
|
|
46
|
+
<div className="shrink-0 bg-base-100 border-b border-base-300">
|
|
47
|
+
{header}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{/* Desktop Table View (hidden on mobile if renderCard provided) */}
|
|
52
|
+
<div
|
|
53
|
+
className={`overflow-auto w-full flex-1 ${renderCard ? "hidden md:block" : "block"}`}
|
|
54
|
+
>
|
|
55
|
+
<table className="table table-hover w-full min-w-full relative border-collapse">
|
|
56
|
+
<thead className="bg-base-200 backdrop-blur sticky top-0 z-10 border-b border-base-300">
|
|
25
57
|
<tr>
|
|
26
58
|
{columns.map((col, index) => (
|
|
27
59
|
<th
|
|
28
60
|
key={col.accessor || index}
|
|
29
|
-
className=
|
|
61
|
+
className={`px-6 py-4 text-left text-xs font-semibold text-base-content/70 uppercase tracking-wider bg-transparent border-none ${col.headerClass || ""}`}
|
|
30
62
|
>
|
|
31
63
|
{col.header}
|
|
32
64
|
</th>
|
|
33
65
|
))}
|
|
34
66
|
</tr>
|
|
35
67
|
</thead>
|
|
68
|
+
<tbody className="divide-y divide-base-200/60 bg-base-100">
|
|
69
|
+
{loading ? (
|
|
70
|
+
Array.from({ length: 5 }).map((_, rowIndex) => (
|
|
71
|
+
<tr key={`skeleton-${rowIndex}`} className="animate-pulse">
|
|
72
|
+
{columns.map((col, colIndex) => {
|
|
73
|
+
const isRight = col.headerClass?.includes("text-right");
|
|
74
|
+
const isFirst = colIndex === 0;
|
|
75
|
+
const isLast = colIndex === columns.length - 1;
|
|
76
|
+
const headerText =
|
|
77
|
+
typeof col.header === "string"
|
|
78
|
+
? col.header.toLowerCase()
|
|
79
|
+
: "";
|
|
36
80
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
81
|
+
return (
|
|
82
|
+
<td
|
|
83
|
+
key={colIndex}
|
|
84
|
+
className={`px-6 py-4 whitespace-nowrap align-middle border-none ${col.headerClass || ""}`}
|
|
85
|
+
>
|
|
86
|
+
{isRight ||
|
|
87
|
+
(isLast && headerText.includes("action")) ? (
|
|
88
|
+
<div className="flex justify-end items-center gap-2">
|
|
89
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
90
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
91
|
+
</div>
|
|
92
|
+
) : headerText.includes("assigned") ? (
|
|
93
|
+
<div className="h-6 w-28 rounded-md bg-base-200 border border-base-300/80"></div>
|
|
94
|
+
) : isFirst ? (
|
|
95
|
+
<div className="space-y-1.5">
|
|
96
|
+
<div
|
|
97
|
+
className="h-4 bg-base-300/80 rounded"
|
|
98
|
+
style={{
|
|
99
|
+
width: `${65 + (rowIndex % 3) * 15}%`,
|
|
100
|
+
maxWidth: "140px",
|
|
101
|
+
}}
|
|
102
|
+
></div>
|
|
103
|
+
<div
|
|
104
|
+
className="h-3 bg-base-200 rounded"
|
|
105
|
+
style={{
|
|
106
|
+
width: `${40 + (rowIndex % 2) * 15}%`,
|
|
107
|
+
maxWidth: "90px",
|
|
108
|
+
}}
|
|
109
|
+
></div>
|
|
110
|
+
</div>
|
|
111
|
+
) : (
|
|
112
|
+
<div
|
|
113
|
+
className={`h-4 bg-base-300/70 rounded ${isRight ? "ml-auto" : ""}`}
|
|
114
|
+
style={{
|
|
115
|
+
width: `${50 + ((rowIndex + colIndex) % 4) * 12}%`,
|
|
116
|
+
maxWidth: "160px",
|
|
117
|
+
}}
|
|
118
|
+
></div>
|
|
119
|
+
)}
|
|
120
|
+
</td>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</tr>
|
|
124
|
+
))
|
|
125
|
+
) : data.length > 0 ? (
|
|
40
126
|
data.map((row, rowIndex) => (
|
|
41
127
|
<tr
|
|
42
128
|
key={row.id || rowIndex}
|
|
43
129
|
onClick={() => onRowClick && onRowClick(row)}
|
|
44
|
-
className={`transition-
|
|
45
|
-
onRowClick
|
|
130
|
+
className={`transition-all duration-200 group ${
|
|
131
|
+
onRowClick
|
|
132
|
+
? "cursor-pointer hover:bg-base-200/30"
|
|
133
|
+
: "hover:bg-base-200/10"
|
|
46
134
|
}`}
|
|
47
135
|
>
|
|
48
136
|
{columns.map((col, colIndex) => {
|
|
@@ -50,7 +138,7 @@ const Table = ({
|
|
|
50
138
|
return (
|
|
51
139
|
<td
|
|
52
140
|
key={col.accessor || colIndex}
|
|
53
|
-
className="px-6 py-4 text-
|
|
141
|
+
className="px-6 py-4 text-[13px] text-base-content/80 whitespace-nowrap align-middle border-none"
|
|
54
142
|
>
|
|
55
143
|
{col.render ? col.render(value, row, rowIndex) : value}
|
|
56
144
|
</td>
|
|
@@ -75,36 +163,98 @@ const Table = ({
|
|
|
75
163
|
</table>
|
|
76
164
|
</div>
|
|
77
165
|
|
|
78
|
-
{/*
|
|
79
|
-
{
|
|
80
|
-
<div className="
|
|
81
|
-
{
|
|
166
|
+
{/* Mobile Card View (only visible if renderCard provided) */}
|
|
167
|
+
{renderCard && (
|
|
168
|
+
<div className="md:hidden flex flex-col divide-y divide-base-200 overflow-y-auto flex-1">
|
|
169
|
+
{loading ? (
|
|
170
|
+
Array.from({ length: 4 }).map((_, i) => (
|
|
171
|
+
<div
|
|
172
|
+
key={`card-skeleton-${i}`}
|
|
173
|
+
className="p-4 flex flex-col gap-3 animate-pulse"
|
|
174
|
+
>
|
|
175
|
+
<div className="flex justify-between items-start">
|
|
176
|
+
<div className="space-y-1.5 flex-1">
|
|
177
|
+
<div className="h-4.5 bg-base-300/80 rounded w-36"></div>
|
|
178
|
+
<div className="h-3 bg-base-200 rounded w-24"></div>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="flex gap-2">
|
|
181
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
182
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div className="space-y-2">
|
|
186
|
+
<div className="h-3.5 bg-base-200 rounded w-48"></div>
|
|
187
|
+
<div className="h-3.5 bg-base-200 rounded w-32"></div>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="flex items-center justify-between pt-2 border-t border-base-200">
|
|
190
|
+
<div className="h-3 bg-base-200 rounded w-20"></div>
|
|
191
|
+
<div className="h-6 bg-base-300/80 rounded-lg w-28"></div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
))
|
|
195
|
+
) : data.length > 0 ? (
|
|
196
|
+
<>
|
|
197
|
+
{data.map((row, index) => (
|
|
198
|
+
<div
|
|
199
|
+
key={row.id || index}
|
|
200
|
+
onClick={() => onRowClick && onRowClick(row)}
|
|
201
|
+
className={onRowClick ? "cursor-pointer" : ""}
|
|
202
|
+
>
|
|
203
|
+
{renderCard(row, index)}
|
|
204
|
+
</div>
|
|
205
|
+
))}
|
|
206
|
+
{pagination && currentPage < totalPages && (
|
|
207
|
+
<div ref={observerTarget} className="p-6 flex justify-center">
|
|
208
|
+
<span className="loading loading-spinner loading-sm text-primary"></span>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</>
|
|
212
|
+
) : (
|
|
213
|
+
<div className="p-12 text-center text-sm text-base-content/60 flex flex-col items-center gap-2">
|
|
214
|
+
<span className="text-2xl">📁</span>
|
|
215
|
+
<span>{emptyMessage}</span>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{/* Desktop Pagination / Load More Footer */}
|
|
222
|
+
{pagination && onPageChange && totalPages > 1 && (
|
|
223
|
+
<div
|
|
224
|
+
className={`border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center shrink-0 ${renderCard ? "hidden md:flex justify-between" : "justify-between"}`}
|
|
225
|
+
>
|
|
82
226
|
<div className="text-xs text-base-content/75 font-medium">
|
|
83
|
-
|
|
84
|
-
<span className="font-bold">{totalPages}</span>
|
|
227
|
+
Showing <span className="font-bold">{data.length}</span> items
|
|
85
228
|
</div>
|
|
86
|
-
|
|
87
|
-
{/* Page controls */}
|
|
88
229
|
<div className="flex gap-2">
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
230
|
+
{currentPage < totalPages && (
|
|
231
|
+
<button
|
|
232
|
+
onClick={() => onPageChange(currentPage + 1)}
|
|
233
|
+
className="btn btn-sm btn-outline btn-primary rounded-lg text-xs"
|
|
234
|
+
>
|
|
235
|
+
Load More
|
|
236
|
+
</button>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
|
|
242
|
+
{/* Standard Pagination (if pagination prop is not true) */}
|
|
243
|
+
{!pagination && onPageChange && totalPages > 0 && (
|
|
244
|
+
<div className="border-t border-base-300 px-3 sm:px-6 py-2.5 sm:py-4 bg-base-200/30 flex items-center justify-between shrink-0 mt-auto gap-2">
|
|
245
|
+
<div className="text-xs text-base-content/75 font-medium whitespace-nowrap">
|
|
246
|
+
Page <span className="font-bold">{currentPage}</span> of{" "}
|
|
247
|
+
<span className="font-bold">{totalPages}</span>
|
|
103
248
|
</div>
|
|
249
|
+
<Pagination
|
|
250
|
+
currentPage={currentPage}
|
|
251
|
+
totalPages={totalPages}
|
|
252
|
+
onPageChange={onPageChange}
|
|
253
|
+
/>
|
|
104
254
|
</div>
|
|
105
255
|
)}
|
|
106
256
|
</div>
|
|
107
257
|
);
|
|
108
258
|
};
|
|
109
259
|
|
|
110
|
-
export default Table;
|
|
260
|
+
export default Table;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
|
+
import { motion } from "framer-motion";
|
|
5
|
+
|
|
6
|
+
export const Tabs = ({
|
|
7
|
+
tabs,
|
|
8
|
+
activeTab,
|
|
9
|
+
onChange,
|
|
10
|
+
variant = "box",
|
|
11
|
+
className = "bg-base-200 w-fit",
|
|
12
|
+
dropdown = false,
|
|
13
|
+
disabled = false,
|
|
14
|
+
size = "base",
|
|
15
|
+
}) => {
|
|
16
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
17
|
+
|
|
18
|
+
const getTabSizeClass = () => {
|
|
19
|
+
switch (size) {
|
|
20
|
+
case "xs":
|
|
21
|
+
return "text-xs px-2.5 h-7 min-h-7";
|
|
22
|
+
case "sm":
|
|
23
|
+
return "text-sm px-3 h-8 min-h-8";
|
|
24
|
+
case "base":
|
|
25
|
+
return "text-base px-4 h-10 min-h-10";
|
|
26
|
+
case "lg":
|
|
27
|
+
return "text-lg px-6 h-12 min-h-12";
|
|
28
|
+
case "xl":
|
|
29
|
+
return "text-xl px-6 h-14 min-h-14";
|
|
30
|
+
case "2xl":
|
|
31
|
+
return "text-2xl px-8 h-16 min-h-16";
|
|
32
|
+
default:
|
|
33
|
+
return "text-base px-4 h-10 min-h-10";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getBtnSizeClass = () => {
|
|
38
|
+
switch (size) {
|
|
39
|
+
case "xs":
|
|
40
|
+
return "btn-xs text-xs";
|
|
41
|
+
case "sm":
|
|
42
|
+
return "btn-sm text-sm";
|
|
43
|
+
case "base":
|
|
44
|
+
return "text-base";
|
|
45
|
+
case "lg":
|
|
46
|
+
return "btn-lg text-lg";
|
|
47
|
+
case "xl":
|
|
48
|
+
return "btn-lg text-xl";
|
|
49
|
+
case "2xl":
|
|
50
|
+
return "btn-lg text-2xl";
|
|
51
|
+
default:
|
|
52
|
+
return "text-base";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const tabSizeClass = getTabSizeClass();
|
|
57
|
+
const btnSizeClass = getBtnSizeClass();
|
|
58
|
+
|
|
59
|
+
// Close dropdown if user clicks outside
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
if (!dropdown || !isOpen) return;
|
|
62
|
+
const closeMenu = () => setIsOpen(false);
|
|
63
|
+
document.addEventListener("click", closeMenu);
|
|
64
|
+
return () => document.removeEventListener("click", closeMenu);
|
|
65
|
+
}, [dropdown, isOpen]);
|
|
66
|
+
|
|
67
|
+
const renderDropdown = () => {
|
|
68
|
+
if (!dropdown) return null;
|
|
69
|
+
const activeItem = tabs.find((t) => t.id === activeTab);
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className="relative md:hidden w-full max-w-xs z-[50]"
|
|
73
|
+
onClick={(e) => e.stopPropagation()}
|
|
74
|
+
>
|
|
75
|
+
{/* Trigger Button */}
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
79
|
+
disabled={disabled}
|
|
80
|
+
className={`btn ${btnSizeClass} bg-base-200 border-base-300 border flex w-full items-center justify-between px-3 text-base-content ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
81
|
+
>
|
|
82
|
+
<div className="flex items-center gap-1.5">
|
|
83
|
+
{activeItem?.icon}
|
|
84
|
+
<span className="font-semibold">{activeItem?.label}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<ChevronDown
|
|
87
|
+
size={14}
|
|
88
|
+
className={`opacity-50 transition-transform ${isOpen ? "rotate-180" : ""}`}
|
|
89
|
+
/>
|
|
90
|
+
</button>
|
|
91
|
+
|
|
92
|
+
{/* Dropdown Menu */}
|
|
93
|
+
{isOpen && (
|
|
94
|
+
<div className="absolute top-full left-0 right-0 mt-1 bg-base-100 border border-base-200 shadow rounded-lg py-1 animate-in fade-in slide-in-from-top-2">
|
|
95
|
+
{tabs.map((tab) => (
|
|
96
|
+
<button
|
|
97
|
+
key={tab.id}
|
|
98
|
+
onClick={() => {
|
|
99
|
+
onChange(tab.id);
|
|
100
|
+
setIsOpen(false);
|
|
101
|
+
}}
|
|
102
|
+
type="button"
|
|
103
|
+
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${size === "base" ? "text-sm" : `text-${size}`} ${
|
|
104
|
+
activeTab === tab.id
|
|
105
|
+
? "bg-primary/10 text-primary font-semibold"
|
|
106
|
+
: "hover:bg-base-200 text-base-content/80"
|
|
107
|
+
}`}
|
|
108
|
+
>
|
|
109
|
+
{tab.icon}
|
|
110
|
+
{tab.label}
|
|
111
|
+
</button>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (variant === "box") {
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
{renderDropdown()}
|
|
123
|
+
<div
|
|
124
|
+
className={`tabs tabs-boxed p-1 flex-nowrap overflow-x-auto hide-scrollbar shrink-0 rounded-lg ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
|
|
125
|
+
>
|
|
126
|
+
{tabs.map((tab) => (
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
key={tab.id}
|
|
130
|
+
onClick={() => onChange(tab.id)}
|
|
131
|
+
disabled={disabled}
|
|
132
|
+
className={`tab ${tabSizeClass} whitespace-nowrap flex items-center rounded-lg gap-2 relative z-0 ${
|
|
133
|
+
activeTab === tab.id
|
|
134
|
+
? "text-primary-content"
|
|
135
|
+
: "text-base-content/70 hover:text-base-content "
|
|
136
|
+
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
137
|
+
>
|
|
138
|
+
{activeTab === tab.id && (
|
|
139
|
+
<motion.div
|
|
140
|
+
layoutId="activeTabBox"
|
|
141
|
+
className="absolute inset-0 bg-primary rounded-lg -z-10"
|
|
142
|
+
transition={{ type: "spring", duration: 0, bounce: 0.2 }}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
{tab.icon}
|
|
146
|
+
{tab.label}
|
|
147
|
+
</button>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// default: underline
|
|
155
|
+
return (
|
|
156
|
+
<>
|
|
157
|
+
{renderDropdown()}
|
|
158
|
+
<div
|
|
159
|
+
className={`tabs tabs-bordered ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
|
|
160
|
+
>
|
|
161
|
+
{tabs.map((tab) => (
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
key={tab.id}
|
|
165
|
+
onClick={() => onChange(tab.id)}
|
|
166
|
+
disabled={disabled}
|
|
167
|
+
className={`tab ${tabSizeClass} whitespace-nowrap relative !border-transparent ${
|
|
168
|
+
activeTab === tab.id
|
|
169
|
+
? "font-semibold text-primary"
|
|
170
|
+
: "text-base-content/70 hover:text-base-content"
|
|
171
|
+
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
172
|
+
>
|
|
173
|
+
{activeTab === tab.id && (
|
|
174
|
+
<motion.div
|
|
175
|
+
layoutId="activeTabUnderline"
|
|
176
|
+
className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary"
|
|
177
|
+
transition={{ type: "spring", duration: 0.5, bounce: 0.2 }}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
{tab.icon && <span className="mr-2">{tab.icon}</span>}
|
|
181
|
+
{tab.label}
|
|
182
|
+
</button>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
185
|
+
</>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { motion } from "motion/react";
|
|
3
4
|
|
|
4
|
-
const ToggleSwitch = ({ checked, onChange, label }) => {
|
|
5
|
+
const ToggleSwitch = ({ checked, onChange, label, className }) => {
|
|
5
6
|
return (
|
|
6
7
|
<div
|
|
7
|
-
className=
|
|
8
|
+
className={`flex items-center gap-3 cursor-pointer select-none w-fit group ${className}`}
|
|
8
9
|
onClick={() => onChange(!checked)}
|
|
9
10
|
>
|
|
10
11
|
<span className="text-sm font-bold">{label}</span>
|
|
@@ -16,7 +17,7 @@ const ToggleSwitch = ({ checked, onChange, label }) => {
|
|
|
16
17
|
}`}
|
|
17
18
|
>
|
|
18
19
|
<motion.div
|
|
19
|
-
className={`w-4 h-4 rounded-full shadow
|
|
20
|
+
className={`w-4 h-4 rounded-full shadow ${
|
|
20
21
|
checked ? "bg-white" : "bg-base-content/40"
|
|
21
22
|
}`}
|
|
22
23
|
layout
|
|
@@ -18,7 +18,8 @@ const ProtectedRoute = ({ children, role }) => {
|
|
|
18
18
|
}
|
|
19
19
|
}, [user, isCheckingAuth, role, router]);
|
|
20
20
|
|
|
21
|
-
if (isCheckingAuth || !user || (role && user.role !== role))
|
|
21
|
+
if (isCheckingAuth || !user || (role && user.role !== role))
|
|
22
|
+
return <Loading />;
|
|
22
23
|
|
|
23
24
|
return <>{children}</>;
|
|
24
25
|
};
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import { usePathname } from "next/navigation";
|
|
2
|
+
import { ReactNode } from "react";
|
|
4
3
|
import SidebarHeader from "../ui/SidebarHeader";
|
|
5
|
-
import Sidebar from "../ui/
|
|
4
|
+
import Sidebar from "../ui/Sidebar";
|
|
6
5
|
import { useUiStore } from "../../store/useUiStore";
|
|
7
|
-
import appConfig from "../../config/appConfig";
|
|
8
6
|
|
|
9
7
|
const SidebarLayout = ({ children }) => {
|
|
10
|
-
const { headerTitle, headerActions
|
|
11
|
-
useUiStore();
|
|
12
|
-
const pathname = usePathname();
|
|
13
|
-
|
|
14
|
-
// Reset header title and actions whenever the route shifts
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
setHeaderTitle(appConfig.app.name);
|
|
17
|
-
setHeaderActions(null);
|
|
18
|
-
}, [pathname, setHeaderTitle, setHeaderActions]);
|
|
8
|
+
const { headerTitle, headerActions } = useUiStore();
|
|
19
9
|
|
|
20
10
|
return (
|
|
21
11
|
<div className="flex h-screen overflow-hidden">
|
|
@@ -24,10 +14,7 @@ const SidebarLayout = ({ children }) => {
|
|
|
24
14
|
<div className="flex flex-1 flex-col overflow-hidden">
|
|
25
15
|
<SidebarHeader title={headerTitle} actions={headerActions} />
|
|
26
16
|
|
|
27
|
-
<main
|
|
28
|
-
className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
|
|
29
|
-
data-lenis-prevent
|
|
30
|
-
>
|
|
17
|
+
<main className="flex-1 h-full overflow-y-auto" data-lenis-prevent>
|
|
31
18
|
{children}
|
|
32
19
|
</main>
|
|
33
20
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { useEffect } from "react";
|
|
2
3
|
import { createPortal } from "react-dom";
|
|
3
4
|
import {
|
|
@@ -63,7 +64,7 @@ const ConfirmModal = ({
|
|
|
63
64
|
|
|
64
65
|
if (!open) return null;
|
|
65
66
|
|
|
66
|
-
const style = variants[variant
|
|
67
|
+
const style = variants[variant || "warning"];
|
|
67
68
|
const Icon = style.icon;
|
|
68
69
|
|
|
69
70
|
return createPortal(
|
|
@@ -79,9 +80,7 @@ const ConfirmModal = ({
|
|
|
79
80
|
|
|
80
81
|
<div className="relative w-full max-w-md mx-4 rounded-2xl bg-base-100 shadow-2xl animate-in fade-in zoom-in duration-200">
|
|
81
82
|
<div className="p-6">
|
|
82
|
-
|
|
83
83
|
<div className="flex gap-4">
|
|
84
|
-
|
|
85
84
|
<div
|
|
86
85
|
className={`w-14 h-14 rounded-full flex items-center justify-center shrink-0 ${style.iconBg}`}
|
|
87
86
|
>
|
|
@@ -89,21 +88,15 @@ const ConfirmModal = ({
|
|
|
89
88
|
</div>
|
|
90
89
|
|
|
91
90
|
<div className="flex-1">
|
|
92
|
-
|
|
93
|
-
<h2 className="text-xl font-bold">
|
|
94
|
-
{title}
|
|
95
|
-
</h2>
|
|
91
|
+
<h2 className="text-xl font-bold">{title}</h2>
|
|
96
92
|
|
|
97
93
|
<p className="mt-2 text-sm text-base-content/70 leading-relaxed">
|
|
98
94
|
{message}
|
|
99
95
|
</p>
|
|
100
|
-
|
|
101
96
|
</div>
|
|
102
|
-
|
|
103
97
|
</div>
|
|
104
98
|
|
|
105
99
|
<div className="mt-8 flex justify-end gap-3">
|
|
106
|
-
|
|
107
100
|
<button
|
|
108
101
|
className="btn btn-ghost"
|
|
109
102
|
disabled={loading}
|
|
@@ -123,7 +116,6 @@ const ConfirmModal = ({
|
|
|
123
116
|
|
|
124
117
|
{confirmText}
|
|
125
118
|
</button>
|
|
126
|
-
|
|
127
119
|
</div>
|
|
128
120
|
</div>
|
|
129
121
|
</div>
|
|
@@ -132,4 +124,4 @@ const ConfirmModal = ({
|
|
|
132
124
|
);
|
|
133
125
|
};
|
|
134
126
|
|
|
135
|
-
export default ConfirmModal;
|
|
127
|
+
export default ConfirmModal;
|