@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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const COLOR_STYLES = {
|
|
4
|
+
blue: {
|
|
5
|
+
icon: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
|
|
6
|
+
activeBorder:
|
|
7
|
+
"border-blue-500/50 shadow-sm ring-1 ring-blue-500/20 bg-gradient-to-br from-base-100 to-blue-500/[0.03]",
|
|
8
|
+
},
|
|
9
|
+
emerald: {
|
|
10
|
+
icon: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
|
11
|
+
activeBorder:
|
|
12
|
+
"border-emerald-500/50 shadow-sm ring-1 ring-emerald-500/20 bg-gradient-to-br from-base-100 to-emerald-500/[0.03]",
|
|
13
|
+
},
|
|
14
|
+
amber: {
|
|
15
|
+
icon: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
16
|
+
activeBorder:
|
|
17
|
+
"border-amber-500/50 shadow-sm ring-1 ring-amber-500/20 bg-gradient-to-br from-base-100 to-amber-500/[0.03]",
|
|
18
|
+
},
|
|
19
|
+
purple: {
|
|
20
|
+
icon: "bg-purple-500/10 text-purple-600 dark:text-purple-400",
|
|
21
|
+
activeBorder:
|
|
22
|
+
"border-purple-500/50 shadow-sm ring-1 ring-purple-500/20 bg-gradient-to-br from-base-100 to-purple-500/[0.03]",
|
|
23
|
+
},
|
|
24
|
+
primary: {
|
|
25
|
+
icon: "bg-primary/10 text-primary",
|
|
26
|
+
activeBorder:
|
|
27
|
+
"border-primary/50 shadow-sm ring-1 ring-primary/20 bg-gradient-to-br from-base-100 to-primary/[0.03]",
|
|
28
|
+
},
|
|
29
|
+
secondary: {
|
|
30
|
+
icon: "bg-secondary/10 text-secondary",
|
|
31
|
+
activeBorder:
|
|
32
|
+
"border-secondary/50 shadow-sm ring-1 ring-secondary/20 bg-gradient-to-br from-base-100 to-secondary/[0.03]",
|
|
33
|
+
},
|
|
34
|
+
success: {
|
|
35
|
+
icon: "bg-success/10 text-success",
|
|
36
|
+
activeBorder:
|
|
37
|
+
"border-success/50 shadow-sm ring-1 ring-success/20 bg-gradient-to-br from-base-100 to-success/[0.03]",
|
|
38
|
+
},
|
|
39
|
+
warning: {
|
|
40
|
+
icon: "bg-warning/10 text-warning",
|
|
41
|
+
activeBorder:
|
|
42
|
+
"border-warning/50 shadow-sm ring-1 ring-warning/20 bg-gradient-to-br from-base-100 to-warning/[0.03]",
|
|
43
|
+
},
|
|
44
|
+
info: {
|
|
45
|
+
icon: "bg-info/10 text-info",
|
|
46
|
+
activeBorder:
|
|
47
|
+
"border-info/50 shadow-sm ring-1 ring-info/20 bg-gradient-to-br from-base-100 to-info/[0.03]",
|
|
48
|
+
},
|
|
49
|
+
error: {
|
|
50
|
+
icon: "bg-error/10 text-error",
|
|
51
|
+
activeBorder:
|
|
52
|
+
"border-error/50 shadow-sm ring-1 ring-error/20 bg-gradient-to-br from-base-100 to-error/[0.03]",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const StatsCard = ({
|
|
57
|
+
title,
|
|
58
|
+
value,
|
|
59
|
+
subValue,
|
|
60
|
+
trend,
|
|
61
|
+
icon,
|
|
62
|
+
color = "primary",
|
|
63
|
+
percentage,
|
|
64
|
+
badge,
|
|
65
|
+
description,
|
|
66
|
+
active = false,
|
|
67
|
+
loading = false,
|
|
68
|
+
onClick,
|
|
69
|
+
className = "",
|
|
70
|
+
}) => {
|
|
71
|
+
const styles = COLOR_STYLES[color] || COLOR_STYLES.primary;
|
|
72
|
+
|
|
73
|
+
// Support both new `subValue` prop and deprecated `percentage` prop
|
|
74
|
+
const displaySubValue =
|
|
75
|
+
subValue !== undefined
|
|
76
|
+
? subValue
|
|
77
|
+
: percentage !== undefined && percentage !== null
|
|
78
|
+
? typeof percentage === "number"
|
|
79
|
+
? `${percentage}%`
|
|
80
|
+
: percentage
|
|
81
|
+
: null;
|
|
82
|
+
|
|
83
|
+
const renderBadge = () => {
|
|
84
|
+
if (!badge) return null;
|
|
85
|
+
if (React.isValidElement(badge)) return badge;
|
|
86
|
+
const b = badge;
|
|
87
|
+
return (
|
|
88
|
+
<span
|
|
89
|
+
className={`badge badge-xs font-medium ${b.variant ? `badge-${b.variant}` : "badge-ghost"}`}
|
|
90
|
+
>
|
|
91
|
+
{b.label}
|
|
92
|
+
</span>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const renderTrend = () => {
|
|
97
|
+
if (!trend) return null;
|
|
98
|
+
if (React.isValidElement(trend)) return trend;
|
|
99
|
+
const t = trend;
|
|
100
|
+
const isPos = t.isPositive ?? true;
|
|
101
|
+
return (
|
|
102
|
+
<span
|
|
103
|
+
className={`inline-flex items-center gap-0.5 text-xs font-semibold ${
|
|
104
|
+
isPos ? "text-success" : "text-error"
|
|
105
|
+
}`}
|
|
106
|
+
>
|
|
107
|
+
<span>{isPos ? "↑" : "↓"}</span>
|
|
108
|
+
<span>{t.value}</span>
|
|
109
|
+
{t.label && (
|
|
110
|
+
<span className="opacity-70 font-normal ml-0.5">{t.label}</span>
|
|
111
|
+
)}
|
|
112
|
+
</span>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div
|
|
118
|
+
onClick={onClick}
|
|
119
|
+
className={`card bg-base-100 border transition-all duration-200 p-4 rounded-2xl ${
|
|
120
|
+
onClick ? "cursor-pointer hover:shadow-sm active:scale-[0.99]" : ""
|
|
121
|
+
} ${
|
|
122
|
+
active ? styles.activeBorder : "border-base-200 hover:border-base-300"
|
|
123
|
+
} ${className}`}
|
|
124
|
+
>
|
|
125
|
+
{/* Top row: Title and Icon */}
|
|
126
|
+
<div className="flex items-center justify-between gap-2">
|
|
127
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-base-content/60 truncate">
|
|
128
|
+
{title}
|
|
129
|
+
</span>
|
|
130
|
+
{icon && (
|
|
131
|
+
<div
|
|
132
|
+
className={`w-9 h-9 rounded-xl flex items-center justify-center shrink-0 ${styles.icon}`}
|
|
133
|
+
>
|
|
134
|
+
{icon}
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Middle row: Value and optional SubValue/Trend */}
|
|
140
|
+
<div className="mt-2 flex items-baseline gap-2 flex-wrap">
|
|
141
|
+
<span className="text-2xl md:text-3xl font-bold tracking-tight text-base-content">
|
|
142
|
+
{loading ? (
|
|
143
|
+
<span className="loading loading-spinner loading-sm"></span>
|
|
144
|
+
) : (
|
|
145
|
+
value
|
|
146
|
+
)}
|
|
147
|
+
</span>
|
|
148
|
+
{displaySubValue !== null && !loading && (
|
|
149
|
+
<span className="text-xs font-semibold opacity-80">
|
|
150
|
+
{displaySubValue}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
{!loading && renderTrend()}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Bottom row: Badge and description */}
|
|
157
|
+
{(badge || description) && (
|
|
158
|
+
<div className="mt-2 flex items-center gap-1.5 text-xs text-base-content/60 min-w-0">
|
|
159
|
+
{renderBadge()}
|
|
160
|
+
{description && <span className="truncate">{description}</span>}
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default StatsCard;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { Pagination } from "./Pagination";
|
|
3
|
+
|
|
3
4
|
const Table = ({
|
|
4
5
|
columns = [],
|
|
5
6
|
data = [],
|
|
@@ -9,135 +10,250 @@ const Table = ({
|
|
|
9
10
|
currentPage = 1,
|
|
10
11
|
totalPages = 1,
|
|
11
12
|
onPageChange = null,
|
|
13
|
+
wrapperClassName = "",
|
|
14
|
+
renderCard,
|
|
15
|
+
pagination = false,
|
|
16
|
+
header,
|
|
12
17
|
}) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
18
|
+
const observerTarget = useRef(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!pagination || !onPageChange || currentPage >= totalPages) return;
|
|
22
|
+
|
|
23
|
+
const observer = new IntersectionObserver(
|
|
24
|
+
(entries) => {
|
|
25
|
+
if (entries[0].isIntersecting) {
|
|
26
|
+
onPageChange(currentPage + 1);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{ threshold: 0.1 },
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (observerTarget.current) {
|
|
33
|
+
observer.observe(observerTarget.current);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return () => observer.disconnect();
|
|
37
|
+
}, [pagination, onPageChange, currentPage, totalPages]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
className={`flex flex-col w-full h-full md:border md:border-base-300 rounded-xl overflow-hidden bg-base-100 ${wrapperClassName}`}
|
|
42
|
+
>
|
|
43
|
+
{/* Optional Custom Header Area */}
|
|
44
|
+
{header && (
|
|
45
|
+
<div className="shrink-0 bg-base-100 border-b border-base-300">
|
|
46
|
+
{header}
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
{/* Desktop Table View (hidden on mobile if renderCard provided) */}
|
|
51
|
+
<div
|
|
52
|
+
className={`overflow-auto w-full flex-1 ${renderCard ? "hidden md:block" : "block"}`}
|
|
53
|
+
>
|
|
54
|
+
<table className="table table-hover w-full min-w-full relative border-collapse">
|
|
55
|
+
<thead className="bg-base-200 backdrop-blur sticky top-0 z-10 border-b border-base-300">
|
|
56
|
+
<tr>
|
|
57
|
+
{columns.map((col, index) => (
|
|
58
|
+
<th
|
|
59
|
+
key={col.accessor || index}
|
|
60
|
+
className={`px-6 py-4 text-left text-xs font-semibold text-base-content/70 uppercase tracking-wider bg-transparent border-none ${col.headerClass || ""}`}
|
|
61
|
+
>
|
|
62
|
+
{col.header}
|
|
63
|
+
</th>
|
|
64
|
+
))}
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
<tbody className="divide-y divide-base-200/60 bg-base-100">
|
|
68
|
+
{loading ? (
|
|
69
|
+
Array.from({ length: 5 }).map((_, rowIndex) => (
|
|
70
|
+
<tr key={`skeleton-${rowIndex}`} className="animate-pulse">
|
|
71
|
+
{columns.map((col, colIndex) => {
|
|
72
|
+
const isRight = col.headerClass?.includes("text-right");
|
|
73
|
+
const isFirst = colIndex === 0;
|
|
74
|
+
const isLast = colIndex === columns.length - 1;
|
|
75
|
+
const headerText =
|
|
76
|
+
typeof col.header === "string"
|
|
77
|
+
? col.header.toLowerCase()
|
|
78
|
+
: "";
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<td
|
|
82
|
+
key={colIndex}
|
|
83
|
+
className={`px-6 py-4 whitespace-nowrap align-middle border-none ${col.headerClass || ""}`}
|
|
84
|
+
>
|
|
85
|
+
{isRight ||
|
|
86
|
+
(isLast && headerText.includes("action")) ? (
|
|
87
|
+
<div className="flex justify-end items-center gap-2">
|
|
88
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
89
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
90
|
+
</div>
|
|
91
|
+
) : headerText.includes("assigned") ? (
|
|
92
|
+
<div className="h-6 w-28 rounded-md bg-base-200 border border-base-300/80"></div>
|
|
93
|
+
) : isFirst ? (
|
|
94
|
+
<div className="space-y-1.5">
|
|
95
|
+
<div
|
|
96
|
+
className="h-4 bg-base-300/80 rounded"
|
|
97
|
+
style={{
|
|
98
|
+
width: `${65 + (rowIndex % 3) * 15}%`,
|
|
99
|
+
maxWidth: "140px",
|
|
100
|
+
}}
|
|
101
|
+
></div>
|
|
102
|
+
<div
|
|
103
|
+
className="h-3 bg-base-200 rounded"
|
|
104
|
+
style={{
|
|
105
|
+
width: `${40 + (rowIndex % 2) * 15}%`,
|
|
106
|
+
maxWidth: "90px",
|
|
107
|
+
}}
|
|
108
|
+
></div>
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
<div
|
|
112
|
+
className={`h-4 bg-base-300/70 rounded ${isRight ? "ml-auto" : ""}`}
|
|
113
|
+
style={{
|
|
114
|
+
width: `${50 + ((rowIndex + colIndex) % 4) * 12}%`,
|
|
115
|
+
maxWidth: "160px",
|
|
116
|
+
}}
|
|
117
|
+
></div>
|
|
118
|
+
)}
|
|
119
|
+
</td>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
</tr>
|
|
123
|
+
))
|
|
124
|
+
) : data.length > 0 ? (
|
|
125
|
+
data.map((row, rowIndex) => (
|
|
126
|
+
<tr
|
|
127
|
+
key={row.id || rowIndex}
|
|
128
|
+
onClick={() => onRowClick && onRowClick(row)}
|
|
129
|
+
className={`transition-all duration-200 group ${
|
|
130
|
+
onRowClick
|
|
131
|
+
? "cursor-pointer hover:bg-base-200/30"
|
|
132
|
+
: "hover:bg-base-200/10"
|
|
133
|
+
}`}
|
|
134
|
+
>
|
|
135
|
+
{columns.map((col, colIndex) => {
|
|
136
|
+
const value = col.accessor ? row[col.accessor] : undefined;
|
|
137
|
+
return (
|
|
138
|
+
<td
|
|
139
|
+
key={col.accessor || colIndex}
|
|
140
|
+
className="px-6 py-4 text-[13px] text-base-content/80 whitespace-nowrap align-middle border-none"
|
|
141
|
+
>
|
|
142
|
+
{col.render ? col.render(value, row, rowIndex) : value}
|
|
143
|
+
</td>
|
|
144
|
+
);
|
|
145
|
+
})}
|
|
146
|
+
</tr>
|
|
147
|
+
))
|
|
148
|
+
) : (
|
|
149
|
+
<tr>
|
|
150
|
+
<td
|
|
151
|
+
colSpan={columns.length}
|
|
152
|
+
className="px-6 py-12 text-center text-sm text-base-content/60"
|
|
153
|
+
>
|
|
154
|
+
<div className="flex flex-col items-center justify-center gap-2">
|
|
155
|
+
<span className="text-2xl">📁</span>
|
|
156
|
+
<span>{emptyMessage}</span>
|
|
157
|
+
</div>
|
|
158
|
+
</td>
|
|
159
|
+
</tr>
|
|
160
|
+
)}
|
|
161
|
+
</tbody>
|
|
162
|
+
</table>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{/* Mobile Card View (only visible if renderCard provided) */}
|
|
166
|
+
{renderCard && (
|
|
167
|
+
<div className="md:hidden flex flex-col divide-y divide-base-200 overflow-y-auto flex-1">
|
|
168
|
+
{loading ? (
|
|
169
|
+
Array.from({ length: 4 }).map((_, i) => (
|
|
170
|
+
<div
|
|
171
|
+
key={`card-skeleton-${i}`}
|
|
172
|
+
className="p-4 flex flex-col gap-3 animate-pulse"
|
|
173
|
+
>
|
|
174
|
+
<div className="flex justify-between items-start">
|
|
175
|
+
<div className="space-y-1.5 flex-1">
|
|
176
|
+
<div className="h-4.5 bg-base-300/80 rounded w-36"></div>
|
|
177
|
+
<div className="h-3 bg-base-200 rounded w-24"></div>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex gap-2">
|
|
180
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
181
|
+
<div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<div className="space-y-2">
|
|
185
|
+
<div className="h-3.5 bg-base-200 rounded w-48"></div>
|
|
186
|
+
<div className="h-3.5 bg-base-200 rounded w-32"></div>
|
|
187
|
+
</div>
|
|
188
|
+
<div className="flex items-center justify-between pt-2 border-t border-base-200">
|
|
189
|
+
<div className="h-3 bg-base-200 rounded w-20"></div>
|
|
190
|
+
<div className="h-6 bg-base-300/80 rounded-lg w-28"></div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
))
|
|
194
|
+
) : data.length > 0 ? (
|
|
195
|
+
<>
|
|
196
|
+
{data.map((row, index) => (
|
|
197
|
+
<div
|
|
198
|
+
key={row.id || index}
|
|
199
|
+
onClick={() => onRowClick && onRowClick(row)}
|
|
200
|
+
className={onRowClick ? "cursor-pointer" : ""}
|
|
201
|
+
>
|
|
202
|
+
{renderCard(row, index)}
|
|
203
|
+
</div>
|
|
204
|
+
))}
|
|
205
|
+
{pagination && currentPage < totalPages && (
|
|
206
|
+
<div ref={observerTarget} className="p-6 flex justify-center">
|
|
207
|
+
<span className="loading loading-spinner loading-sm text-primary"></span>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</>
|
|
211
|
+
) : (
|
|
212
|
+
<div className="p-12 text-center text-sm text-base-content/60 flex flex-col items-center gap-2">
|
|
213
|
+
<span className="text-2xl">📁</span>
|
|
214
|
+
<span>{emptyMessage}</span>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{/* Desktop Pagination / Load More Footer */}
|
|
221
|
+
{pagination && onPageChange && totalPages > 1 && (
|
|
222
|
+
<div
|
|
223
|
+
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"}`}
|
|
224
|
+
>
|
|
225
|
+
<div className="text-xs text-base-content/75 font-medium">
|
|
226
|
+
Showing <span className="font-bold">{data.length}</span> items
|
|
227
|
+
</div>
|
|
228
|
+
<div className="flex gap-2">
|
|
229
|
+
{currentPage < totalPages && (
|
|
230
|
+
<button
|
|
231
|
+
onClick={() => onPageChange(currentPage + 1)}
|
|
232
|
+
className="btn btn-sm btn-outline btn-primary rounded-lg text-xs"
|
|
233
|
+
>
|
|
234
|
+
Load More
|
|
235
|
+
</button>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
|
|
241
|
+
{/* Standard Pagination (if pagination prop is not true) */}
|
|
242
|
+
{!pagination && onPageChange && totalPages > 0 && (
|
|
243
|
+
<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">
|
|
244
|
+
<div className="text-xs text-base-content/75 font-medium whitespace-nowrap">
|
|
245
|
+
Page <span className="font-bold">{currentPage}</span> of{" "}
|
|
246
|
+
<span className="font-bold">{totalPages}</span>
|
|
247
|
+
</div>
|
|
248
|
+
<Pagination
|
|
249
|
+
currentPage={currentPage}
|
|
250
|
+
totalPages={totalPages}
|
|
251
|
+
onPageChange={onPageChange}
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
141
257
|
};
|
|
142
|
-
|
|
143
|
-
export
|
|
258
|
+
|
|
259
|
+
export default Table;
|