@imksh/create 1.0.30 → 1.0.32

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.
Files changed (247) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/src/commands/create.js +2 -0
  4. package/src/index.js +1 -1
  5. package/src/utils/askTemplate.js +2 -1
  6. package/templates/layers/electron/base/electron/main.js +262 -0
  7. package/templates/layers/electron/base/electron/preload.js +37 -0
  8. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  9. package/templates/layers/electron/javascript/_gitignore +38 -0
  10. package/templates/layers/electron/javascript/package.json +49 -0
  11. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  12. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  13. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  14. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  15. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  16. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  17. package/templates/layers/electron/javascript/vite.config.js +29 -0
  18. package/templates/layers/electron/typescript/_gitignore +38 -0
  19. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  20. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  21. package/templates/layers/electron/typescript/package.json +52 -0
  22. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  23. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  24. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  25. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  26. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  27. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  28. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  29. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  30. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  31. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  32. package/templates/layers/next/javascript/package.json +8 -2
  33. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  34. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  35. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  36. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  37. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  38. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  39. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  40. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  41. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  42. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  43. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  44. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  45. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  46. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  47. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  48. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  49. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  50. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  51. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  52. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  53. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  54. package/templates/layers/next/typescript/package.json +14 -3
  55. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  56. package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
  57. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  58. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  59. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  60. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  61. package/templates/layers/next/typescript/src/components/common/Table.tsx +205 -39
  62. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  63. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  64. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  65. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  66. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +3 -1
  67. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  68. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  69. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  70. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  71. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  72. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  73. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  74. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  75. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  76. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  77. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  78. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  79. package/templates/layers/node/javascript/.env.example +3 -1
  80. package/templates/layers/node/javascript/package.json +12 -3
  81. package/templates/layers/node/javascript/src/app.js +2 -1
  82. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  83. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  84. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  85. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  86. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  87. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  88. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  89. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  90. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  91. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  92. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  93. package/templates/layers/node/typescript/.env.example +3 -1
  94. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  95. package/templates/layers/node/typescript/package-lock.json +2 -2
  96. package/templates/layers/node/typescript/package.json +17 -3
  97. package/templates/layers/node/typescript/src/app.ts +3 -2
  98. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  99. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  100. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  101. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  102. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  103. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  104. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  105. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  106. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  107. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  108. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  109. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  110. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  111. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  112. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  113. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  114. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  115. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  116. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  117. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  118. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  119. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  120. package/templates/layers/node-prisma/javascript/package.json +11 -2
  121. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  122. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  123. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  124. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  125. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  126. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  127. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  128. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  129. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  130. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  131. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  132. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  133. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  134. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  135. package/templates/layers/node-prisma/typescript/package.json +19 -5
  136. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  137. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  138. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  139. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  140. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  141. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  142. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  143. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  144. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  145. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  146. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  147. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  148. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  149. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  150. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  151. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  152. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  153. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  154. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  155. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  156. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  157. package/templates/layers/prisma/javascript/package.json +8 -0
  158. package/templates/layers/prisma/typescript/package.json +14 -1
  159. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  160. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  161. package/templates/layers/react/javascript/package.json +6 -0
  162. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  163. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  164. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  165. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  166. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  167. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  168. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  169. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  170. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  171. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  172. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  173. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  174. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  175. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  176. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  177. package/templates/layers/react/typescript/package.json +11 -2
  178. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  179. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  180. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  181. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  182. package/templates/layers/react/typescript/src/components/common/Table.tsx +201 -38
  183. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  184. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  185. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  186. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  187. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  188. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  189. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  190. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  191. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  192. package/templates/layers/react/typescript/src/main.tsx +11 -11
  193. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  194. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  195. package/templates/layers/react-native/base/app.json +9 -22
  196. package/templates/layers/react-native/base/eslint.config.js +5 -1
  197. package/templates/layers/react-native/base/package.json +11 -1
  198. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  199. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  200. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  201. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  202. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  203. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  204. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  205. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  206. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  207. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  208. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  209. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  210. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  211. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  212. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  213. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  214. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  215. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  216. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  217. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  218. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  219. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  220. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  221. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  222. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  223. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  224. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  225. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  226. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  227. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  228. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  229. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  230. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  231. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  232. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  233. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  234. package/templates/presets/electron/preset.json +9 -0
  235. package/templates/presets/electron-ts/preset.json +9 -0
  236. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  237. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  238. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  239. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  240. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  241. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  242. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  243. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  244. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
  245. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  246. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  247. /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
@@ -1,13 +1,14 @@
1
- import React from "react";
2
- import TableSkeleton from "../skeletons/common/TableSkeleton";
1
+ import React, { useEffect, useRef } from "react";
2
+ import { Pagination } from "./Pagination";
3
3
 
4
- export interface TableColumn<T = any> {
5
- header: string;
4
+ export interface TableColumn<T = Record<string, unknown>> {
5
+ header: React.ReactNode | string;
6
6
  accessor?: keyof T | string;
7
- render?: (value: any, row: T, index: number) => React.ReactNode;
7
+ render?: (value: unknown, row: T, index: number) => React.ReactNode;
8
+ headerClass?: string;
8
9
  }
9
10
 
10
- interface TableProps<T = any> {
11
+ interface TableProps<T = Record<string, unknown>> {
11
12
  columns: TableColumn<T>[];
12
13
  data: T[];
13
14
  loading?: boolean;
@@ -16,9 +17,13 @@ interface TableProps<T = any> {
16
17
  currentPage?: number;
17
18
  totalPages?: number;
18
19
  onPageChange?: ((page: number) => void) | null;
20
+ wrapperClassName?: string;
21
+ renderCard?: (row: T, index: number) => React.ReactNode;
22
+ pagination?: boolean; // if true, uses load more/infinite scroll on mobile
23
+ header?: React.ReactNode; // custom header content to render above the table
19
24
  }
20
25
 
21
- const Table = <T extends Record<string, any>>({
26
+ const Table = <T extends Record<string, unknown>>({
22
27
  columns = [],
23
28
  data = [],
24
29
  loading = false,
@@ -27,35 +32,126 @@ const Table = <T extends Record<string, any>>({
27
32
  currentPage = 1,
28
33
  totalPages = 1,
29
34
  onPageChange = null,
35
+ wrapperClassName = "",
36
+ renderCard,
37
+ pagination = false,
38
+ header,
30
39
  }: TableProps<T>) => {
31
- if (loading) {
32
- return <TableSkeleton rows={5} columns={columns.length || 4} />;
33
- }
40
+ const observerTarget = useRef<HTMLDivElement>(null);
41
+
42
+ useEffect(() => {
43
+ if (!pagination || !onPageChange || currentPage >= totalPages) return;
44
+
45
+ const observer = new IntersectionObserver(
46
+ (entries) => {
47
+ if (entries[0].isIntersecting) {
48
+ onPageChange(currentPage + 1);
49
+ }
50
+ },
51
+ { threshold: 0.1 },
52
+ );
53
+
54
+ if (observerTarget.current) {
55
+ observer.observe(observerTarget.current);
56
+ }
57
+
58
+ return () => observer.disconnect();
59
+ }, [pagination, onPageChange, currentPage, totalPages]);
34
60
 
35
61
  return (
36
- <div className="flex flex-col w-full border border-base-300 rounded-xl overflow-hidden bg-base-100 shadow">
37
- <div className="overflow-x-auto w-full">
38
- <table className="table table-hover w-full min-w-full">
39
- <thead className="bg-base-200/60 border-b border-base-300">
62
+ <div
63
+ className={`flex flex-col w-full h-full md:border md:border-base-300 rounded-xl overflow-hidden bg-base-100 ${wrapperClassName}`}
64
+ >
65
+ {/* Optional Custom Header Area */}
66
+ {header && (
67
+ <div className="shrink-0 bg-base-100 border-b border-base-300">
68
+ {header}
69
+ </div>
70
+ )}
71
+
72
+ {/* Desktop Table View (hidden on mobile if renderCard provided) */}
73
+ <div
74
+ className={`overflow-auto w-full flex-1 ${renderCard ? "hidden md:block" : "block"}`}
75
+ >
76
+ <table className="table table-hover w-full min-w-full relative border-collapse">
77
+ <thead className="bg-base-200 backdrop-blur sticky top-0 z-10 border-b border-base-300">
40
78
  <tr>
41
79
  {columns.map((col, index) => (
42
80
  <th
43
81
  key={(col.accessor as string) || index}
44
- className="px-6 py-4 text-left text-xs font-bold text-base-content/80 uppercase tracking-wider"
82
+ className={`px-6 py-4 text-left text-xs font-semibold text-base-content/70 uppercase tracking-wider bg-transparent border-none ${col.headerClass || ""}`}
45
83
  >
46
84
  {col.header}
47
85
  </th>
48
86
  ))}
49
87
  </tr>
50
88
  </thead>
51
- <tbody className="divide-y divide-base-300 bg-base-100">
52
- {data.length > 0 ? (
89
+ <tbody className="divide-y divide-base-200/60 bg-base-100">
90
+ {loading ? (
91
+ Array.from({ length: 5 }).map((_, rowIndex) => (
92
+ <tr key={`skeleton-${rowIndex}`} className="animate-pulse">
93
+ {columns.map((col, colIndex) => {
94
+ const isRight = col.headerClass?.includes("text-right");
95
+ const isFirst = colIndex === 0;
96
+ const isLast = colIndex === columns.length - 1;
97
+ const headerText =
98
+ typeof col.header === "string"
99
+ ? col.header.toLowerCase()
100
+ : "";
101
+
102
+ return (
103
+ <td
104
+ key={colIndex}
105
+ className={`px-6 py-4 whitespace-nowrap align-middle border-none ${col.headerClass || ""}`}
106
+ >
107
+ {isRight ||
108
+ (isLast && headerText.includes("action")) ? (
109
+ <div className="flex justify-end items-center gap-2">
110
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
111
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
112
+ </div>
113
+ ) : headerText.includes("assigned") ? (
114
+ <div className="h-6 w-28 rounded-md bg-base-200 border border-base-300/80"></div>
115
+ ) : isFirst ? (
116
+ <div className="space-y-1.5">
117
+ <div
118
+ className="h-4 bg-base-300/80 rounded"
119
+ style={{
120
+ width: `${65 + (rowIndex % 3) * 15}%`,
121
+ maxWidth: "140px",
122
+ }}
123
+ ></div>
124
+ <div
125
+ className="h-3 bg-base-200 rounded"
126
+ style={{
127
+ width: `${40 + (rowIndex % 2) * 15}%`,
128
+ maxWidth: "90px",
129
+ }}
130
+ ></div>
131
+ </div>
132
+ ) : (
133
+ <div
134
+ className={`h-4 bg-base-300/70 rounded ${isRight ? "ml-auto" : ""}`}
135
+ style={{
136
+ width: `${50 + ((rowIndex + colIndex) % 4) * 12}%`,
137
+ maxWidth: "160px",
138
+ }}
139
+ ></div>
140
+ )}
141
+ </td>
142
+ );
143
+ })}
144
+ </tr>
145
+ ))
146
+ ) : data.length > 0 ? (
53
147
  data.map((row, rowIndex) => (
54
148
  <tr
55
149
  key={row.id || rowIndex}
56
150
  onClick={() => onRowClick && onRowClick(row)}
57
- className={`transition-colors duration-150 ${
58
- onRowClick ? "cursor-pointer hover:bg-base-200" : ""
151
+ className={`transition-all duration-200 group ${
152
+ onRowClick
153
+ ? "cursor-pointer hover:bg-base-200/30"
154
+ : "hover:bg-base-200/10"
59
155
  }`}
60
156
  >
61
157
  {columns.map((col, colIndex) => {
@@ -65,7 +161,7 @@ const Table = <T extends Record<string, any>>({
65
161
  return (
66
162
  <td
67
163
  key={(col.accessor as string) || colIndex}
68
- className="px-6 py-4 text-sm text-base-content/90 whitespace-nowrap align-middle"
164
+ className="px-6 py-4 text-[13px] text-base-content/80 whitespace-nowrap align-middle border-none"
69
165
  >
70
166
  {col.render ? col.render(value, row, rowIndex) : value}
71
167
  </td>
@@ -89,28 +185,95 @@ const Table = <T extends Record<string, any>>({
89
185
  </tbody>
90
186
  </table>
91
187
  </div>
92
- {totalPages > 1 && onPageChange && (
93
- <div className="border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center justify-between shrink-0">
188
+
189
+ {/* Mobile Card View (only visible if renderCard provided) */}
190
+ {renderCard && (
191
+ <div className="md:hidden flex flex-col divide-y divide-base-200 overflow-y-auto flex-1">
192
+ {loading ? (
193
+ Array.from({ length: 4 }).map((_, i) => (
194
+ <div
195
+ key={`card-skeleton-${i}`}
196
+ className="p-4 flex flex-col gap-3 animate-pulse"
197
+ >
198
+ <div className="flex justify-between items-start">
199
+ <div className="space-y-1.5 flex-1">
200
+ <div className="h-4.5 bg-base-300/80 rounded w-36"></div>
201
+ <div className="h-3 bg-base-200 rounded w-24"></div>
202
+ </div>
203
+ <div className="flex gap-2">
204
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
205
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
206
+ </div>
207
+ </div>
208
+ <div className="space-y-2">
209
+ <div className="h-3.5 bg-base-200 rounded w-48"></div>
210
+ <div className="h-3.5 bg-base-200 rounded w-32"></div>
211
+ </div>
212
+ <div className="flex items-center justify-between pt-2 border-t border-base-200">
213
+ <div className="h-3 bg-base-200 rounded w-20"></div>
214
+ <div className="h-6 bg-base-300/80 rounded-lg w-28"></div>
215
+ </div>
216
+ </div>
217
+ ))
218
+ ) : data.length > 0 ? (
219
+ <>
220
+ {data.map((row, index) => (
221
+ <div
222
+ key={row.id || index}
223
+ onClick={() => onRowClick && onRowClick(row)}
224
+ className={onRowClick ? "cursor-pointer" : ""}
225
+ >
226
+ {renderCard(row, index)}
227
+ </div>
228
+ ))}
229
+ {pagination && currentPage < totalPages && (
230
+ <div ref={observerTarget} className="p-6 flex justify-center">
231
+ <span className="loading loading-spinner loading-sm text-primary"></span>
232
+ </div>
233
+ )}
234
+ </>
235
+ ) : (
236
+ <div className="p-12 text-center text-sm text-base-content/60 flex flex-col items-center gap-2">
237
+ <span className="text-2xl">📁</span>
238
+ <span>{emptyMessage}</span>
239
+ </div>
240
+ )}
241
+ </div>
242
+ )}
243
+
244
+ {/* Desktop Pagination / Load More Footer */}
245
+ {pagination && onPageChange && totalPages > 1 && (
246
+ <div
247
+ 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"}`}
248
+ >
94
249
  <div className="text-xs text-base-content/75 font-medium">
95
- Page <span className="font-bold">{currentPage}</span> of{" "}
96
- <span className="font-bold">{totalPages}</span>
250
+ Showing <span className="font-bold">{data.length}</span> items
97
251
  </div>
98
252
  <div className="flex gap-2">
99
- <button
100
- onClick={() => onPageChange(currentPage - 1)}
101
- disabled={currentPage <= 1}
102
- className="btn btn-sm btn-outline rounded-lg text-xs"
103
- >
104
- Previous
105
- </button>
106
- <button
107
- onClick={() => onPageChange(currentPage + 1)}
108
- disabled={currentPage >= totalPages}
109
- className="btn btn-sm btn-outline rounded-lg text-xs"
110
- >
111
- Next
112
- </button>
253
+ {currentPage < totalPages && (
254
+ <button
255
+ onClick={() => onPageChange(currentPage + 1)}
256
+ className="btn btn-sm btn-outline btn-primary rounded-lg text-xs"
257
+ >
258
+ Load More
259
+ </button>
260
+ )}
261
+ </div>
262
+ </div>
263
+ )}
264
+
265
+ {/* Standard Pagination (if pagination prop is not true) */}
266
+ {!pagination && onPageChange && totalPages > 0 && (
267
+ <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">
268
+ <div className="text-xs text-base-content/75 font-medium whitespace-nowrap">
269
+ Page <span className="font-bold">{currentPage}</span> of{" "}
270
+ <span className="font-bold">{totalPages}</span>
113
271
  </div>
272
+ <Pagination
273
+ currentPage={currentPage}
274
+ totalPages={totalPages}
275
+ onPageChange={onPageChange}
276
+ />
114
277
  </div>
115
278
  )}
116
279
  </div>
@@ -15,18 +15,49 @@ interface TabsProps {
15
15
  variant?: "underline" | "box";
16
16
  className?: string;
17
17
  dropdown?: boolean;
18
+ disabled?: boolean;
19
+ size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
18
20
  }
19
21
 
20
22
  export const Tabs = ({
21
23
  tabs,
22
24
  activeTab,
23
25
  onChange,
24
- variant = "underline",
25
- className = "",
26
+ variant = "box",
27
+ className = "bg-base-200 w-fit",
26
28
  dropdown = false,
29
+ disabled = false,
30
+ size = "base",
27
31
  }: TabsProps) => {
28
32
  const [isOpen, setIsOpen] = React.useState(false);
29
33
 
34
+ const getTabSizeClass = () => {
35
+ switch (size) {
36
+ case "xs": return "text-xs px-2.5 h-7 min-h-7";
37
+ case "sm": return "text-sm px-3 h-8 min-h-8";
38
+ case "base": return "text-base px-4 h-10 min-h-10";
39
+ case "lg": return "text-lg px-6 h-12 min-h-12";
40
+ case "xl": return "text-xl px-6 h-14 min-h-14";
41
+ case "2xl": return "text-2xl px-8 h-16 min-h-16";
42
+ default: return "text-base px-4 h-10 min-h-10";
43
+ }
44
+ };
45
+
46
+ const getBtnSizeClass = () => {
47
+ switch (size) {
48
+ case "xs": return "btn-xs text-xs";
49
+ case "sm": return "btn-sm text-sm";
50
+ case "base": return "text-base";
51
+ case "lg": return "btn-lg text-lg";
52
+ case "xl": return "btn-lg text-xl";
53
+ case "2xl": return "btn-lg text-2xl";
54
+ default: return "text-base";
55
+ }
56
+ };
57
+
58
+ const tabSizeClass = getTabSizeClass();
59
+ const btnSizeClass = getBtnSizeClass();
60
+
30
61
  // Close dropdown if user clicks outside
31
62
  React.useEffect(() => {
32
63
  if (!dropdown || !isOpen) return;
@@ -39,17 +70,25 @@ export const Tabs = ({
39
70
  if (!dropdown) return null;
40
71
  const activeItem = tabs.find((t) => t.id === activeTab);
41
72
  return (
42
- <div className="relative md:hidden w-full max-w-xs z-[50]" onClick={(e) => e.stopPropagation()}>
73
+ <div
74
+ className="relative md:hidden w-full max-w-xs z-[50]"
75
+ onClick={(e) => e.stopPropagation()}
76
+ >
43
77
  {/* Trigger Button */}
44
78
  <button
79
+ type="button"
45
80
  onClick={() => setIsOpen(!isOpen)}
46
- className="btn btn-sm bg-base-200 border-base-300 border flex w-full items-center justify-between px-3 text-base-content "
81
+ disabled={disabled}
82
+ 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" : ""}`}
47
83
  >
48
84
  <div className="flex items-center gap-1.5">
49
85
  {activeItem?.icon}
50
86
  <span className="font-semibold">{activeItem?.label}</span>
51
87
  </div>
52
- <ChevronDown size={14} className={`opacity-50 transition-transform ${isOpen ? "rotate-180" : ""}`} />
88
+ <ChevronDown
89
+ size={14}
90
+ className={`opacity-50 transition-transform ${isOpen ? "rotate-180" : ""}`}
91
+ />
53
92
  </button>
54
93
 
55
94
  {/* Dropdown Menu */}
@@ -62,7 +101,8 @@ export const Tabs = ({
62
101
  onChange(tab.id);
63
102
  setIsOpen(false);
64
103
  }}
65
- className={`w-full flex items-center gap-2 px-3 py-2 text-sm transition-colors ${
104
+ type="button"
105
+ className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${size === "base" ? "text-sm" : `text-${size}`} ${
66
106
  activeTab === tab.id
67
107
  ? "bg-primary/10 text-primary font-semibold"
68
108
  : "hover:bg-base-200 text-base-content/80"
@@ -83,17 +123,19 @@ export const Tabs = ({
83
123
  <>
84
124
  {renderDropdown()}
85
125
  <div
86
- className={`tabs tabs-boxed bg-base-200 p-1 flex-nowrap overflow-x-auto hide-scrollbar shrink-0 rounded-lg ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
126
+ className={`tabs tabs-boxed p-1 flex-nowrap overflow-x-auto hide-scrollbar shrink-0 rounded-lg ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
87
127
  >
88
128
  {tabs.map((tab) => (
89
129
  <button
130
+ type="button"
90
131
  key={tab.id}
91
132
  onClick={() => onChange(tab.id)}
92
- className={`tab whitespace-nowrap flex items-center rounded-lg gap-2 relative z-0 ${
133
+ disabled={disabled}
134
+ className={`tab ${tabSizeClass} whitespace-nowrap flex items-center rounded-lg gap-2 relative z-0 ${
93
135
  activeTab === tab.id
94
136
  ? "text-primary-content"
95
- : "text-base-content/70 hover:text-base-content"
96
- }`}
137
+ : "text-base-content/70 hover:text-base-content "
138
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
97
139
  >
98
140
  {activeTab === tab.id && (
99
141
  <motion.div
@@ -120,13 +162,15 @@ export const Tabs = ({
120
162
  >
121
163
  {tabs.map((tab) => (
122
164
  <button
165
+ type="button"
123
166
  key={tab.id}
124
167
  onClick={() => onChange(tab.id)}
125
- className={`tab whitespace-nowrap relative !border-transparent ${
168
+ disabled={disabled}
169
+ className={`tab ${tabSizeClass} whitespace-nowrap relative !border-transparent ${
126
170
  activeTab === tab.id
127
171
  ? "font-semibold text-primary"
128
172
  : "text-base-content/70 hover:text-base-content"
129
- }`}
173
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
130
174
  >
131
175
  {activeTab === tab.id && (
132
176
  <motion.div
@@ -1,10 +1,22 @@
1
1
  import React from "react";
2
2
  import { motion } from "motion/react";
3
3
 
4
- const ToggleSwitch = ({ checked, onChange, label }) => {
4
+ interface ToggleSwitchProps {
5
+ checked: boolean;
6
+ onChange: (checked: boolean) => void;
7
+ label?: React.ReactNode;
8
+ className?:string
9
+ }
10
+
11
+ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
12
+ checked,
13
+ onChange,
14
+ label,
15
+ className
16
+ }) => {
5
17
  return (
6
18
  <div
7
- className="flex items-center gap-3 cursor-pointer select-none w-fit group"
19
+ className={`flex items-center gap-3 cursor-pointer select-none w-fit group ${className}`}
8
20
  onClick={() => onChange(!checked)}
9
21
  >
10
22
  <span className="text-sm font-bold">{label}</span>
@@ -16,7 +28,7 @@ const ToggleSwitch = ({ checked, onChange, label }) => {
16
28
  }`}
17
29
  >
18
30
  <motion.div
19
- className={`w-4 h-4 rounded-full shadow-sm ${
31
+ className={`w-4 h-4 rounded-full shadow ${
20
32
  checked ? "bg-white" : "bg-base-content/40"
21
33
  }`}
22
34
  layout
@@ -10,11 +10,8 @@ const SidebarLayout = () => {
10
10
  useUiStore();
11
11
  const { pathname } = useLocation();
12
12
 
13
- // Reset header title and actions whenever the route shifts
14
- useEffect(() => {
15
- setHeaderTitle(appConfig.app.name);
16
- setHeaderActions(null);
17
- }, [pathname, setHeaderTitle, setHeaderActions]);
13
+ // Layout no longer resets title on every route change.
14
+ // Individual pages are responsible for setting their own title via useUiStore.
18
15
 
19
16
  return (
20
17
  <div className="flex h-screen overflow-hidden">
@@ -24,7 +21,7 @@ const SidebarLayout = () => {
24
21
  <SidebarHeader title={headerTitle} actions={headerActions} />
25
22
 
26
23
  <main
27
- className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
24
+ className="flex-1 h-full overflow-y-auto "
28
25
  data-lenis-prevent
29
26
  >
30
27
  <Outlet />
@@ -0,0 +1,151 @@
1
+ import { useEffect, ReactNode } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { IoClose } from "react-icons/io5";
4
+ import { useUiStore } from "../../store/useUiStore";
5
+
6
+ export interface ModalProps {
7
+ open: boolean;
8
+ onClose: () => void;
9
+ title?: string;
10
+ children: ReactNode;
11
+ size?:
12
+ | "sm"
13
+ | "md"
14
+ | "lg"
15
+ | "xl"
16
+ | "2xl"
17
+ | "3xl"
18
+ | "4xl"
19
+ | "5xl"
20
+ | "6xl"
21
+ | "7xl"
22
+ | "full";
23
+ closeOnBackdrop?: boolean;
24
+ closeOnEsc?: boolean;
25
+ showCloseButton?: boolean;
26
+ hideHeader?: boolean;
27
+ className?: string;
28
+ bodyClass?: string;
29
+ footer?: ReactNode;
30
+ height?: string | number;
31
+ }
32
+
33
+ const sizes = {
34
+ sm: "max-w-sm",
35
+ md: "max-w-md",
36
+ lg: "max-w-lg",
37
+ xl: "max-w-xl",
38
+ "2xl": "max-w-2xl",
39
+ "3xl": "max-w-3xl",
40
+ "4xl": "max-w-4xl",
41
+ "5xl": "max-w-5xl",
42
+ "6xl": "max-w-6xl",
43
+ "7xl": "max-w-7xl",
44
+ full: "max-w-7xl",
45
+ };
46
+
47
+ const Modal = ({
48
+ open,
49
+ onClose,
50
+ title,
51
+ children,
52
+ size = "4xl",
53
+ closeOnBackdrop = true,
54
+ closeOnEsc = true,
55
+ showCloseButton = true,
56
+ hideHeader = false,
57
+ className = "",
58
+ bodyClass = "p-2 sm:p-4",
59
+ footer,
60
+ height,
61
+ }: ModalProps) => {
62
+ const setModalOpen = useUiStore((state) => state.setModalOpen);
63
+
64
+ useEffect(() => {
65
+ if (open) {
66
+ setModalOpen(true);
67
+ return () => {
68
+ setModalOpen(false);
69
+ };
70
+ }
71
+ }, [open, setModalOpen]);
72
+
73
+ useEffect(() => {
74
+ if (!open || !closeOnEsc) return;
75
+
76
+ const handleKeyDown = (e: KeyboardEvent) => {
77
+ if (e.key === "Escape") {
78
+ onClose?.();
79
+ }
80
+ };
81
+
82
+ document.addEventListener("keydown", handleKeyDown);
83
+
84
+ return () => {
85
+ document.removeEventListener("keydown", handleKeyDown);
86
+ };
87
+ }, [open, closeOnEsc, onClose]);
88
+
89
+ if (!open) return null;
90
+
91
+ return createPortal(
92
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
93
+ {/* Backdrop */}
94
+ <div
95
+ className="absolute inset-0 bg-black/50 backdrop-blur-sm"
96
+ onClick={closeOnBackdrop ? onClose : undefined}
97
+ />
98
+
99
+ {/* Modal */}
100
+ <div
101
+ className={`
102
+ relative
103
+ flex flex-col overflow-hidden max-h-[90vh]
104
+ w-full
105
+ ${sizes[size]}
106
+ rounded-2xl
107
+ bg-base-100
108
+ shadow-md
109
+ border border-base-300
110
+ animate-in zoom-in-95 fade-in duration-200
111
+ ${className}
112
+ `}
113
+ style={height ? { height } : undefined}
114
+ >
115
+ {/* Header */}
116
+ {!hideHeader && (title || showCloseButton) && (
117
+ <div className="flex items-center justify-between border-b border-base-300 px-6 py-4 shrink-0">
118
+ {title && <h2 className="text-lg font-bold">{title}</h2>}
119
+
120
+ {showCloseButton && (
121
+ <button
122
+ className="btn btn-circle btn-sm btn-error btn-soft text-error/70 hover:text-error hover:bg-error/20 hover:shadow-sm hover:rotate-180 transition-transform duration-200"
123
+ onClick={onClose}
124
+ >
125
+ <IoClose size={20} className="" />
126
+ </button>
127
+ )}
128
+ </div>
129
+ )}
130
+
131
+ {/* Body */}
132
+ <div
133
+ className={`flex-1 min-h-0 overflow-y-auto ${bodyClass}`}
134
+ data-lenis-prevent="true"
135
+ >
136
+ {children}
137
+ </div>
138
+
139
+ {/* Footer */}
140
+ {footer && (
141
+ <div className="border-t border-base-300 px-6 py-4 shrink-0">
142
+ {footer}
143
+ </div>
144
+ )}
145
+ </div>
146
+ </div>,
147
+ document.body,
148
+ );
149
+ };
150
+
151
+ export default Modal;