@marktiderman/genesis-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/dist/ViewToggle-DDe_Luj2.d.ts +10 -0
  2. package/dist/chunk-2B3MFHBQ.js +94 -0
  3. package/dist/chunk-2B3MFHBQ.js.map +1 -0
  4. package/dist/chunk-2MQKBZBJ.js +87 -0
  5. package/dist/chunk-2MQKBZBJ.js.map +1 -0
  6. package/dist/chunk-2MXMWBDV.js +30 -0
  7. package/dist/chunk-2MXMWBDV.js.map +1 -0
  8. package/dist/chunk-2SBHHXAC.js +40 -0
  9. package/dist/chunk-2SBHHXAC.js.map +1 -0
  10. package/dist/chunk-4HUKJSKC.js +51 -0
  11. package/dist/chunk-4HUKJSKC.js.map +1 -0
  12. package/dist/chunk-4RAZJ6LL.js +27 -0
  13. package/dist/chunk-4RAZJ6LL.js.map +1 -0
  14. package/dist/chunk-4TROUCWO.js +43 -0
  15. package/dist/chunk-4TROUCWO.js.map +1 -0
  16. package/dist/chunk-4V4H2ZM7.js +170 -0
  17. package/dist/chunk-4V4H2ZM7.js.map +1 -0
  18. package/dist/chunk-5J7BP5DI.js +47 -0
  19. package/dist/chunk-5J7BP5DI.js.map +1 -0
  20. package/dist/chunk-5Q4FABZQ.js +44 -0
  21. package/dist/chunk-5Q4FABZQ.js.map +1 -0
  22. package/dist/chunk-63FW5ZUA.js +26 -0
  23. package/dist/chunk-63FW5ZUA.js.map +1 -0
  24. package/dist/chunk-6V4GJUE3.js +25 -0
  25. package/dist/chunk-6V4GJUE3.js.map +1 -0
  26. package/dist/chunk-7H3E5VOJ.js +205 -0
  27. package/dist/chunk-7H3E5VOJ.js.map +1 -0
  28. package/dist/chunk-7OBL3H5P.js +83 -0
  29. package/dist/chunk-7OBL3H5P.js.map +1 -0
  30. package/dist/chunk-A5WHNNXJ.js +22 -0
  31. package/dist/chunk-A5WHNNXJ.js.map +1 -0
  32. package/dist/chunk-AI6CCNWK.js +51 -0
  33. package/dist/chunk-AI6CCNWK.js.map +1 -0
  34. package/dist/chunk-AIODTBWP.js +23 -0
  35. package/dist/chunk-AIODTBWP.js.map +1 -0
  36. package/dist/chunk-C6H7AMO4.js +43 -0
  37. package/dist/chunk-C6H7AMO4.js.map +1 -0
  38. package/dist/chunk-CLK7JBZJ.js +288 -0
  39. package/dist/chunk-CLK7JBZJ.js.map +1 -0
  40. package/dist/chunk-EI5EGKHL.js +32 -0
  41. package/dist/chunk-EI5EGKHL.js.map +1 -0
  42. package/dist/chunk-F2JB7YFF.js +341 -0
  43. package/dist/chunk-F2JB7YFF.js.map +1 -0
  44. package/dist/chunk-FCGFG4RV.js +120 -0
  45. package/dist/chunk-FCGFG4RV.js.map +1 -0
  46. package/dist/chunk-FFSDFQQN.js +19 -0
  47. package/dist/chunk-FFSDFQQN.js.map +1 -0
  48. package/dist/chunk-FN3FZVUR.js +79 -0
  49. package/dist/chunk-FN3FZVUR.js.map +1 -0
  50. package/dist/chunk-FZYUVUXU.js +199 -0
  51. package/dist/chunk-FZYUVUXU.js.map +1 -0
  52. package/dist/chunk-GJ6XSCNF.js +64 -0
  53. package/dist/chunk-GJ6XSCNF.js.map +1 -0
  54. package/dist/chunk-HSHIZHYR.js +23 -0
  55. package/dist/chunk-HSHIZHYR.js.map +1 -0
  56. package/dist/chunk-HVQCZFJ7.js +115 -0
  57. package/dist/chunk-HVQCZFJ7.js.map +1 -0
  58. package/dist/chunk-IKIFQILN.js +124 -0
  59. package/dist/chunk-IKIFQILN.js.map +1 -0
  60. package/dist/chunk-JEECTX26.js +20 -0
  61. package/dist/chunk-JEECTX26.js.map +1 -0
  62. package/dist/chunk-JJ4NOMMI.js +73 -0
  63. package/dist/chunk-JJ4NOMMI.js.map +1 -0
  64. package/dist/chunk-JXI2MYL3.js +40 -0
  65. package/dist/chunk-JXI2MYL3.js.map +1 -0
  66. package/dist/chunk-JYPENH7T.js +95 -0
  67. package/dist/chunk-JYPENH7T.js.map +1 -0
  68. package/dist/chunk-K23AQNOM.js +51 -0
  69. package/dist/chunk-K23AQNOM.js.map +1 -0
  70. package/dist/chunk-K4YLKVMX.js +24 -0
  71. package/dist/chunk-K4YLKVMX.js.map +1 -0
  72. package/dist/chunk-KBS4BHME.js +11 -0
  73. package/dist/chunk-KBS4BHME.js.map +1 -0
  74. package/dist/chunk-LRXDZBV2.js +26 -0
  75. package/dist/chunk-LRXDZBV2.js.map +1 -0
  76. package/dist/chunk-M7AU5DDM.js +26 -0
  77. package/dist/chunk-M7AU5DDM.js.map +1 -0
  78. package/dist/chunk-MUQLZFUR.js +31 -0
  79. package/dist/chunk-MUQLZFUR.js.map +1 -0
  80. package/dist/chunk-N2ZFNOOT.js +57 -0
  81. package/dist/chunk-N2ZFNOOT.js.map +1 -0
  82. package/dist/chunk-NJMO3VPA.js +26 -0
  83. package/dist/chunk-NJMO3VPA.js.map +1 -0
  84. package/dist/chunk-NZ5WBA4R.js +18 -0
  85. package/dist/chunk-NZ5WBA4R.js.map +1 -0
  86. package/dist/chunk-OCTV3S4I.js +29 -0
  87. package/dist/chunk-OCTV3S4I.js.map +1 -0
  88. package/dist/chunk-OVU3Z2MT.js +22 -0
  89. package/dist/chunk-OVU3Z2MT.js.map +1 -0
  90. package/dist/chunk-OZ7ZAYCS.js +35 -0
  91. package/dist/chunk-OZ7ZAYCS.js.map +1 -0
  92. package/dist/chunk-OZZTPE7F.js +51 -0
  93. package/dist/chunk-OZZTPE7F.js.map +1 -0
  94. package/dist/chunk-QBZMY2AF.js +90 -0
  95. package/dist/chunk-QBZMY2AF.js.map +1 -0
  96. package/dist/chunk-QCJAASJT.js +242 -0
  97. package/dist/chunk-QCJAASJT.js.map +1 -0
  98. package/dist/chunk-QFM4Z2SS.js +19 -0
  99. package/dist/chunk-QFM4Z2SS.js.map +1 -0
  100. package/dist/chunk-SBJV3K4G.js +120 -0
  101. package/dist/chunk-SBJV3K4G.js.map +1 -0
  102. package/dist/chunk-SCUT2ZCD.js +109 -0
  103. package/dist/chunk-SCUT2ZCD.js.map +1 -0
  104. package/dist/chunk-SVXDWPLO.js +102 -0
  105. package/dist/chunk-SVXDWPLO.js.map +1 -0
  106. package/dist/chunk-SXSCHWZU.js +27 -0
  107. package/dist/chunk-SXSCHWZU.js.map +1 -0
  108. package/dist/chunk-T465NJRL.js +134 -0
  109. package/dist/chunk-T465NJRL.js.map +1 -0
  110. package/dist/chunk-TAP3PBQT.js +39 -0
  111. package/dist/chunk-TAP3PBQT.js.map +1 -0
  112. package/dist/chunk-TKA4LOMT.js +37 -0
  113. package/dist/chunk-TKA4LOMT.js.map +1 -0
  114. package/dist/chunk-UGHQFQOE.js +142 -0
  115. package/dist/chunk-UGHQFQOE.js.map +1 -0
  116. package/dist/chunk-W5Y3LG5K.js +26 -0
  117. package/dist/chunk-W5Y3LG5K.js.map +1 -0
  118. package/dist/chunk-W64GERJW.js +22 -0
  119. package/dist/chunk-W64GERJW.js.map +1 -0
  120. package/dist/chunk-WALXVR6V.js +9 -0
  121. package/dist/chunk-WALXVR6V.js.map +1 -0
  122. package/dist/chunk-WZWR2ELB.js +30 -0
  123. package/dist/chunk-WZWR2ELB.js.map +1 -0
  124. package/dist/chunk-X7KMF6NH.js +59 -0
  125. package/dist/chunk-X7KMF6NH.js.map +1 -0
  126. package/dist/chunk-XISSLIMH.js +20 -0
  127. package/dist/chunk-XISSLIMH.js.map +1 -0
  128. package/dist/chunk-YZEA5TGK.js +87 -0
  129. package/dist/chunk-YZEA5TGK.js.map +1 -0
  130. package/dist/chunk-ZD32TSOK.js +101 -0
  131. package/dist/chunk-ZD32TSOK.js.map +1 -0
  132. package/dist/chunk-ZDAT5JDH.js +82 -0
  133. package/dist/chunk-ZDAT5JDH.js.map +1 -0
  134. package/dist/components/data/index.d.ts +458 -0
  135. package/dist/components/data/index.js +2923 -0
  136. package/dist/components/data/index.js.map +1 -0
  137. package/dist/components/ui/accordion.d.ts +9 -0
  138. package/dist/components/ui/accordion.js +4 -0
  139. package/dist/components/ui/accordion.js.map +1 -0
  140. package/dist/components/ui/alert-dialog.d.ts +17 -0
  141. package/dist/components/ui/alert-dialog.js +4 -0
  142. package/dist/components/ui/alert-dialog.js.map +1 -0
  143. package/dist/components/ui/alert.d.ts +14 -0
  144. package/dist/components/ui/alert.js +4 -0
  145. package/dist/components/ui/alert.js.map +1 -0
  146. package/dist/components/ui/aspect-ratio.d.ts +6 -0
  147. package/dist/components/ui/aspect-ratio.js +3 -0
  148. package/dist/components/ui/aspect-ratio.js.map +1 -0
  149. package/dist/components/ui/avatar.d.ts +8 -0
  150. package/dist/components/ui/avatar.js +4 -0
  151. package/dist/components/ui/avatar.js.map +1 -0
  152. package/dist/components/ui/badge.d.ts +13 -0
  153. package/dist/components/ui/badge.js +4 -0
  154. package/dist/components/ui/badge.js.map +1 -0
  155. package/dist/components/ui/breadcrumb.d.ts +22 -0
  156. package/dist/components/ui/breadcrumb.js +4 -0
  157. package/dist/components/ui/breadcrumb.js.map +1 -0
  158. package/dist/components/ui/button-group.d.ts +9 -0
  159. package/dist/components/ui/button-group.js +4 -0
  160. package/dist/components/ui/button-group.js.map +1 -0
  161. package/dist/components/ui/button.d.ts +15 -0
  162. package/dist/components/ui/button.js +4 -0
  163. package/dist/components/ui/button.js.map +1 -0
  164. package/dist/components/ui/calendar.d.ts +13 -0
  165. package/dist/components/ui/calendar.js +5 -0
  166. package/dist/components/ui/calendar.js.map +1 -0
  167. package/dist/components/ui/card.d.ts +11 -0
  168. package/dist/components/ui/card.js +4 -0
  169. package/dist/components/ui/card.js.map +1 -0
  170. package/dist/components/ui/carousel.d.ts +24 -0
  171. package/dist/components/ui/carousel.js +5 -0
  172. package/dist/components/ui/carousel.js.map +1 -0
  173. package/dist/components/ui/chart.d.ts +51 -0
  174. package/dist/components/ui/chart.js +4 -0
  175. package/dist/components/ui/chart.js.map +1 -0
  176. package/dist/components/ui/checkbox.d.ts +6 -0
  177. package/dist/components/ui/checkbox.js +4 -0
  178. package/dist/components/ui/checkbox.js.map +1 -0
  179. package/dist/components/ui/collapsible.d.ts +8 -0
  180. package/dist/components/ui/collapsible.js +3 -0
  181. package/dist/components/ui/collapsible.js.map +1 -0
  182. package/dist/components/ui/command.d.ts +85 -0
  183. package/dist/components/ui/command.js +5 -0
  184. package/dist/components/ui/command.js.map +1 -0
  185. package/dist/components/ui/context-menu.d.ts +30 -0
  186. package/dist/components/ui/context-menu.js +4 -0
  187. package/dist/components/ui/context-menu.js.map +1 -0
  188. package/dist/components/ui/date-picker.d.ts +12 -0
  189. package/dist/components/ui/date-picker.js +7 -0
  190. package/dist/components/ui/date-picker.js.map +1 -0
  191. package/dist/components/ui/dialog.d.ts +18 -0
  192. package/dist/components/ui/dialog.js +4 -0
  193. package/dist/components/ui/dialog.js.map +1 -0
  194. package/dist/components/ui/drawer.d.ts +27 -0
  195. package/dist/components/ui/drawer.js +4 -0
  196. package/dist/components/ui/drawer.js.map +1 -0
  197. package/dist/components/ui/dropdown-menu.d.ts +30 -0
  198. package/dist/components/ui/dropdown-menu.js +4 -0
  199. package/dist/components/ui/dropdown-menu.js.map +1 -0
  200. package/dist/components/ui/empty-state.d.ts +22 -0
  201. package/dist/components/ui/empty-state.js +4 -0
  202. package/dist/components/ui/empty-state.js.map +1 -0
  203. package/dist/components/ui/form-field.d.ts +17 -0
  204. package/dist/components/ui/form-field.js +5 -0
  205. package/dist/components/ui/form-field.js.map +1 -0
  206. package/dist/components/ui/hover-card.d.ts +8 -0
  207. package/dist/components/ui/hover-card.js +4 -0
  208. package/dist/components/ui/hover-card.js.map +1 -0
  209. package/dist/components/ui/input-group.d.ts +14 -0
  210. package/dist/components/ui/input-group.js +4 -0
  211. package/dist/components/ui/input-group.js.map +1 -0
  212. package/dist/components/ui/input-otp.d.ts +37 -0
  213. package/dist/components/ui/input-otp.js +4 -0
  214. package/dist/components/ui/input-otp.js.map +1 -0
  215. package/dist/components/ui/input.d.ts +5 -0
  216. package/dist/components/ui/input.js +4 -0
  217. package/dist/components/ui/input.js.map +1 -0
  218. package/dist/components/ui/kbd.d.ts +8 -0
  219. package/dist/components/ui/kbd.js +4 -0
  220. package/dist/components/ui/kbd.js.map +1 -0
  221. package/dist/components/ui/label.d.ts +8 -0
  222. package/dist/components/ui/label.js +4 -0
  223. package/dist/components/ui/label.js.map +1 -0
  224. package/dist/components/ui/menubar.d.ts +31 -0
  225. package/dist/components/ui/menubar.js +4 -0
  226. package/dist/components/ui/menubar.js.map +1 -0
  227. package/dist/components/ui/navigation-menu.d.ts +15 -0
  228. package/dist/components/ui/navigation-menu.js +4 -0
  229. package/dist/components/ui/navigation-menu.js.map +1 -0
  230. package/dist/components/ui/pagination.d.ts +33 -0
  231. package/dist/components/ui/pagination.js +5 -0
  232. package/dist/components/ui/pagination.js.map +1 -0
  233. package/dist/components/ui/popover.d.ts +8 -0
  234. package/dist/components/ui/popover.js +4 -0
  235. package/dist/components/ui/popover.js.map +1 -0
  236. package/dist/components/ui/progress.d.ts +9 -0
  237. package/dist/components/ui/progress.js +4 -0
  238. package/dist/components/ui/progress.js.map +1 -0
  239. package/dist/components/ui/radio-group.d.ts +7 -0
  240. package/dist/components/ui/radio-group.js +4 -0
  241. package/dist/components/ui/radio-group.js.map +1 -0
  242. package/dist/components/ui/resizable.d.ts +27 -0
  243. package/dist/components/ui/resizable.js +4 -0
  244. package/dist/components/ui/resizable.js.map +1 -0
  245. package/dist/components/ui/scroll-area.d.ts +9 -0
  246. package/dist/components/ui/scroll-area.js +4 -0
  247. package/dist/components/ui/scroll-area.js.map +1 -0
  248. package/dist/components/ui/select.d.ts +13 -0
  249. package/dist/components/ui/select.js +4 -0
  250. package/dist/components/ui/select.js.map +1 -0
  251. package/dist/components/ui/separator.d.ts +10 -0
  252. package/dist/components/ui/separator.js +4 -0
  253. package/dist/components/ui/separator.js.map +1 -0
  254. package/dist/components/ui/sheet.d.ts +29 -0
  255. package/dist/components/ui/sheet.js +4 -0
  256. package/dist/components/ui/sheet.js.map +1 -0
  257. package/dist/components/ui/skeleton.d.ts +6 -0
  258. package/dist/components/ui/skeleton.js +4 -0
  259. package/dist/components/ui/skeleton.js.map +1 -0
  260. package/dist/components/ui/slider.d.ts +6 -0
  261. package/dist/components/ui/slider.js +4 -0
  262. package/dist/components/ui/slider.js.map +1 -0
  263. package/dist/components/ui/sonner.d.ts +9 -0
  264. package/dist/components/ui/sonner.js +4 -0
  265. package/dist/components/ui/sonner.js.map +1 -0
  266. package/dist/components/ui/spinner.d.ts +9 -0
  267. package/dist/components/ui/spinner.js +4 -0
  268. package/dist/components/ui/spinner.js.map +1 -0
  269. package/dist/components/ui/switch.d.ts +6 -0
  270. package/dist/components/ui/switch.js +4 -0
  271. package/dist/components/ui/switch.js.map +1 -0
  272. package/dist/components/ui/table.d.ts +12 -0
  273. package/dist/components/ui/table.js +4 -0
  274. package/dist/components/ui/table.js.map +1 -0
  275. package/dist/components/ui/tabs.d.ts +20 -0
  276. package/dist/components/ui/tabs.js +4 -0
  277. package/dist/components/ui/tabs.js.map +1 -0
  278. package/dist/components/ui/textarea.d.ts +5 -0
  279. package/dist/components/ui/textarea.js +4 -0
  280. package/dist/components/ui/textarea.js.map +1 -0
  281. package/dist/components/ui/toggle-group.d.ts +15 -0
  282. package/dist/components/ui/toggle-group.js +5 -0
  283. package/dist/components/ui/toggle-group.js.map +1 -0
  284. package/dist/components/ui/toggle.d.ts +15 -0
  285. package/dist/components/ui/toggle.js +4 -0
  286. package/dist/components/ui/toggle.js.map +1 -0
  287. package/dist/components/ui/tooltip.d.ts +10 -0
  288. package/dist/components/ui/tooltip.js +4 -0
  289. package/dist/components/ui/tooltip.js.map +1 -0
  290. package/dist/components/ui/typography.d.ts +14 -0
  291. package/dist/components/ui/typography.js +4 -0
  292. package/dist/components/ui/typography.js.map +1 -0
  293. package/dist/hooks/index.d.ts +13 -0
  294. package/dist/hooks/index.js +11 -0
  295. package/dist/hooks/index.js.map +1 -0
  296. package/dist/hooks/use-data-filters.d.ts +32 -0
  297. package/dist/hooks/use-data-filters.js +4 -0
  298. package/dist/hooks/use-data-filters.js.map +1 -0
  299. package/dist/hooks/use-keyboard-navigation.d.ts +29 -0
  300. package/dist/hooks/use-keyboard-navigation.js +3 -0
  301. package/dist/hooks/use-keyboard-navigation.js.map +1 -0
  302. package/dist/hooks/use-mobile.d.ts +3 -0
  303. package/dist/hooks/use-mobile.js +3 -0
  304. package/dist/hooks/use-mobile.js.map +1 -0
  305. package/dist/hooks/use-one.d.ts +18 -0
  306. package/dist/hooks/use-one.js +4 -0
  307. package/dist/hooks/use-one.js.map +1 -0
  308. package/dist/hooks/use-prefetch.d.ts +19 -0
  309. package/dist/hooks/use-prefetch.js +3 -0
  310. package/dist/hooks/use-prefetch.js.map +1 -0
  311. package/dist/hooks/use-resource-form.d.ts +37 -0
  312. package/dist/hooks/use-resource-form.js +7 -0
  313. package/dist/hooks/use-resource-form.js.map +1 -0
  314. package/dist/hooks/use-resource-page.d.ts +5 -0
  315. package/dist/hooks/use-resource-page.js +4 -0
  316. package/dist/hooks/use-resource-page.js.map +1 -0
  317. package/dist/hooks/use-resource.d.ts +46 -0
  318. package/dist/hooks/use-resource.js +4 -0
  319. package/dist/hooks/use-resource.js.map +1 -0
  320. package/dist/hooks/use-view-preference.d.ts +6 -0
  321. package/dist/hooks/use-view-preference.js +3 -0
  322. package/dist/hooks/use-view-preference.js.map +1 -0
  323. package/dist/hooks/use-view-settings.d.ts +15 -0
  324. package/dist/hooks/use-view-settings.js +3 -0
  325. package/dist/hooks/use-view-settings.js.map +1 -0
  326. package/dist/hooks/use-wizard.d.ts +25 -0
  327. package/dist/hooks/use-wizard.js +3 -0
  328. package/dist/hooks/use-wizard.js.map +1 -0
  329. package/dist/index.d.ts +87 -0
  330. package/dist/index.js +56 -0
  331. package/dist/index.js.map +1 -0
  332. package/dist/navigation-qI86Ldug.d.ts +90 -0
  333. package/dist/provider/index.d.ts +1 -0
  334. package/dist/provider/index.js +3 -0
  335. package/dist/provider/index.js.map +1 -0
  336. package/dist/status-colors.d.ts +29 -0
  337. package/dist/status-colors.js +72 -0
  338. package/dist/status-colors.js.map +1 -0
  339. package/dist/types-HIZY7zxO.d.ts +61 -0
  340. package/dist/use-resource-page-BzvUM8de.d.ts +159 -0
  341. package/dist/utils.d.ts +7 -0
  342. package/dist/utils.js +3 -0
  343. package/dist/utils.js.map +1 -0
  344. package/package.json +350 -0
@@ -0,0 +1,2923 @@
1
+ "use client";
2
+ import { useResourceForm, buildArrayItemDefaults } from '../../chunk-F2JB7YFF.js';
3
+ import { useResource } from '../../chunk-IKIFQILN.js';
4
+ import { useWizard } from '../../chunk-4HUKJSKC.js';
5
+ import { Textarea } from '../../chunk-W64GERJW.js';
6
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../../chunk-SCUT2ZCD.js';
7
+ import { Skeleton } from '../../chunk-QFM4Z2SS.js';
8
+ import { Spinner } from '../../chunk-OVU3Z2MT.js';
9
+ import { Switch } from '../../chunk-2MXMWBDV.js';
10
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
11
+ import { Progress } from '../../chunk-W5Y3LG5K.js';
12
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../chunk-2B3MFHBQ.js';
13
+ import { Separator } from '../../chunk-SXSCHWZU.js';
14
+ import { Input } from '../../chunk-6V4GJUE3.js';
15
+ import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '../../chunk-7OBL3H5P.js';
16
+ import { Popover, PopoverTrigger, PopoverContent } from '../../chunk-NJMO3VPA.js';
17
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-YZEA5TGK.js';
18
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuItem } from '../../chunk-SBJV3K4G.js';
19
+ import { FormField, FormLabel, FormError, FormDescription } from '../../chunk-X7KMF6NH.js';
20
+ import '../../chunk-AIODTBWP.js';
21
+ import { Card, CardContent, CardHeader, CardTitle } from '../../chunk-FN3FZVUR.js';
22
+ import { Button } from '../../chunk-4TROUCWO.js';
23
+ import { Checkbox } from '../../chunk-WZWR2ELB.js';
24
+ import { Badge } from '../../chunk-MUQLZFUR.js';
25
+ import { cn } from '../../chunk-NZ5WBA4R.js';
26
+ import { titleCase, useResourcePage } from '../../chunk-CLK7JBZJ.js';
27
+ import '../../chunk-XISSLIMH.js';
28
+ import { DefaultLink, defaultNavigate, isPathActive } from '../../chunk-AI6CCNWK.js';
29
+ export { DefaultLink, defaultNavigate, isPathActive } from '../../chunk-AI6CCNWK.js';
30
+ import { useIsMobile } from '../../chunk-M7AU5DDM.js';
31
+ import '../../chunk-A5WHNNXJ.js';
32
+ import { useDataProvider } from '../../chunk-JEECTX26.js';
33
+ import { Search, LayoutGrid, Table2, List, Settings2, AlertTriangle, RefreshCw, ChevronDown, Check, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Menu, Sun, Moon, Plus } from 'lucide-react';
34
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
35
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
36
+ import { formatDistanceToNow, format } from 'date-fns';
37
+ import { useFieldArray, Controller, useWatch } from 'react-hook-form';
38
+ import { useDropzone } from 'react-dropzone';
39
+
40
+ function EmptyState({
41
+ icon: Icon = Search,
42
+ message = "No items found",
43
+ action,
44
+ hasFilters = false,
45
+ onClearFilters
46
+ }) {
47
+ return /* @__PURE__ */ jsxs("div", { className: "text-center py-16 animate-fade-in", children: [
48
+ /* @__PURE__ */ jsx(Icon, { className: "h-10 w-10 text-muted-foreground/30 mx-auto mb-3" }),
49
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: hasFilters ? "No results match your filters" : message }),
50
+ hasFilters && onClearFilters ? /* @__PURE__ */ jsx(Button, { variant: "link", size: "sm", onClick: onClearFilters, className: "mt-2", children: "Clear all filters" }) : action && /* @__PURE__ */ jsx("div", { className: "mt-4", children: action })
51
+ ] });
52
+ }
53
+ function ViewToggle({ viewMode, onViewModeChange }) {
54
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center border rounded-lg overflow-hidden", children: [
55
+ /* @__PURE__ */ jsx(
56
+ Button,
57
+ {
58
+ variant: viewMode === "grid" ? "default" : "ghost",
59
+ size: "icon",
60
+ className: "h-8 w-8 rounded-none",
61
+ onClick: () => onViewModeChange("grid"),
62
+ title: "Grid view",
63
+ children: /* @__PURE__ */ jsx(LayoutGrid, { className: "h-4 w-4" })
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx(
67
+ Button,
68
+ {
69
+ variant: viewMode === "table" ? "default" : "ghost",
70
+ size: "icon",
71
+ className: "h-8 w-8 rounded-none",
72
+ onClick: () => onViewModeChange("table"),
73
+ title: "Table view",
74
+ children: /* @__PURE__ */ jsx(Table2, { className: "h-4 w-4" })
75
+ }
76
+ ),
77
+ /* @__PURE__ */ jsx(
78
+ Button,
79
+ {
80
+ variant: viewMode === "list" ? "default" : "ghost",
81
+ size: "icon",
82
+ className: "h-8 w-8 rounded-none",
83
+ onClick: () => onViewModeChange("list"),
84
+ title: "List view",
85
+ children: /* @__PURE__ */ jsx(List, { className: "h-4 w-4" })
86
+ }
87
+ )
88
+ ] });
89
+ }
90
+ var PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
91
+ var DENSITY_OPTIONS = [
92
+ { value: "compact", label: "Compact" },
93
+ { value: "comfortable", label: "Comfortable" },
94
+ { value: "spacious", label: "Spacious" }
95
+ ];
96
+ function ViewSettings({
97
+ pageSize,
98
+ onPageSizeChange,
99
+ density,
100
+ onDensityChange
101
+ }) {
102
+ return /* @__PURE__ */ jsxs(Popover, { children: [
103
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
104
+ Button,
105
+ {
106
+ variant: "ghost",
107
+ size: "icon",
108
+ className: "h-8 w-8",
109
+ title: "View settings",
110
+ children: /* @__PURE__ */ jsx(Settings2, { className: "h-4 w-4" })
111
+ }
112
+ ) }),
113
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-56 space-y-4", children: [
114
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Display Settings" }),
115
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
116
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Page size" }),
117
+ /* @__PURE__ */ jsxs(
118
+ Select,
119
+ {
120
+ value: String(pageSize),
121
+ onValueChange: (v) => onPageSizeChange(Number(v)),
122
+ children: [
123
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
124
+ /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: PAGE_SIZE_OPTIONS.map((n) => /* @__PURE__ */ jsxs(SelectItem, { value: String(n), children: [
125
+ n,
126
+ " items"
127
+ ] }, n)) })
128
+ ]
129
+ }
130
+ )
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
133
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Density" }),
134
+ /* @__PURE__ */ jsxs(
135
+ Select,
136
+ {
137
+ value: density,
138
+ onValueChange: (v) => onDensityChange(v),
139
+ children: [
140
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
141
+ /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: DENSITY_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
142
+ ]
143
+ }
144
+ )
145
+ ] })
146
+ ] })
147
+ ] });
148
+ }
149
+ function DataPageShell({
150
+ title,
151
+ icon: Icon,
152
+ entityName,
153
+ subtitle,
154
+ createAction,
155
+ isLoading = false,
156
+ isEmpty = false,
157
+ emptyIcon,
158
+ emptyMessage,
159
+ emptyAction,
160
+ hasError = false,
161
+ errorMessage,
162
+ onRetry,
163
+ count,
164
+ totalCount,
165
+ viewMode,
166
+ onViewModeChange,
167
+ pageSize,
168
+ onPageSizeChange,
169
+ density,
170
+ onDensityChange,
171
+ filters,
172
+ hasActiveFilters = false,
173
+ onClearFilters,
174
+ children
175
+ }) {
176
+ const entity = entityName || title.toLowerCase();
177
+ const hasViewToggle = viewMode && onViewModeChange;
178
+ const hasViewSettings = onPageSizeChange && onDensityChange;
179
+ const hasCount = count !== void 0 && totalCount !== void 0;
180
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
181
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
182
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
183
+ Icon && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 overflow-hidden", children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-primary" }) }),
184
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
185
+ /* @__PURE__ */ jsx(
186
+ "h1",
187
+ {
188
+ "data-testid": "page-title",
189
+ className: "text-xl sm:text-2xl font-bold font-display tracking-tight truncate",
190
+ children: title
191
+ }
192
+ ),
193
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground truncate", children: subtitle })
194
+ ] })
195
+ ] }),
196
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
197
+ hasCount && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: hasActiveFilters ? `${count} of ${totalCount}` : `${totalCount} ${entity.endsWith("s") ? entity : `${entity}s`}` }),
198
+ hasViewToggle && /* @__PURE__ */ jsx(ViewToggle, { viewMode, onViewModeChange }),
199
+ hasViewSettings && /* @__PURE__ */ jsx(
200
+ ViewSettings,
201
+ {
202
+ pageSize: pageSize ?? 25,
203
+ onPageSizeChange,
204
+ density: density ?? "comfortable",
205
+ onDensityChange
206
+ }
207
+ ),
208
+ createAction
209
+ ] })
210
+ ] }),
211
+ filters,
212
+ isLoading ? /* @__PURE__ */ jsx(
213
+ "div",
214
+ {
215
+ "data-testid": "page-loader",
216
+ className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4",
217
+ children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsx(
218
+ Skeleton,
219
+ {
220
+ className: "h-40 rounded-xl",
221
+ style: { animationDelay: `${i * 75}ms` }
222
+ },
223
+ i
224
+ ))
225
+ }
226
+ ) : hasError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
227
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3" }),
228
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground mb-1", children: "Something went wrong" }),
229
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: errorMessage || `Failed to load ${entity}s. Please try again.` }),
230
+ onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
231
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 mr-1" }),
232
+ " Retry"
233
+ ] })
234
+ ] }) : isEmpty ? /* @__PURE__ */ jsx(
235
+ EmptyState,
236
+ {
237
+ icon: emptyIcon,
238
+ message: emptyMessage,
239
+ action: emptyAction,
240
+ hasFilters: hasActiveFilters,
241
+ onClearFilters
242
+ }
243
+ ) : children
244
+ ] });
245
+ }
246
+ function FilterCombobox({
247
+ label,
248
+ icon: Icon,
249
+ options,
250
+ selected,
251
+ onChange,
252
+ searchPlaceholder,
253
+ allLabel,
254
+ labelMap
255
+ }) {
256
+ const [open, setOpen] = useState(false);
257
+ const toggle = (value) => {
258
+ onChange(
259
+ selected.includes(value) ? selected.filter((v) => v !== value) : [...selected, value]
260
+ );
261
+ };
262
+ const getDisplayLabel = (value) => labelMap?.[value] || value;
263
+ const triggerLabel = selected.length === 0 ? allLabel || `All ${label}` : selected.length === 1 ? getDisplayLabel(selected[0]) : `${selected.length} ${label.toLowerCase()}`;
264
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
265
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
266
+ Button,
267
+ {
268
+ variant: selected.length > 0 ? "default" : "outline",
269
+ size: "sm",
270
+ className: "text-xs h-7 gap-1.5",
271
+ children: [
272
+ /* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
273
+ triggerLabel,
274
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-0.5 opacity-60" })
275
+ ]
276
+ }
277
+ ) }),
278
+ /* @__PURE__ */ jsx(
279
+ PopoverContent,
280
+ {
281
+ className: "w-[220px] max-w-[calc(100vw-2rem)] p-0 bg-popover z-50",
282
+ align: "start",
283
+ children: /* @__PURE__ */ jsxs(Command, { children: [
284
+ /* @__PURE__ */ jsx(
285
+ CommandInput,
286
+ {
287
+ placeholder: searchPlaceholder || `Search ${label.toLowerCase()}...`
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsxs(CommandList, { children: [
291
+ /* @__PURE__ */ jsxs(CommandEmpty, { children: [
292
+ "No ",
293
+ label.toLowerCase(),
294
+ " found."
295
+ ] }),
296
+ /* @__PURE__ */ jsxs(CommandGroup, { children: [
297
+ /* @__PURE__ */ jsxs(
298
+ CommandItem,
299
+ {
300
+ value: `__all_${label.toLowerCase()}__`,
301
+ onSelect: () => onChange([]),
302
+ children: [
303
+ /* @__PURE__ */ jsx(
304
+ Check,
305
+ {
306
+ className: cn(
307
+ "mr-2 h-3.5 w-3.5",
308
+ selected.length === 0 ? "opacity-100" : "opacity-0"
309
+ )
310
+ }
311
+ ),
312
+ allLabel || `All ${label}`
313
+ ]
314
+ }
315
+ ),
316
+ options.map((opt) => /* @__PURE__ */ jsxs(
317
+ CommandItem,
318
+ {
319
+ value: opt.label || getDisplayLabel(opt.value),
320
+ onSelect: () => toggle(opt.value),
321
+ children: [
322
+ /* @__PURE__ */ jsx(
323
+ Check,
324
+ {
325
+ className: cn(
326
+ "mr-2 h-3.5 w-3.5",
327
+ selected.includes(opt.value) ? "opacity-100" : "opacity-0"
328
+ )
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: opt.label || getDisplayLabel(opt.value) }),
332
+ opt.count !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-2", children: opt.count })
333
+ ]
334
+ },
335
+ opt.value
336
+ ))
337
+ ] })
338
+ ] })
339
+ ] })
340
+ }
341
+ )
342
+ ] });
343
+ }
344
+ function DataFilters({
345
+ search,
346
+ onSearchChange,
347
+ searchPlaceholder = "Search...",
348
+ sort,
349
+ onSortChange,
350
+ sortOptions,
351
+ filters = [],
352
+ statusChips,
353
+ savedViews = [],
354
+ onLoadView,
355
+ onSaveView,
356
+ onDeleteView,
357
+ canSaveViews = false,
358
+ onViewSaved,
359
+ onClearAll,
360
+ searchInputRef
361
+ }) {
362
+ const internalSearchRef = useRef(null);
363
+ const effectiveSearchRef = searchInputRef || internalSearchRef;
364
+ const [viewsOpen, setViewsOpen] = useState(false);
365
+ const [saveDialogOpen, setSaveDialogOpen] = useState(false);
366
+ const [newViewName, setNewViewName] = useState("");
367
+ const hasFilters = filters.some((f) => f.selected.length > 0) || (statusChips?.selected.length ?? 0) > 0;
368
+ const toggleStatus = (status) => {
369
+ if (!statusChips) return;
370
+ const { selected, onChange } = statusChips;
371
+ onChange(
372
+ selected.includes(status) ? selected.filter((s) => s !== status) : [...selected, status]
373
+ );
374
+ };
375
+ const handleSaveView = () => {
376
+ if (!newViewName.trim()) return;
377
+ onSaveView?.(newViewName.trim());
378
+ setNewViewName("");
379
+ setSaveDialogOpen(false);
380
+ onViewSaved?.(newViewName.trim());
381
+ };
382
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
383
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
384
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
385
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
386
+ /* @__PURE__ */ jsx(
387
+ Input,
388
+ {
389
+ ref: effectiveSearchRef,
390
+ placeholder: searchPlaceholder,
391
+ value: search,
392
+ onChange: (e) => onSearchChange(e.target.value),
393
+ className: "pl-9"
394
+ }
395
+ ),
396
+ search && /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ onClick: () => onSearchChange(""),
400
+ className: "absolute right-3 top-1/2 -translate-y-1/2",
401
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5 text-muted-foreground" })
402
+ }
403
+ )
404
+ ] }),
405
+ /* @__PURE__ */ jsxs(Select, { value: sort, onValueChange: onSortChange, children: [
406
+ /* @__PURE__ */ jsxs(SelectTrigger, { className: "w-full sm:w-[180px] shrink-0", children: [
407
+ /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3.5 w-3.5 mr-1.5 text-muted-foreground" }),
408
+ /* @__PURE__ */ jsx(SelectValue, { placeholder: "Sort by..." })
409
+ ] }),
410
+ /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: sortOptions.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
411
+ ] })
412
+ ] }),
413
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
414
+ (savedViews.length > 0 || canSaveViews) && /* @__PURE__ */ jsxs(Popover, { open: viewsOpen, onOpenChange: setViewsOpen, children: [
415
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
416
+ Button,
417
+ {
418
+ variant: "outline",
419
+ size: "sm",
420
+ className: "text-xs h-7 gap-1.5",
421
+ children: [
422
+ /* @__PURE__ */ jsx(Bookmark, { className: "h-3 w-3" }),
423
+ "Views",
424
+ savedViews.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-0.5", children: [
425
+ "(",
426
+ savedViews.length,
427
+ ")"
428
+ ] }),
429
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-0.5 opacity-60" })
430
+ ]
431
+ }
432
+ ) }),
433
+ /* @__PURE__ */ jsx(
434
+ PopoverContent,
435
+ {
436
+ className: "w-[240px] p-2 bg-popover z-50",
437
+ align: "start",
438
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
439
+ savedViews.map((view) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 group", children: [
440
+ /* @__PURE__ */ jsx(
441
+ "button",
442
+ {
443
+ className: "flex-1 text-left text-sm px-2 py-1.5 rounded hover:bg-accent truncate",
444
+ onClick: () => {
445
+ onLoadView?.(view.filters);
446
+ setViewsOpen(false);
447
+ },
448
+ children: view.name
449
+ }
450
+ ),
451
+ onDeleteView && /* @__PURE__ */ jsx(
452
+ "button",
453
+ {
454
+ className: "opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-opacity",
455
+ onClick: () => onDeleteView(view.id),
456
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
457
+ }
458
+ )
459
+ ] }, view.id)),
460
+ savedViews.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground px-2 py-1", children: "No saved views yet" }),
461
+ canSaveViews && /* @__PURE__ */ jsxs(Fragment, { children: [
462
+ /* @__PURE__ */ jsx("div", { className: "border-t my-1" }),
463
+ /* @__PURE__ */ jsxs(
464
+ "button",
465
+ {
466
+ className: "w-full flex items-center gap-1.5 text-sm px-2 py-1.5 rounded hover:bg-accent text-primary",
467
+ onClick: () => {
468
+ setViewsOpen(false);
469
+ setSaveDialogOpen(true);
470
+ },
471
+ children: [
472
+ /* @__PURE__ */ jsx(BookmarkPlus, { className: "h-3.5 w-3.5" }),
473
+ "Save current view"
474
+ ]
475
+ }
476
+ )
477
+ ] })
478
+ ] })
479
+ }
480
+ )
481
+ ] }),
482
+ statusChips && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mr-1", children: statusChips.options.map((s) => /* @__PURE__ */ jsx(
483
+ "button",
484
+ {
485
+ onClick: () => toggleStatus(s),
486
+ className: cn(
487
+ "text-xs px-2.5 py-1 rounded-full border transition-all font-medium",
488
+ statusChips.selected.includes(s) ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border hover:border-primary/40"
489
+ ),
490
+ children: s
491
+ },
492
+ s
493
+ )) }),
494
+ filters.map(
495
+ (f) => f.options.length > 0 ? /* @__PURE__ */ jsx(
496
+ FilterCombobox,
497
+ {
498
+ label: f.label,
499
+ icon: f.icon,
500
+ options: f.options,
501
+ selected: f.selected,
502
+ onChange: f.onChange,
503
+ searchPlaceholder: f.searchPlaceholder,
504
+ allLabel: f.allLabel,
505
+ labelMap: f.labelMap
506
+ },
507
+ f.key
508
+ ) : null
509
+ ),
510
+ filters.flatMap(
511
+ (f) => f.selected.map((val) => /* @__PURE__ */ jsxs(
512
+ Badge,
513
+ {
514
+ variant: "secondary",
515
+ className: "text-xs cursor-pointer gap-1",
516
+ onClick: () => f.onChange(f.selected.filter((v) => v !== val)),
517
+ children: [
518
+ f.labelMap?.[val] || val,
519
+ /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
520
+ ]
521
+ },
522
+ `${f.key}-${val}`
523
+ ))
524
+ ),
525
+ hasFilters && onClearAll && /* @__PURE__ */ jsxs(
526
+ Button,
527
+ {
528
+ variant: "ghost",
529
+ size: "sm",
530
+ className: "text-xs h-7 text-muted-foreground",
531
+ onClick: onClearAll,
532
+ children: [
533
+ /* @__PURE__ */ jsx(X, { className: "h-3 w-3 mr-1" }),
534
+ "Clear all"
535
+ ]
536
+ }
537
+ )
538
+ ] }),
539
+ /* @__PURE__ */ jsx(Dialog, { open: saveDialogOpen, onOpenChange: setSaveDialogOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm", children: [
540
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { className: "font-display", children: "Save Current View" }) }),
541
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
542
+ /* @__PURE__ */ jsx(
543
+ Input,
544
+ {
545
+ placeholder: "View name (e.g. 'Active Sessions')",
546
+ value: newViewName,
547
+ onChange: (e) => setNewViewName(e.target.value),
548
+ onKeyDown: (e) => e.key === "Enter" && handleSaveView(),
549
+ autoFocus: true
550
+ }
551
+ ),
552
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
553
+ /* @__PURE__ */ jsx(
554
+ Button,
555
+ {
556
+ variant: "outline",
557
+ size: "sm",
558
+ onClick: () => setSaveDialogOpen(false),
559
+ children: "Cancel"
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsx(
563
+ Button,
564
+ {
565
+ size: "sm",
566
+ onClick: handleSaveView,
567
+ disabled: !newViewName.trim(),
568
+ children: "Save"
569
+ }
570
+ )
571
+ ] })
572
+ ] })
573
+ ] }) })
574
+ ] });
575
+ }
576
+ var DEFAULT_BATCH_SIZE = 48;
577
+ function DataGrid({
578
+ items,
579
+ viewMode,
580
+ batchSize = DEFAULT_BATCH_SIZE,
581
+ gridCols = "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
582
+ renderCard,
583
+ renderTable,
584
+ renderListItem,
585
+ staggerAnimation = true,
586
+ getKey,
587
+ focusedIndex = -1,
588
+ keyboardContainerRef
589
+ }) {
590
+ const [visibleCount, setVisibleCount] = useState(batchSize);
591
+ const sentinelRef = useRef(null);
592
+ useEffect(() => {
593
+ setVisibleCount(batchSize);
594
+ }, [items.length, batchSize]);
595
+ useEffect(() => {
596
+ if (viewMode !== "grid") return;
597
+ const observer = new IntersectionObserver(
598
+ (entries) => {
599
+ if (entries[0]?.isIntersecting && visibleCount < items.length) {
600
+ setVisibleCount(
601
+ (prev) => Math.min(prev + batchSize, items.length)
602
+ );
603
+ }
604
+ },
605
+ { rootMargin: "200px" }
606
+ );
607
+ if (sentinelRef.current) observer.observe(sentinelRef.current);
608
+ return () => observer.disconnect();
609
+ }, [viewMode, visibleCount, items.length, batchSize]);
610
+ if (viewMode === "table" && renderTable) {
611
+ return /* @__PURE__ */ jsx(Fragment, { children: renderTable(items) });
612
+ }
613
+ if (viewMode === "list" && renderListItem) {
614
+ const keyFnList = getKey || ((item) => item.id);
615
+ return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border rounded-lg border", children: items.map((item, i) => /* @__PURE__ */ jsx(
616
+ "div",
617
+ {
618
+ className: "px-4 py-3 transition-colors hover:bg-accent/50",
619
+ children: renderListItem(item, i)
620
+ },
621
+ keyFnList(item)
622
+ )) });
623
+ }
624
+ const visible = items.slice(0, visibleCount);
625
+ const keyFn = getKey || ((item) => item.id);
626
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
627
+ /* @__PURE__ */ jsx("div", { ref: keyboardContainerRef, className: `grid ${gridCols} gap-4`, children: visible.map((item, i) => /* @__PURE__ */ jsx(
628
+ "div",
629
+ {
630
+ "data-nav-item": true,
631
+ className: cn(
632
+ staggerAnimation && "animate-fade-in",
633
+ focusedIndex === i && "ring-2 ring-primary ring-offset-2 ring-offset-background rounded-xl transition-shadow duration-150"
634
+ ),
635
+ style: staggerAnimation ? {
636
+ animationDelay: `${i % batchSize * 30}ms`,
637
+ animationFillMode: "both"
638
+ } : void 0,
639
+ children: renderCard(item, i)
640
+ },
641
+ keyFn(item)
642
+ )) }),
643
+ visibleCount < items.length && /* @__PURE__ */ jsx(
644
+ "div",
645
+ {
646
+ ref: sentinelRef,
647
+ className: "h-12 flex items-center justify-center",
648
+ children: /* @__PURE__ */ jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" })
649
+ }
650
+ )
651
+ ] });
652
+ }
653
+ function getCellClasses(col) {
654
+ const hide = col.hideBelow ? col.hideBelow === "sm" ? "hidden sm:table-cell" : col.hideBelow === "md" ? "hidden md:table-cell" : col.hideBelow === "lg" ? "hidden lg:table-cell" : "hidden xl:table-cell" : "";
655
+ return cn(col.width, hide);
656
+ }
657
+ function EditableCell({
658
+ item,
659
+ column,
660
+ value
661
+ }) {
662
+ const [editing, setEditing] = useState(false);
663
+ const [draft, setDraft] = useState(String(value ?? ""));
664
+ const editable = column.editable;
665
+ const commit = useCallback(
666
+ (v) => {
667
+ editable.onSave(item, v);
668
+ setEditing(false);
669
+ },
670
+ [editable, item]
671
+ );
672
+ if (!editing) {
673
+ return /* @__PURE__ */ jsx(
674
+ "span",
675
+ {
676
+ className: "cursor-pointer hover:underline decoration-dashed underline-offset-4 decoration-muted-foreground/40",
677
+ onClick: (e) => {
678
+ e.stopPropagation();
679
+ setDraft(String(value ?? ""));
680
+ setEditing(true);
681
+ },
682
+ children: column.render ? column.render(item, value) : String(value ?? "\u2014")
683
+ }
684
+ );
685
+ }
686
+ if (editable.type === "select" && editable.options) {
687
+ return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Select, { defaultValue: draft, onValueChange: (v) => commit(v), children: [
688
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
689
+ /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
690
+ ] }) });
691
+ }
692
+ return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
693
+ Input,
694
+ {
695
+ autoFocus: true,
696
+ className: "h-7 text-xs w-full",
697
+ value: draft,
698
+ onChange: (e) => setDraft(e.target.value),
699
+ onBlur: () => commit(draft),
700
+ onKeyDown: (e) => {
701
+ if (e.key === "Enter") commit(draft);
702
+ if (e.key === "Escape") setEditing(false);
703
+ }
704
+ }
705
+ ) });
706
+ }
707
+ function ResizeHandle({
708
+ onResize,
709
+ onResizeEnd
710
+ }) {
711
+ const startX = useRef(0);
712
+ const [isResizing, setIsResizing] = useState(false);
713
+ const handlePointerDown = useCallback(
714
+ (e) => {
715
+ e.preventDefault();
716
+ e.stopPropagation();
717
+ startX.current = e.clientX;
718
+ setIsResizing(true);
719
+ document.body.style.cursor = "col-resize";
720
+ const target = e.currentTarget;
721
+ target.setPointerCapture(e.pointerId);
722
+ const onMove = (ev) => {
723
+ const delta = ev.clientX - startX.current;
724
+ startX.current = ev.clientX;
725
+ onResize(delta);
726
+ };
727
+ const onUp = () => {
728
+ target.removeEventListener("pointermove", onMove);
729
+ target.removeEventListener("pointerup", onUp);
730
+ setIsResizing(false);
731
+ document.body.style.cursor = "";
732
+ onResizeEnd?.();
733
+ };
734
+ target.addEventListener("pointermove", onMove);
735
+ target.addEventListener("pointerup", onUp);
736
+ },
737
+ [onResize, onResizeEnd]
738
+ );
739
+ return /* @__PURE__ */ jsx(
740
+ "div",
741
+ {
742
+ className: cn(
743
+ "absolute right-0 top-0 bottom-0 cursor-col-resize transition-colors",
744
+ isResizing ? "w-1.5 bg-primary/50" : "w-1 hover:bg-primary/30"
745
+ ),
746
+ onPointerDown: handlePointerDown,
747
+ onClick: (e) => e.stopPropagation()
748
+ }
749
+ );
750
+ }
751
+ function DataTable({
752
+ items,
753
+ columns,
754
+ getKey,
755
+ onRowClick,
756
+ selectable,
757
+ selected,
758
+ onToggleSelect,
759
+ onToggleAll,
760
+ renderActions,
761
+ emptyMessage = "No records found",
762
+ columnVisibility = true,
763
+ columnVisibilityKey,
764
+ resizableColumns = true,
765
+ pagination = true,
766
+ pageSize: initialPageSize = 25,
767
+ pageSizeOptions = [10, 25, 50, 100],
768
+ stickyHeader = true,
769
+ resizeKey,
770
+ stickyFirstColumn
771
+ }) {
772
+ const [sorts, setSorts] = useState([]);
773
+ const handleSort = useCallback(
774
+ (key, shiftKey) => {
775
+ setSorts((prev) => {
776
+ const existing = prev.findIndex((s) => s.key === key);
777
+ if (existing >= 0) {
778
+ const entry = prev[existing];
779
+ if (entry.dir === "asc") {
780
+ const next = [...prev];
781
+ next[existing] = { key, dir: "desc" };
782
+ return next;
783
+ }
784
+ return prev.filter((_, i) => i !== existing);
785
+ }
786
+ if (shiftKey) return [...prev, { key, dir: "asc" }];
787
+ return [{ key, dir: "asc" }];
788
+ });
789
+ },
790
+ []
791
+ );
792
+ const [visibleCols, setVisibleCols] = useState(
793
+ () => {
794
+ if (columnVisibilityKey) {
795
+ try {
796
+ const saved = localStorage.getItem(
797
+ `dt-vis-${columnVisibilityKey}`
798
+ );
799
+ if (saved) return JSON.parse(saved);
800
+ } catch {
801
+ }
802
+ }
803
+ const initial = {};
804
+ columns.forEach((c) => {
805
+ initial[c.key] = c.defaultVisible !== false;
806
+ });
807
+ return initial;
808
+ }
809
+ );
810
+ useEffect(() => {
811
+ if (columnVisibilityKey) {
812
+ localStorage.setItem(
813
+ `dt-vis-${columnVisibilityKey}`,
814
+ JSON.stringify(visibleCols)
815
+ );
816
+ }
817
+ }, [visibleCols, columnVisibilityKey]);
818
+ const toggleColumnVisibility = useCallback((key) => {
819
+ setVisibleCols((prev) => ({ ...prev, [key]: !prev[key] }));
820
+ }, []);
821
+ const resetColumnVisibility = useCallback(() => {
822
+ const defaults = {};
823
+ columns.forEach((c) => {
824
+ defaults[c.key] = c.defaultVisible !== false;
825
+ });
826
+ setVisibleCols(defaults);
827
+ if (columnVisibilityKey) {
828
+ localStorage.removeItem(`dt-vis-${columnVisibilityKey}`);
829
+ }
830
+ }, [columns, columnVisibilityKey]);
831
+ useEffect(() => {
832
+ setVisibleCols((prev) => {
833
+ const updated = { ...prev };
834
+ let changed = false;
835
+ columns.forEach((c) => {
836
+ if (!(c.key in updated)) {
837
+ updated[c.key] = c.defaultVisible !== false;
838
+ changed = true;
839
+ }
840
+ });
841
+ return changed ? updated : prev;
842
+ });
843
+ }, [columns]);
844
+ const activeColumns = columnVisibility ? columns.filter((c) => visibleCols[c.key] !== false) : columns;
845
+ const [colWidths, setColWidths] = useState(() => {
846
+ if (resizeKey) {
847
+ try {
848
+ const saved = localStorage.getItem(`dt-resize-${resizeKey}`);
849
+ if (saved) return JSON.parse(saved);
850
+ } catch {
851
+ }
852
+ }
853
+ const initial = {};
854
+ columns.forEach((c) => {
855
+ if (c.width) {
856
+ const px = parseInt(c.width, 10);
857
+ if (!isNaN(px) && px > 0) initial[c.key] = px;
858
+ }
859
+ });
860
+ return initial;
861
+ });
862
+ const handleResize = useCallback(
863
+ (key, delta, minWidth) => {
864
+ setColWidths((prev) => {
865
+ const current = prev[key] || 150;
866
+ return { ...prev, [key]: Math.max(minWidth, current + delta) };
867
+ });
868
+ },
869
+ []
870
+ );
871
+ const handleResizeEnd = useCallback(() => {
872
+ if (resizeKey) {
873
+ localStorage.setItem(`dt-resize-${resizeKey}`, JSON.stringify(colWidths));
874
+ }
875
+ }, [resizeKey, colWidths]);
876
+ const [currentPage, setCurrentPage] = useState(0);
877
+ const [currentPageSize, setCurrentPageSize] = useState(initialPageSize);
878
+ useEffect(() => {
879
+ setCurrentPage(0);
880
+ }, [items.length, currentPageSize]);
881
+ const sorted = (() => {
882
+ if (sorts.length === 0) return items;
883
+ return [...items].sort((a, b) => {
884
+ for (const s of sorts) {
885
+ const col = columns.find((c) => c.key === s.key);
886
+ if (!col) continue;
887
+ const acc = col.accessor || ((item) => item[col.key]);
888
+ const va = acc(a);
889
+ const vb = acc(b);
890
+ const sa = va == null ? "" : String(va);
891
+ const sb = vb == null ? "" : String(vb);
892
+ const cmp = sa.localeCompare(sb, void 0, {
893
+ numeric: true,
894
+ sensitivity: "base"
895
+ });
896
+ if (cmp !== 0) return s.dir === "asc" ? cmp : -cmp;
897
+ }
898
+ return 0;
899
+ });
900
+ })();
901
+ const totalItems = sorted.length;
902
+ const totalPages = pagination ? Math.max(1, Math.ceil(totalItems / currentPageSize)) : 1;
903
+ const displayItems = pagination ? sorted.slice(
904
+ currentPage * currentPageSize,
905
+ (currentPage + 1) * currentPageSize
906
+ ) : sorted;
907
+ const showingFrom = totalItems === 0 ? 0 : currentPage * currentPageSize + 1;
908
+ const showingTo = Math.min(
909
+ (currentPage + 1) * currentPageSize,
910
+ totalItems
911
+ );
912
+ const totalCols = activeColumns.length + (selectable ? 1 : 0) + (renderActions ? 1 : 0);
913
+ return /* @__PURE__ */ jsxs("div", { className: "border rounded-lg animate-fade-in flex flex-col", children: [
914
+ columnVisibility && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end px-3 py-2 border-b bg-muted/30", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
915
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
916
+ Button,
917
+ {
918
+ variant: "outline",
919
+ size: "sm",
920
+ className: "h-7 text-xs gap-1.5",
921
+ children: [
922
+ /* @__PURE__ */ jsx(Columns3, { className: "h-3.5 w-3.5" }),
923
+ "Columns"
924
+ ]
925
+ }
926
+ ) }),
927
+ /* @__PURE__ */ jsxs(
928
+ DropdownMenuContent,
929
+ {
930
+ align: "end",
931
+ className: "w-44 bg-popover z-50",
932
+ children: [
933
+ columns.map(
934
+ (col) => col.header ? /* @__PURE__ */ jsx(
935
+ DropdownMenuCheckboxItem,
936
+ {
937
+ checked: visibleCols[col.key] !== false,
938
+ onCheckedChange: () => toggleColumnVisibility(col.key),
939
+ children: col.header
940
+ },
941
+ col.key
942
+ ) : null
943
+ ),
944
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
945
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: resetColumnVisibility, children: "Reset to defaults" })
946
+ ]
947
+ }
948
+ )
949
+ ] }) }),
950
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { style: { tableLayout: "fixed" }, children: [
951
+ /* @__PURE__ */ jsx(
952
+ TableHeader,
953
+ {
954
+ className: stickyHeader ? "sticky top-0 z-10 bg-background" : "",
955
+ children: /* @__PURE__ */ jsxs(TableRow, { children: [
956
+ selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(
957
+ Checkbox,
958
+ {
959
+ checked: items.length > 0 && selected?.size === items.length,
960
+ onCheckedChange: onToggleAll
961
+ }
962
+ ) }),
963
+ activeColumns.map((col, colIndex) => {
964
+ const sortIndex = sorts.findIndex((s) => s.key === col.key);
965
+ const sortEntry = sortIndex >= 0 ? sorts[sortIndex] : null;
966
+ const resizeWidth = colWidths[col.key];
967
+ const isFirstCol = colIndex === 0 && stickyFirstColumn;
968
+ return /* @__PURE__ */ jsxs(
969
+ TableHead,
970
+ {
971
+ className: cn(
972
+ getCellClasses(col),
973
+ col.sortable && "cursor-pointer select-none",
974
+ resizableColumns && "relative",
975
+ isFirstCol && "sticky left-0 bg-background",
976
+ isFirstCol && stickyHeader ? "z-[15]" : isFirstCol ? "z-[5]" : void 0
977
+ ),
978
+ style: resizeWidth ? { width: resizeWidth, minWidth: resizeWidth } : void 0,
979
+ onClick: col.sortable ? (e) => handleSort(col.key, e.shiftKey) : void 0,
980
+ children: [
981
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
982
+ col.header,
983
+ col.sortable && (sortEntry ? /* @__PURE__ */ jsxs(Fragment, { children: [
984
+ sortEntry.dir === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 text-foreground" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "h-3 w-3 text-foreground" }),
985
+ sorts.length > 1 && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: sortIndex + 1 })
986
+ ] }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3 w-3 text-muted-foreground/50" }))
987
+ ] }),
988
+ resizableColumns && /* @__PURE__ */ jsx(
989
+ ResizeHandle,
990
+ {
991
+ onResize: (delta) => handleResize(
992
+ col.key,
993
+ delta,
994
+ col.minWidth ?? 60
995
+ ),
996
+ onResizeEnd: handleResizeEnd
997
+ }
998
+ )
999
+ ]
1000
+ },
1001
+ col.key
1002
+ );
1003
+ }),
1004
+ renderActions && /* @__PURE__ */ jsx(TableHead, { className: "w-10" })
1005
+ ] })
1006
+ }
1007
+ ),
1008
+ /* @__PURE__ */ jsx(TableBody, { children: displayItems.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
1009
+ TableCell,
1010
+ {
1011
+ colSpan: totalCols,
1012
+ className: "text-center py-12 text-muted-foreground",
1013
+ children: emptyMessage
1014
+ }
1015
+ ) }) : displayItems.map((item) => {
1016
+ const id = getKey(item);
1017
+ return /* @__PURE__ */ jsxs(
1018
+ TableRow,
1019
+ {
1020
+ className: cn(onRowClick && "cursor-pointer"),
1021
+ "data-state": selected?.has(id) ? "selected" : void 0,
1022
+ onClick: onRowClick ? () => onRowClick(item) : void 0,
1023
+ children: [
1024
+ selectable && /* @__PURE__ */ jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
1025
+ Checkbox,
1026
+ {
1027
+ checked: selected?.has(id),
1028
+ onCheckedChange: () => onToggleSelect?.(id)
1029
+ }
1030
+ ) }),
1031
+ activeColumns.map((col, colIndex) => {
1032
+ const acc = col.accessor || ((it) => it[col.key]);
1033
+ const value = acc(item);
1034
+ const isFirstCol = colIndex === 0 && stickyFirstColumn;
1035
+ return /* @__PURE__ */ jsx(
1036
+ TableCell,
1037
+ {
1038
+ className: cn(
1039
+ getCellClasses(
1040
+ col
1041
+ ),
1042
+ isFirstCol && "sticky left-0 z-[1] bg-background"
1043
+ ),
1044
+ children: col.editable ? /* @__PURE__ */ jsx(
1045
+ EditableCell,
1046
+ {
1047
+ item,
1048
+ column: col,
1049
+ value
1050
+ }
1051
+ ) : col.render ? col.render(item, value) : /* @__PURE__ */ jsx("span", { className: "truncate block max-w-[300px]", children: String(value ?? "\u2014") })
1052
+ },
1053
+ col.key
1054
+ );
1055
+ }),
1056
+ renderActions && /* @__PURE__ */ jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: renderActions(item) })
1057
+ ]
1058
+ },
1059
+ id
1060
+ );
1061
+ }) })
1062
+ ] }) }),
1063
+ pagination && /* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 z-10 flex flex-col sm:flex-row items-center justify-between gap-2 px-4 py-3 border-t bg-muted/30 text-xs text-muted-foreground", children: [
1064
+ /* @__PURE__ */ jsx("span", { children: totalItems === 0 ? "No records" : `Showing ${showingFrom}\u2013${showingTo} of ${totalItems}` }),
1065
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1066
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1067
+ /* @__PURE__ */ jsx("span", { children: "Rows:" }),
1068
+ /* @__PURE__ */ jsxs(
1069
+ Select,
1070
+ {
1071
+ value: String(currentPageSize),
1072
+ onValueChange: (v) => setCurrentPageSize(Number(v)),
1073
+ children: [
1074
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 w-16 text-xs", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1075
+ /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: pageSizeOptions.map((n) => /* @__PURE__ */ jsx(SelectItem, { value: String(n), children: n }, n)) })
1076
+ ]
1077
+ }
1078
+ )
1079
+ ] }),
1080
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1081
+ /* @__PURE__ */ jsx(
1082
+ Button,
1083
+ {
1084
+ variant: "outline",
1085
+ size: "icon",
1086
+ className: "h-7 w-7",
1087
+ disabled: currentPage === 0,
1088
+ onClick: () => setCurrentPage((p) => p - 1),
1089
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-3.5 w-3.5" })
1090
+ }
1091
+ ),
1092
+ /* @__PURE__ */ jsxs("span", { className: "px-2 tabular-nums", children: [
1093
+ currentPage + 1,
1094
+ " / ",
1095
+ totalPages
1096
+ ] }),
1097
+ /* @__PURE__ */ jsx(
1098
+ Button,
1099
+ {
1100
+ variant: "outline",
1101
+ size: "icon",
1102
+ className: "h-7 w-7",
1103
+ disabled: currentPage >= totalPages - 1,
1104
+ onClick: () => setCurrentPage((p) => p + 1),
1105
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
1106
+ }
1107
+ )
1108
+ ] })
1109
+ ] })
1110
+ ] })
1111
+ ] });
1112
+ }
1113
+ function ThumbnailCell({
1114
+ src,
1115
+ alt
1116
+ }) {
1117
+ if (!src) {
1118
+ return /* @__PURE__ */ jsx("div", { className: "h-10 w-14 rounded bg-muted flex items-center justify-center text-[10px] text-muted-foreground", children: "\u2014" });
1119
+ }
1120
+ return /* @__PURE__ */ jsx(
1121
+ "img",
1122
+ {
1123
+ src,
1124
+ alt,
1125
+ className: "h-10 w-14 rounded object-cover bg-muted",
1126
+ loading: "lazy"
1127
+ }
1128
+ );
1129
+ }
1130
+ function LinkCell({
1131
+ href,
1132
+ label
1133
+ }) {
1134
+ if (!href) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1135
+ return /* @__PURE__ */ jsxs(
1136
+ "a",
1137
+ {
1138
+ href,
1139
+ target: "_blank",
1140
+ rel: "noopener noreferrer",
1141
+ onClick: (e) => e.stopPropagation(),
1142
+ className: "inline-flex items-center gap-1 text-xs text-primary hover:underline max-w-[200px] truncate",
1143
+ children: [
1144
+ label || new URL(href).hostname,
1145
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0" })
1146
+ ]
1147
+ }
1148
+ );
1149
+ }
1150
+ function DataBulkBar({
1151
+ selected,
1152
+ totalCount,
1153
+ onToggleAll,
1154
+ onClearSelection,
1155
+ actions
1156
+ }) {
1157
+ if (selected.size === 0) return null;
1158
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 rounded-lg border bg-muted/50 animate-fade-in", children: [
1159
+ /* @__PURE__ */ jsx(
1160
+ Checkbox,
1161
+ {
1162
+ checked: selected.size === totalCount,
1163
+ onCheckedChange: onToggleAll
1164
+ }
1165
+ ),
1166
+ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
1167
+ selected.size,
1168
+ " selected"
1169
+ ] }),
1170
+ actions.map((action) => {
1171
+ const ActionIcon = action.icon;
1172
+ return /* @__PURE__ */ jsxs(
1173
+ Button,
1174
+ {
1175
+ size: "sm",
1176
+ variant: action.variant || "outline",
1177
+ onClick: action.onClick,
1178
+ children: [
1179
+ /* @__PURE__ */ jsx(ActionIcon, { className: "h-3.5 w-3.5 mr-1" }),
1180
+ action.label
1181
+ ]
1182
+ },
1183
+ action.label
1184
+ );
1185
+ }),
1186
+ /* @__PURE__ */ jsx(
1187
+ Button,
1188
+ {
1189
+ size: "sm",
1190
+ variant: "ghost",
1191
+ onClick: onClearSelection,
1192
+ className: "ml-auto text-xs text-muted-foreground",
1193
+ children: "Clear"
1194
+ }
1195
+ )
1196
+ ] });
1197
+ }
1198
+ function DateCell(options) {
1199
+ return (_item, value) => {
1200
+ if (!value) return "\u2014";
1201
+ const date = new Date(String(value));
1202
+ if (isNaN(date.getTime())) return String(value);
1203
+ const relative = options?.relative !== false;
1204
+ const fmt = options?.format ?? "MMM d, yyyy";
1205
+ return relative ? formatDistanceToNow(date, { addSuffix: true }) : format(date, fmt);
1206
+ };
1207
+ }
1208
+ function NumberCell(options) {
1209
+ const formatter = new Intl.NumberFormat(void 0, options);
1210
+ return (_item, value) => {
1211
+ if (value == null) return "\u2014";
1212
+ const num = Number(value);
1213
+ return isNaN(num) ? String(value) : formatter.format(num);
1214
+ };
1215
+ }
1216
+ function CurrencyCell(currency = "USD", locale) {
1217
+ const formatter = new Intl.NumberFormat(locale, {
1218
+ style: "currency",
1219
+ currency
1220
+ });
1221
+ return (_item, value) => {
1222
+ if (value == null) return "\u2014";
1223
+ const str = String(value).replace(/[^0-9.-]/g, "");
1224
+ const num = Number(str);
1225
+ return isNaN(num) ? String(value) : formatter.format(num);
1226
+ };
1227
+ }
1228
+ function BadgeCell(colorMap2) {
1229
+ return (_item, value) => {
1230
+ if (value == null) return "\u2014";
1231
+ const str = String(value);
1232
+ const variant = colorMap2?.[str] ?? "outline";
1233
+ return /* @__PURE__ */ jsx(Badge, { variant, children: str });
1234
+ };
1235
+ }
1236
+ var colorMap = {
1237
+ primary: "text-primary",
1238
+ success: "text-success",
1239
+ warning: "text-warning",
1240
+ info: "text-info",
1241
+ destructive: "text-destructive",
1242
+ muted: "text-muted-foreground"
1243
+ };
1244
+ function resolveColor(color) {
1245
+ return colorMap[color] ?? color;
1246
+ }
1247
+ function StatCard({
1248
+ label,
1249
+ value,
1250
+ icon: Icon,
1251
+ color = "primary",
1252
+ href,
1253
+ linkComponent: Link = DefaultLink,
1254
+ trend,
1255
+ className
1256
+ }) {
1257
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1258
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1259
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: label }),
1260
+ Icon && /* @__PURE__ */ jsx(Icon, { className: cn("h-4 w-4", resolveColor(color)) })
1261
+ ] }),
1262
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-2xl font-bold", children: value }),
1263
+ trend && /* @__PURE__ */ jsxs(
1264
+ "p",
1265
+ {
1266
+ className: cn(
1267
+ "mt-1 text-xs font-medium",
1268
+ trend.value >= 0 ? "text-primary" : "text-destructive"
1269
+ ),
1270
+ children: [
1271
+ trend.value >= 0 ? "+" : "",
1272
+ trend.value,
1273
+ "% ",
1274
+ trend.label
1275
+ ]
1276
+ }
1277
+ )
1278
+ ] });
1279
+ const cardClasses = cn(
1280
+ "rounded-xl border border-border bg-card p-4 transition-all hover:shadow-md",
1281
+ href && "cursor-pointer hover:border-primary/30",
1282
+ className
1283
+ );
1284
+ if (href) {
1285
+ return /* @__PURE__ */ jsx(Link, { href, className: cardClasses, children: content });
1286
+ }
1287
+ return /* @__PURE__ */ jsx("div", { className: cardClasses, children: content });
1288
+ }
1289
+ var statusColorMap = {
1290
+ neutral: { color: "text-muted-foreground", bgColor: "bg-muted" },
1291
+ warning: { color: "text-warning", bgColor: "bg-warning/15" },
1292
+ success: { color: "text-success", bgColor: "bg-success/15" },
1293
+ info: { color: "text-info", bgColor: "bg-info/15" },
1294
+ destructive: { color: "text-destructive", bgColor: "bg-destructive/15" },
1295
+ primary: { color: "text-primary", bgColor: "bg-primary/15" }
1296
+ };
1297
+ function KanbanBoard({
1298
+ columns,
1299
+ renderCard,
1300
+ emptyMessage = "No items",
1301
+ className
1302
+ }) {
1303
+ return /* @__PURE__ */ jsx(
1304
+ "div",
1305
+ {
1306
+ className: cn(
1307
+ "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
1308
+ className
1309
+ ),
1310
+ children: columns.map((column) => {
1311
+ const resolved = column.status ? statusColorMap[column.status] : { color: column.color ?? "", bgColor: column.bgColor ?? "" };
1312
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
1313
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1314
+ /* @__PURE__ */ jsx("span", { className: cn("text-sm font-semibold", resolved.color), children: column.label }),
1315
+ /* @__PURE__ */ jsx(
1316
+ Badge,
1317
+ {
1318
+ variant: "secondary",
1319
+ className: cn("text-[10px] px-1.5 py-0", resolved.bgColor),
1320
+ children: column.items.length
1321
+ }
1322
+ )
1323
+ ] }),
1324
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-h-[80px]", children: column.items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-lg border border-dashed border-border p-6 text-xs text-muted-foreground", children: emptyMessage }) : column.items.map((item, i) => /* @__PURE__ */ jsx("div", { className: "animate-fade-in", style: { animationDelay: `${i * 30}ms`, animationFillMode: "both" }, children: renderCard(item) }, i)) })
1325
+ ] }, column.key);
1326
+ })
1327
+ }
1328
+ );
1329
+ }
1330
+ function DataList({
1331
+ items,
1332
+ renderItem,
1333
+ emptyMessage = "No items found",
1334
+ className
1335
+ }) {
1336
+ if (items.length === 0) {
1337
+ return /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: emptyMessage });
1338
+ }
1339
+ return /* @__PURE__ */ jsx("div", { className: cn("divide-y divide-border rounded-lg border", className), children: items.map((item, i) => /* @__PURE__ */ jsx(
1340
+ "div",
1341
+ {
1342
+ className: "px-4 py-3 transition-colors hover:bg-accent/50",
1343
+ children: renderItem(item, i)
1344
+ },
1345
+ i
1346
+ )) });
1347
+ }
1348
+ var widthClasses = {
1349
+ sm: "w-80",
1350
+ md: "w-96",
1351
+ lg: "w-[480px]"
1352
+ };
1353
+ function keyToLabel(key) {
1354
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
1355
+ }
1356
+ function formatValue(value) {
1357
+ if (value === null || value === void 0) return "\u2014";
1358
+ if (typeof value === "boolean") return value ? "Yes" : "No";
1359
+ if (value instanceof Date) return value.toLocaleDateString();
1360
+ if (typeof value === "object") return JSON.stringify(value);
1361
+ return String(value);
1362
+ }
1363
+ function DetailPanel({
1364
+ item,
1365
+ open,
1366
+ onClose,
1367
+ title,
1368
+ subtitle,
1369
+ onEdit,
1370
+ onDelete,
1371
+ children,
1372
+ fields,
1373
+ width = "md"
1374
+ }) {
1375
+ if (!open || !item) return null;
1376
+ const renderContent = () => {
1377
+ if (children) {
1378
+ return children(item);
1379
+ }
1380
+ if (fields) {
1381
+ return /* @__PURE__ */ jsx("dl", { className: "space-y-0", children: fields.map((field, index) => /* @__PURE__ */ jsxs("div", { children: [
1382
+ index > 0 && /* @__PURE__ */ jsx(Separator, {}),
1383
+ /* @__PURE__ */ jsxs("div", { className: "py-3", children: [
1384
+ /* @__PURE__ */ jsx("dt", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: field.label }),
1385
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: field.render ? field.render(item[field.key], item) : formatValue(item[field.key]) })
1386
+ ] })
1387
+ ] }, field.key)) });
1388
+ }
1389
+ const entries = Object.entries(item).filter(([key]) => key !== "id");
1390
+ return /* @__PURE__ */ jsx("dl", { className: "space-y-0", children: entries.map(([key, value], index) => /* @__PURE__ */ jsxs("div", { children: [
1391
+ index > 0 && /* @__PURE__ */ jsx(Separator, {}),
1392
+ /* @__PURE__ */ jsxs("div", { className: "py-3", children: [
1393
+ /* @__PURE__ */ jsx("dt", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: keyToLabel(key) }),
1394
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: formatValue(value) })
1395
+ ] })
1396
+ ] }, key)) });
1397
+ };
1398
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1399
+ /* @__PURE__ */ jsx(
1400
+ "div",
1401
+ {
1402
+ className: "fixed inset-0 z-40 bg-black/50 sm:hidden",
1403
+ onClick: onClose,
1404
+ "aria-hidden": "true"
1405
+ }
1406
+ ),
1407
+ /* @__PURE__ */ jsxs(
1408
+ "div",
1409
+ {
1410
+ role: "dialog",
1411
+ "aria-label": title ?? "Detail panel",
1412
+ onKeyDown: (e) => {
1413
+ if (e.key === "Escape") onClose();
1414
+ },
1415
+ tabIndex: -1,
1416
+ className: cn(
1417
+ "fixed inset-y-0 right-0 z-50 flex flex-col border-l border-border bg-card shadow-xl",
1418
+ "max-w-[90vw]",
1419
+ widthClasses[width],
1420
+ "transform transition-transform duration-200 ease-in-out",
1421
+ open ? "translate-x-0" : "translate-x-full"
1422
+ ),
1423
+ children: [
1424
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3", children: [
1425
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1426
+ title && /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold truncate", children: title }),
1427
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate", children: subtitle })
1428
+ ] }),
1429
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
1430
+ onEdit && /* @__PURE__ */ jsx(
1431
+ Button,
1432
+ {
1433
+ variant: "ghost",
1434
+ size: "icon",
1435
+ className: "h-8 w-8",
1436
+ onClick: () => onEdit(item),
1437
+ "aria-label": "Edit",
1438
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
1439
+ }
1440
+ ),
1441
+ onDelete && /* @__PURE__ */ jsx(
1442
+ Button,
1443
+ {
1444
+ variant: "ghost",
1445
+ size: "icon",
1446
+ className: "h-8 w-8 text-destructive hover:text-destructive",
1447
+ onClick: () => onDelete(item),
1448
+ "aria-label": "Delete",
1449
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
1450
+ }
1451
+ ),
1452
+ /* @__PURE__ */ jsx(
1453
+ Button,
1454
+ {
1455
+ variant: "ghost",
1456
+ size: "icon",
1457
+ className: "h-8 w-8",
1458
+ onClick: onClose,
1459
+ "aria-label": "Close",
1460
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1461
+ }
1462
+ )
1463
+ ] })
1464
+ ] }),
1465
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-4 py-3", children: renderContent() })
1466
+ ]
1467
+ }
1468
+ )
1469
+ ] });
1470
+ }
1471
+ function RelationField({
1472
+ field,
1473
+ value,
1474
+ onChange,
1475
+ disabled
1476
+ }) {
1477
+ const [open, setOpen] = useState(false);
1478
+ const [createOpen, setCreateOpen] = useState(false);
1479
+ const [createValue, setCreateValue] = useState("");
1480
+ const [createError, setCreateError] = useState(null);
1481
+ const [isCreating, setIsCreating] = useState(false);
1482
+ const [extraOptions, setExtraOptions] = useState([]);
1483
+ const relation = field.relation;
1484
+ const valueField = relation.valueField ?? "id";
1485
+ const provider = useDataProvider();
1486
+ const { list } = useResource(relation.resource, {
1487
+ defaultPerPage: 100
1488
+ });
1489
+ const options = useMemo(() => {
1490
+ const base = list.data?.data ? list.data.data.map((item) => {
1491
+ const record = item;
1492
+ return {
1493
+ value: String(record[valueField] ?? ""),
1494
+ label: String(record[relation.labelField] ?? "")
1495
+ };
1496
+ }) : [];
1497
+ return [...base, ...extraOptions];
1498
+ }, [list.data?.data, valueField, relation.labelField, extraOptions]);
1499
+ const handleCreate = async () => {
1500
+ if (!createValue.trim() || isCreating) return;
1501
+ setIsCreating(true);
1502
+ try {
1503
+ const res = await provider.create(relation.resource, {
1504
+ data: { [relation.labelField]: createValue.trim() }
1505
+ });
1506
+ const record = res.data;
1507
+ const newValue = String(record[valueField] ?? "");
1508
+ const newLabel = String(record[relation.labelField] ?? "");
1509
+ setExtraOptions((prev) => [...prev, { value: newValue, label: newLabel }]);
1510
+ onChange(newValue);
1511
+ setCreateOpen(false);
1512
+ setCreateValue("");
1513
+ } catch (err) {
1514
+ console.error(`Failed to create ${relation.resource}:`, err);
1515
+ setCreateError(err instanceof Error ? err.message : "Failed to create record");
1516
+ } finally {
1517
+ setIsCreating(false);
1518
+ }
1519
+ };
1520
+ const showSearch = relation.searchable ?? options.length > 10;
1521
+ const selectedLabel = useMemo(() => {
1522
+ if (!value) return null;
1523
+ const match = options.find((opt) => opt.value === value);
1524
+ return match?.label ?? null;
1525
+ }, [value, options]);
1526
+ const isLoading = list.isLoading;
1527
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1528
+ /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
1529
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1530
+ Button,
1531
+ {
1532
+ variant: "outline",
1533
+ role: "combobox",
1534
+ "aria-expanded": open,
1535
+ className: "w-full justify-between font-normal",
1536
+ disabled: disabled || isLoading,
1537
+ children: [
1538
+ isLoading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-muted-foreground", children: [
1539
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
1540
+ "Loading..."
1541
+ ] }) : selectedLabel ? selectedLabel : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: field.placeholder ?? "Select..." }),
1542
+ /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })
1543
+ ]
1544
+ }
1545
+ ) }),
1546
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", children: /* @__PURE__ */ jsxs(Command, { children: [
1547
+ showSearch && /* @__PURE__ */ jsx(CommandInput, { placeholder: "Search..." }),
1548
+ /* @__PURE__ */ jsxs(CommandList, { children: [
1549
+ /* @__PURE__ */ jsx(CommandEmpty, { children: "No results found." }),
1550
+ /* @__PURE__ */ jsx(CommandGroup, { children: options.map((opt) => /* @__PURE__ */ jsxs(
1551
+ CommandItem,
1552
+ {
1553
+ value: opt.label,
1554
+ onSelect: () => {
1555
+ onChange(opt.value === value ? "" : opt.value);
1556
+ setOpen(false);
1557
+ },
1558
+ children: [
1559
+ /* @__PURE__ */ jsx(
1560
+ Check,
1561
+ {
1562
+ className: cn(
1563
+ "mr-2 h-4 w-4",
1564
+ value === opt.value ? "opacity-100" : "opacity-0"
1565
+ )
1566
+ }
1567
+ ),
1568
+ opt.label
1569
+ ]
1570
+ },
1571
+ opt.value
1572
+ )) }),
1573
+ relation.allowCreate && /* @__PURE__ */ jsxs(Fragment, { children: [
1574
+ /* @__PURE__ */ jsx(CommandSeparator, {}),
1575
+ /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx(
1576
+ CommandItem,
1577
+ {
1578
+ onSelect: () => {
1579
+ setOpen(false);
1580
+ setCreateOpen(true);
1581
+ },
1582
+ children: "+ Create new..."
1583
+ }
1584
+ ) })
1585
+ ] })
1586
+ ] })
1587
+ ] }) })
1588
+ ] }),
1589
+ relation.allowCreate && /* @__PURE__ */ jsx(Dialog, { open: createOpen, onOpenChange: setCreateOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-[400px]", children: [
1590
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs(DialogTitle, { children: [
1591
+ "Create new ",
1592
+ relation.resource.replace(/s$/, "")
1593
+ ] }) }),
1594
+ /* @__PURE__ */ jsx(
1595
+ Input,
1596
+ {
1597
+ placeholder: `Enter ${relation.labelField}...`,
1598
+ value: createValue,
1599
+ onChange: (e) => {
1600
+ setCreateValue(e.target.value);
1601
+ setCreateError(null);
1602
+ },
1603
+ onKeyDown: (e) => {
1604
+ if (e.key === "Enter") {
1605
+ e.preventDefault();
1606
+ void handleCreate();
1607
+ }
1608
+ },
1609
+ autoFocus: true
1610
+ }
1611
+ ),
1612
+ createError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: createError }),
1613
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1614
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setCreateOpen(false), disabled: isCreating, children: "Cancel" }),
1615
+ /* @__PURE__ */ jsx(Button, { onClick: () => void handleCreate(), disabled: isCreating || !createValue.trim(), children: isCreating ? "Creating..." : "Create" })
1616
+ ] })
1617
+ ] }) })
1618
+ ] });
1619
+ }
1620
+ var DEFAULT_MAX_SIZE = 10 * 1024 * 1024;
1621
+ function formatFileSize(bytes) {
1622
+ if (bytes < 1024) return `${bytes} B`;
1623
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1624
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1625
+ }
1626
+ function parseAcceptProp(accept) {
1627
+ if (!accept) return void 0;
1628
+ const result = {};
1629
+ const parts = accept.split(",").map((s) => s.trim());
1630
+ for (const part of parts) {
1631
+ if (part.includes("/")) {
1632
+ result[part] = [];
1633
+ } else if (part.startsWith(".")) {
1634
+ const key = "application/octet-stream";
1635
+ if (!result[key]) result[key] = [];
1636
+ result[key].push(part);
1637
+ }
1638
+ }
1639
+ return Object.keys(result).length > 0 ? result : void 0;
1640
+ }
1641
+ function FileUploadField({
1642
+ field,
1643
+ value,
1644
+ onChange,
1645
+ disabled
1646
+ }) {
1647
+ const multiple = field.multiple ?? false;
1648
+ const maxSize = field.maxSize ?? DEFAULT_MAX_SIZE;
1649
+ const isImage = field.type === "image";
1650
+ const acceptObj = useMemo(
1651
+ () => parseAcceptProp(field.accept ?? (isImage ? "image/*" : void 0)),
1652
+ [field.accept, isImage]
1653
+ );
1654
+ const [errors, setErrors] = useState([]);
1655
+ const files = useMemo(() => {
1656
+ if (!value) return [];
1657
+ return Array.isArray(value) ? value : [value];
1658
+ }, [value]);
1659
+ const [previews, setPreviews] = useState([]);
1660
+ useEffect(() => {
1661
+ const urls = files.filter((f) => f.type.startsWith("image/")).map((f) => URL.createObjectURL(f));
1662
+ setPreviews(urls);
1663
+ return () => urls.forEach((u) => URL.revokeObjectURL(u));
1664
+ }, [files]);
1665
+ const onDrop = useCallback(
1666
+ (accepted, rejected) => {
1667
+ const newErrors = [];
1668
+ for (const rej of rejected) {
1669
+ for (const err of rej.errors) {
1670
+ if (err.code === "file-too-large") {
1671
+ newErrors.push(
1672
+ `"${rej.file.name}" exceeds ${formatFileSize(maxSize)} limit`
1673
+ );
1674
+ } else if (err.code === "file-invalid-type") {
1675
+ newErrors.push(`"${rej.file.name}" is not an accepted file type`);
1676
+ } else {
1677
+ newErrors.push(`"${rej.file.name}": ${err.message}`);
1678
+ }
1679
+ }
1680
+ }
1681
+ setErrors(newErrors);
1682
+ if (accepted.length === 0) return;
1683
+ if (multiple) {
1684
+ const merged = [...files, ...accepted];
1685
+ onChange(merged);
1686
+ } else {
1687
+ onChange(accepted[0]);
1688
+ }
1689
+ },
1690
+ [files, maxSize, multiple, onChange]
1691
+ );
1692
+ const removeFile = useCallback(
1693
+ (index) => {
1694
+ const next = files.filter((_, i) => i !== index);
1695
+ if (next.length === 0) {
1696
+ onChange(null);
1697
+ } else if (multiple) {
1698
+ onChange(next);
1699
+ } else {
1700
+ onChange(null);
1701
+ }
1702
+ setErrors([]);
1703
+ },
1704
+ [files, multiple, onChange]
1705
+ );
1706
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
1707
+ onDrop,
1708
+ accept: acceptObj,
1709
+ maxSize,
1710
+ multiple,
1711
+ disabled
1712
+ });
1713
+ const hasFiles = files.length > 0;
1714
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1715
+ /* @__PURE__ */ jsxs(
1716
+ "div",
1717
+ {
1718
+ ...getRootProps(),
1719
+ className: cn(
1720
+ "flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors",
1721
+ isDragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-muted-foreground/50",
1722
+ disabled && "pointer-events-none opacity-50",
1723
+ hasFiles && "py-4"
1724
+ ),
1725
+ children: [
1726
+ /* @__PURE__ */ jsx("input", { ...getInputProps() }),
1727
+ isImage ? /* @__PURE__ */ jsx(ImageIcon, { className: "mb-2 h-8 w-8 text-muted-foreground" }) : /* @__PURE__ */ jsx(Upload, { className: "mb-2 h-8 w-8 text-muted-foreground" }),
1728
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: isDragActive ? "Drop files here..." : "Drag & drop or click to upload" }),
1729
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground/70", children: [
1730
+ "Max ",
1731
+ formatFileSize(maxSize),
1732
+ field.accept ? ` -- ${field.accept}` : ""
1733
+ ] })
1734
+ ]
1735
+ }
1736
+ ),
1737
+ errors.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1", children: errors.map((err, i) => /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-sm text-destructive", children: [
1738
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 shrink-0" }),
1739
+ err
1740
+ ] }, i)) }),
1741
+ hasFiles && /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: files.map((file, index) => {
1742
+ const isImg = file.type.startsWith("image/");
1743
+ const previewUrl = isImg ? previews[files.filter((f, i) => i <= index && f.type.startsWith("image/")).length - 1] : void 0;
1744
+ return /* @__PURE__ */ jsxs(
1745
+ "li",
1746
+ {
1747
+ className: "flex items-center gap-3 rounded-md border bg-muted/30 p-2",
1748
+ children: [
1749
+ isImg && previewUrl ? /* @__PURE__ */ jsx(
1750
+ "img",
1751
+ {
1752
+ src: previewUrl,
1753
+ alt: file.name,
1754
+ className: "h-10 w-10 rounded object-cover"
1755
+ }
1756
+ ) : /* @__PURE__ */ jsx(FileIcon, { className: "h-10 w-10 shrink-0 text-muted-foreground p-1" }),
1757
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1758
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: file.name }),
1759
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatFileSize(file.size) })
1760
+ ] }),
1761
+ /* @__PURE__ */ jsx(
1762
+ "button",
1763
+ {
1764
+ type: "button",
1765
+ onClick: (e) => {
1766
+ e.stopPropagation();
1767
+ removeFile(index);
1768
+ },
1769
+ className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
1770
+ "aria-label": `Remove ${file.name}`,
1771
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1772
+ }
1773
+ )
1774
+ ]
1775
+ },
1776
+ `${file.name}-${index}`
1777
+ );
1778
+ }) })
1779
+ ] });
1780
+ }
1781
+ function FieldArrayRenderer({
1782
+ field,
1783
+ control,
1784
+ action,
1785
+ watchedValues
1786
+ }) {
1787
+ const { fields: items, append, remove } = useFieldArray({
1788
+ control,
1789
+ name: field.key
1790
+ });
1791
+ const subFields = field.arrayFields ?? [];
1792
+ const label = field.label ?? titleCase(field.key);
1793
+ const canAdd = field.arrayMax == null || items.length < field.arrayMax;
1794
+ const canRemove = field.arrayMin == null || items.length > field.arrayMin;
1795
+ const handleAdd = () => {
1796
+ append(buildArrayItemDefaults(subFields));
1797
+ };
1798
+ return /* @__PURE__ */ jsxs(FormField, { children: [
1799
+ /* @__PURE__ */ jsx(FormLabel, { required: field.required, children: label }),
1800
+ field.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: field.description }),
1801
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: items.map((item, index) => /* @__PURE__ */ jsx(Card, { className: "relative", children: /* @__PURE__ */ jsxs(CardContent, { className: "pt-4 pb-3 px-4", children: [
1802
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
1803
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground", children: [
1804
+ "Item ",
1805
+ index + 1
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx(
1808
+ Button,
1809
+ {
1810
+ type: "button",
1811
+ variant: "ghost",
1812
+ size: "sm",
1813
+ className: "h-7 px-2 text-destructive hover:text-destructive",
1814
+ disabled: !canRemove,
1815
+ onClick: () => remove(index),
1816
+ "aria-label": `Remove item ${index + 1}`,
1817
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "h-4 w-4" })
1818
+ }
1819
+ )
1820
+ ] }),
1821
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: subFields.map((subField) => /* @__PURE__ */ jsx(
1822
+ ResourceFormFieldRenderer,
1823
+ {
1824
+ field: {
1825
+ ...subField,
1826
+ key: `${field.key}.${index}.${subField.key}`
1827
+ },
1828
+ control,
1829
+ action,
1830
+ watchedValues
1831
+ },
1832
+ subField.key
1833
+ )) })
1834
+ ] }) }, item.id)) }),
1835
+ /* @__PURE__ */ jsxs(
1836
+ Button,
1837
+ {
1838
+ type: "button",
1839
+ variant: "outline",
1840
+ size: "sm",
1841
+ className: "mt-2",
1842
+ disabled: !canAdd,
1843
+ onClick: handleAdd,
1844
+ children: [
1845
+ /* @__PURE__ */ jsx(PlusIcon, { className: "h-4 w-4 mr-1" }),
1846
+ "Add ",
1847
+ label.replace(/s$/, "")
1848
+ ]
1849
+ }
1850
+ ),
1851
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error` })
1852
+ ] });
1853
+ }
1854
+ function TrashIcon({ className }) {
1855
+ return /* @__PURE__ */ jsxs(
1856
+ "svg",
1857
+ {
1858
+ xmlns: "http://www.w3.org/2000/svg",
1859
+ viewBox: "0 0 24 24",
1860
+ fill: "none",
1861
+ stroke: "currentColor",
1862
+ strokeWidth: 2,
1863
+ strokeLinecap: "round",
1864
+ strokeLinejoin: "round",
1865
+ className,
1866
+ children: [
1867
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
1868
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
1869
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
1870
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "11", x2: "10", y2: "17" }),
1871
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "11", x2: "14", y2: "17" })
1872
+ ]
1873
+ }
1874
+ );
1875
+ }
1876
+ function PlusIcon({ className }) {
1877
+ return /* @__PURE__ */ jsxs(
1878
+ "svg",
1879
+ {
1880
+ xmlns: "http://www.w3.org/2000/svg",
1881
+ viewBox: "0 0 24 24",
1882
+ fill: "none",
1883
+ stroke: "currentColor",
1884
+ strokeWidth: 2,
1885
+ strokeLinecap: "round",
1886
+ strokeLinejoin: "round",
1887
+ className,
1888
+ children: [
1889
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1890
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1891
+ ]
1892
+ }
1893
+ );
1894
+ }
1895
+ function ResourceFormFieldRenderer({
1896
+ field,
1897
+ control,
1898
+ action,
1899
+ autoFocus,
1900
+ watchedValues
1901
+ }) {
1902
+ const label = field.label ?? titleCase(field.key);
1903
+ const fieldType = field.type ?? "text";
1904
+ const isReadOnly = action === "edit" && field.readOnlyOnEdit;
1905
+ const isDisabled = isReadOnly || (watchedValues ? field.disabled?.(watchedValues) === true : false);
1906
+ if (fieldType === "array" && field.arrayFields) {
1907
+ return /* @__PURE__ */ jsx(
1908
+ FieldArrayRenderer,
1909
+ {
1910
+ field,
1911
+ control,
1912
+ action,
1913
+ watchedValues
1914
+ }
1915
+ );
1916
+ }
1917
+ if (field.hidden || fieldType === "hidden") {
1918
+ return /* @__PURE__ */ jsx(
1919
+ Controller,
1920
+ {
1921
+ name: field.key,
1922
+ control,
1923
+ render: ({ field: f }) => /* @__PURE__ */ jsx("input", { type: "hidden", ...f, value: String(f.value ?? "") })
1924
+ }
1925
+ );
1926
+ }
1927
+ return /* @__PURE__ */ jsx(
1928
+ Controller,
1929
+ {
1930
+ name: field.key,
1931
+ control,
1932
+ render: ({ field: f, fieldState }) => {
1933
+ const errorMessage = fieldState.error?.message;
1934
+ const ariaProps = {
1935
+ "aria-invalid": !!errorMessage || void 0,
1936
+ "aria-describedby": errorMessage ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
1937
+ "aria-required": field.required || void 0
1938
+ };
1939
+ if (fieldType === "switch") {
1940
+ return /* @__PURE__ */ jsxs(FormField, { className: "flex flex-row items-center justify-between rounded-lg border p-4", children: [
1941
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }) }),
1942
+ /* @__PURE__ */ jsx(
1943
+ Switch,
1944
+ {
1945
+ checked: !!f.value,
1946
+ onCheckedChange: f.onChange,
1947
+ disabled: isDisabled,
1948
+ ...ariaProps
1949
+ }
1950
+ )
1951
+ ] });
1952
+ }
1953
+ if (fieldType === "checkbox") {
1954
+ return /* @__PURE__ */ jsxs(FormField, { className: "flex flex-row items-start space-x-3 space-y-0", children: [
1955
+ /* @__PURE__ */ jsx(
1956
+ Checkbox,
1957
+ {
1958
+ checked: !!f.value,
1959
+ onCheckedChange: f.onChange,
1960
+ disabled: isDisabled,
1961
+ ...ariaProps
1962
+ }
1963
+ ),
1964
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 leading-none", children: [
1965
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }),
1966
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
1967
+ ] })
1968
+ ] });
1969
+ }
1970
+ if (fieldType === "select" && field.options) {
1971
+ return /* @__PURE__ */ jsxs(FormField, { children: [
1972
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
1973
+ /* @__PURE__ */ jsxs(
1974
+ Select,
1975
+ {
1976
+ value: f.value != null ? String(f.value) : "",
1977
+ onValueChange: f.onChange,
1978
+ disabled: isDisabled,
1979
+ children: [
1980
+ /* @__PURE__ */ jsx(
1981
+ SelectTrigger,
1982
+ {
1983
+ ...ariaProps,
1984
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: field.placeholder ?? `Select ${label.toLowerCase()}...` })
1985
+ }
1986
+ ),
1987
+ /* @__PURE__ */ jsx(SelectContent, { children: field.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
1988
+ ]
1989
+ }
1990
+ ),
1991
+ field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
1992
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
1993
+ ] });
1994
+ }
1995
+ if (fieldType === "relation" && field.relation) {
1996
+ return /* @__PURE__ */ jsxs(FormField, { children: [
1997
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
1998
+ /* @__PURE__ */ jsx(
1999
+ RelationField,
2000
+ {
2001
+ field,
2002
+ value: f.value != null ? String(f.value) : "",
2003
+ onChange: f.onChange,
2004
+ disabled: isDisabled
2005
+ }
2006
+ ),
2007
+ field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2008
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2009
+ ] });
2010
+ }
2011
+ if (fieldType === "file" || fieldType === "image") {
2012
+ return /* @__PURE__ */ jsxs(FormField, { children: [
2013
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2014
+ /* @__PURE__ */ jsx(
2015
+ FileUploadField,
2016
+ {
2017
+ field,
2018
+ value: f.value,
2019
+ onChange: f.onChange,
2020
+ disabled: isDisabled
2021
+ }
2022
+ ),
2023
+ field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2024
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2025
+ ] });
2026
+ }
2027
+ if (fieldType === "textarea") {
2028
+ return /* @__PURE__ */ jsxs(FormField, { children: [
2029
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2030
+ /* @__PURE__ */ jsx(
2031
+ Textarea,
2032
+ {
2033
+ placeholder: field.placeholder,
2034
+ rows: field.rows ?? 3,
2035
+ value: f.value != null ? String(f.value) : "",
2036
+ onChange: (e) => f.onChange(e.target.value),
2037
+ disabled: isDisabled,
2038
+ autoFocus,
2039
+ ...ariaProps
2040
+ }
2041
+ ),
2042
+ field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2043
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2044
+ ] });
2045
+ }
2046
+ if (fieldType === "number") {
2047
+ return /* @__PURE__ */ jsxs(FormField, { children: [
2048
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2049
+ /* @__PURE__ */ jsx(
2050
+ Input,
2051
+ {
2052
+ type: "number",
2053
+ placeholder: field.placeholder,
2054
+ min: field.min,
2055
+ max: field.max,
2056
+ value: f.value != null ? String(f.value) : "",
2057
+ onChange: (e) => {
2058
+ const val = e.target.value;
2059
+ f.onChange(val === "" ? void 0 : Number(val));
2060
+ },
2061
+ disabled: isDisabled,
2062
+ autoFocus,
2063
+ ...ariaProps
2064
+ }
2065
+ ),
2066
+ field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2067
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2068
+ ] });
2069
+ }
2070
+ return /* @__PURE__ */ jsxs(FormField, { children: [
2071
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2072
+ /* @__PURE__ */ jsx(
2073
+ Input,
2074
+ {
2075
+ type: fieldType,
2076
+ placeholder: field.placeholder,
2077
+ value: f.value != null ? String(f.value) : "",
2078
+ onChange: (e) => f.onChange(e.target.value),
2079
+ disabled: isDisabled,
2080
+ autoFocus,
2081
+ ...ariaProps
2082
+ }
2083
+ ),
2084
+ field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2085
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2086
+ ] });
2087
+ }
2088
+ }
2089
+ );
2090
+ }
2091
+ function WizardForm({
2092
+ steps,
2093
+ form,
2094
+ fields,
2095
+ onSubmit,
2096
+ isSaving,
2097
+ renderField,
2098
+ action,
2099
+ onInvalid
2100
+ }) {
2101
+ const wizard = useWizard(steps, form);
2102
+ const watchedValues = useWatch({ control: form.control });
2103
+ const stepFieldKeys = new Set(wizard.currentStepDef.fields);
2104
+ const stepFields = fields.filter(
2105
+ (f) => stepFieldKeys.has(f.key) && f.visible?.(watchedValues) !== false
2106
+ );
2107
+ const handleNext = async () => {
2108
+ if (wizard.isLast) {
2109
+ await form.handleSubmit(onSubmit, onInvalid)();
2110
+ } else {
2111
+ await wizard.next();
2112
+ }
2113
+ };
2114
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
2115
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2116
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [
2117
+ /* @__PURE__ */ jsxs("span", { children: [
2118
+ "Step ",
2119
+ wizard.currentStep + 1,
2120
+ " of ",
2121
+ wizard.totalSteps
2122
+ ] }),
2123
+ /* @__PURE__ */ jsxs("span", { children: [
2124
+ Math.round(wizard.progress * 100),
2125
+ "%"
2126
+ ] })
2127
+ ] }),
2128
+ /* @__PURE__ */ jsx(Progress, { value: wizard.progress * 100 })
2129
+ ] }),
2130
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2131
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: wizard.currentStepDef.title }),
2132
+ wizard.currentStepDef.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: wizard.currentStepDef.description })
2133
+ ] }),
2134
+ /* @__PURE__ */ jsx(
2135
+ "div",
2136
+ {
2137
+ className: "animate-in fade-in slide-in-from-right-2 duration-200",
2138
+ children: /* @__PURE__ */ jsx("div", { className: "space-y-4", children: stepFields.map(
2139
+ (field) => renderField ? /* @__PURE__ */ jsx("div", { children: renderField(field, form.control, action, watchedValues) }, field.key) : /* @__PURE__ */ jsx(
2140
+ ResourceFormFieldRenderer,
2141
+ {
2142
+ field,
2143
+ control: form.control,
2144
+ action,
2145
+ watchedValues
2146
+ },
2147
+ field.key
2148
+ )
2149
+ ) })
2150
+ },
2151
+ wizard.currentStep
2152
+ ),
2153
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between pt-2", children: [
2154
+ /* @__PURE__ */ jsx(
2155
+ Button,
2156
+ {
2157
+ type: "button",
2158
+ variant: "outline",
2159
+ onClick: wizard.back,
2160
+ disabled: wizard.isFirst || isSaving,
2161
+ children: "Back"
2162
+ }
2163
+ ),
2164
+ /* @__PURE__ */ jsxs(
2165
+ Button,
2166
+ {
2167
+ type: "button",
2168
+ onClick: handleNext,
2169
+ disabled: isSaving,
2170
+ children: [
2171
+ isSaving && /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
2172
+ wizard.isLast ? action === "create" ? "Create" : "Save" : "Next"
2173
+ ]
2174
+ }
2175
+ )
2176
+ ] })
2177
+ ] });
2178
+ }
2179
+ function FormBody({
2180
+ form,
2181
+ resolvedFields,
2182
+ renderForm,
2183
+ action,
2184
+ isSaving,
2185
+ isLoading,
2186
+ onOpenChange,
2187
+ handleSave,
2188
+ mode = "standard",
2189
+ steps,
2190
+ columns = 1
2191
+ }) {
2192
+ const hasConditionalFields = resolvedFields.some((f) => f.visible || f.disabled);
2193
+ const watchedValues = useWatch({
2194
+ control: form.control,
2195
+ disabled: !hasConditionalFields
2196
+ });
2197
+ const onInvalid = (errors) => {
2198
+ const firstErrorKey = Object.keys(errors)[0];
2199
+ if (firstErrorKey) form.setFocus(firstErrorKey);
2200
+ };
2201
+ if (isLoading) {
2202
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx(Spinner, {}) });
2203
+ }
2204
+ if (mode === "wizard" && steps) {
2205
+ return /* @__PURE__ */ jsx(
2206
+ WizardForm,
2207
+ {
2208
+ steps,
2209
+ form,
2210
+ fields: resolvedFields,
2211
+ onSubmit: handleSave,
2212
+ isSaving,
2213
+ action,
2214
+ onInvalid
2215
+ }
2216
+ );
2217
+ }
2218
+ const visibleFields = resolvedFields.filter(
2219
+ (field) => field.visible?.(watchedValues) !== false
2220
+ );
2221
+ let autoFocusAssigned = false;
2222
+ const groups = [];
2223
+ const groupMap = /* @__PURE__ */ new Map();
2224
+ for (const field of visibleFields) {
2225
+ const groupKey = field.group ?? null;
2226
+ if (!groupMap.has(groupKey)) {
2227
+ const arr = [];
2228
+ groupMap.set(groupKey, arr);
2229
+ groups.push({ name: groupKey, fields: arr });
2230
+ }
2231
+ groupMap.get(groupKey).push(field);
2232
+ }
2233
+ function renderFields(fields) {
2234
+ const useGrid = columns === 2;
2235
+ return /* @__PURE__ */ jsx("div", { className: useGrid ? "grid grid-cols-2 gap-4" : "space-y-4", children: fields.map((field) => {
2236
+ const isHidden = field.hidden || field.type === "hidden";
2237
+ const shouldAutoFocus = !isHidden && !autoFocusAssigned;
2238
+ if (shouldAutoFocus) autoFocusAssigned = true;
2239
+ const autoSpan = useGrid && (field.type === "textarea" || field.type === "array") ? 2 : void 0;
2240
+ const span = useGrid ? field.colSpan ?? autoSpan ?? 1 : void 0;
2241
+ return /* @__PURE__ */ jsx("div", { className: span === 2 ? "col-span-2" : "", children: /* @__PURE__ */ jsx(
2242
+ ResourceFormFieldRenderer,
2243
+ {
2244
+ field,
2245
+ control: form.control,
2246
+ action,
2247
+ autoFocus: shouldAutoFocus,
2248
+ watchedValues
2249
+ }
2250
+ ) }, field.key);
2251
+ }) });
2252
+ }
2253
+ return /* @__PURE__ */ jsxs(
2254
+ "form",
2255
+ {
2256
+ onSubmit: (e) => {
2257
+ e.preventDefault();
2258
+ void form.handleSubmit(handleSave, onInvalid)(e);
2259
+ },
2260
+ className: "space-y-4",
2261
+ children: [
2262
+ /* @__PURE__ */ jsx("div", { className: "max-h-[60vh] overflow-y-auto px-1", children: renderForm ? renderForm(form, visibleFields) : /* @__PURE__ */ jsx("div", { className: "space-y-4", children: groups.map((group, idx) => /* @__PURE__ */ jsxs("div", { children: [
2263
+ group.name && /* @__PURE__ */ jsxs("div", { className: "pt-2", children: [
2264
+ idx > 0 && /* @__PURE__ */ jsx("div", { className: "border-t mb-3" }),
2265
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-muted-foreground mb-3", children: group.name })
2266
+ ] }),
2267
+ renderFields(group.fields)
2268
+ ] }, group.name ?? "__default")) }) }),
2269
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2270
+ /* @__PURE__ */ jsx(
2271
+ Button,
2272
+ {
2273
+ type: "button",
2274
+ variant: "outline",
2275
+ onClick: () => onOpenChange(false),
2276
+ disabled: isSaving,
2277
+ children: "Cancel"
2278
+ }
2279
+ ),
2280
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: isSaving, children: [
2281
+ isSaving && /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }),
2282
+ action === "create" ? "Create" : "Save"
2283
+ ] })
2284
+ ] })
2285
+ ]
2286
+ }
2287
+ );
2288
+ }
2289
+ function ResourceForm(props) {
2290
+ const {
2291
+ resource,
2292
+ action,
2293
+ item,
2294
+ open,
2295
+ onOpenChange,
2296
+ title: titleProp,
2297
+ fields: fieldsProp,
2298
+ listData,
2299
+ renderForm,
2300
+ onSuccess,
2301
+ onError,
2302
+ layout: layoutProp = "dialog",
2303
+ mode = "standard",
2304
+ steps,
2305
+ columns = 1
2306
+ } = props;
2307
+ const isMobile = useIsMobile();
2308
+ const effectiveLayout = isMobile ? "sheet" : layoutProp;
2309
+ const resourceLabel = titleCase(resource.replace(/s$/, ""));
2310
+ const formTitle = titleProp ?? (action === "create" ? `Create ${resourceLabel}` : `Edit ${resourceLabel}`);
2311
+ const formDescription = `${action === "create" ? "Fill in the details to create a new" : "Update the"} ${resourceLabel.toLowerCase()} record.`;
2312
+ const hookOptions = {
2313
+ resource,
2314
+ action,
2315
+ id: item ? String(item.id ?? "") : void 0,
2316
+ initialData: item ?? void 0,
2317
+ fields: fieldsProp,
2318
+ listData,
2319
+ onSuccess: (data) => {
2320
+ onOpenChange(false);
2321
+ onSuccess?.(data);
2322
+ },
2323
+ onError
2324
+ };
2325
+ const { form, resolvedFields, handleSave, isSaving, isLoading } = useResourceForm(hookOptions);
2326
+ const bodyProps = {
2327
+ form,
2328
+ resolvedFields,
2329
+ renderForm,
2330
+ action,
2331
+ isSaving,
2332
+ isLoading,
2333
+ onOpenChange,
2334
+ handleSave,
2335
+ mode,
2336
+ steps,
2337
+ columns
2338
+ };
2339
+ if (effectiveLayout === "dialog") {
2340
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-[560px]", children: [
2341
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2342
+ /* @__PURE__ */ jsx(DialogTitle, { children: formTitle }),
2343
+ /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: formDescription })
2344
+ ] }),
2345
+ /* @__PURE__ */ jsx(FormBody, { ...bodyProps })
2346
+ ] }) });
2347
+ }
2348
+ if (effectiveLayout === "sheet") {
2349
+ return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(SheetContent, { side: "right", className: "sm:max-w-[480px] overflow-y-auto", children: [
2350
+ /* @__PURE__ */ jsxs(SheetHeader, { children: [
2351
+ /* @__PURE__ */ jsx(SheetTitle, { children: formTitle }),
2352
+ /* @__PURE__ */ jsx(SheetDescription, { className: "sr-only", children: formDescription })
2353
+ ] }),
2354
+ /* @__PURE__ */ jsx(FormBody, { ...bodyProps })
2355
+ ] }) });
2356
+ }
2357
+ if (!open) return null;
2358
+ return /* @__PURE__ */ jsxs(Card, { className: "mx-auto max-w-[560px]", children: [
2359
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: formTitle }) }),
2360
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(FormBody, { ...bodyProps }) })
2361
+ ] });
2362
+ }
2363
+ function ResourcePage(props) {
2364
+ const {
2365
+ resource: resourceName,
2366
+ data: dataProp,
2367
+ total: totalProp,
2368
+ title,
2369
+ subtitle,
2370
+ icon,
2371
+ columns: columnsProp,
2372
+ detail = "panel",
2373
+ detailWidth = "md",
2374
+ titleField: titleFieldProp,
2375
+ subtitleField: subtitleFieldProp,
2376
+ renderDetail: renderDetailProp,
2377
+ detailFields,
2378
+ statusFilter,
2379
+ searchFields,
2380
+ searchPlaceholder,
2381
+ initialFilters,
2382
+ onFiltersChange,
2383
+ onCreate,
2384
+ onEdit,
2385
+ onDelete,
2386
+ createLabel = "New",
2387
+ allowCreate,
2388
+ allowEdit,
2389
+ formFields,
2390
+ renderForm: renderFormProp,
2391
+ formMode,
2392
+ formSteps,
2393
+ formLayout: formLayoutProp,
2394
+ bulkActions,
2395
+ toolbar,
2396
+ emptyState,
2397
+ resourceOptions,
2398
+ defaultView = "table",
2399
+ onNavigate = defaultNavigate
2400
+ } = props;
2401
+ const isClientMode = !!resourceName;
2402
+ const resourceHook = useResource(
2403
+ resourceName ?? "__noop__",
2404
+ isClientMode ? resourceOptions : { enabled: false }
2405
+ );
2406
+ const displayData = isClientMode ? resourceHook.list.data?.data ?? [] : dataProp ?? [];
2407
+ const displayTotal = isClientMode ? resourceHook.list.data?.total ?? 0 : totalProp ?? 0;
2408
+ const isLoading = isClientMode ? resourceHook.list.isLoading : false;
2409
+ const hasError = isClientMode ? resourceHook.list.isError : false;
2410
+ const page = useResourcePage({
2411
+ resource: resourceName,
2412
+ data: displayData,
2413
+ total: displayTotal,
2414
+ columns: columnsProp,
2415
+ statusFilter,
2416
+ searchFields,
2417
+ initialFilters,
2418
+ onFiltersChange,
2419
+ defaultView,
2420
+ detail,
2421
+ titleField: titleFieldProp,
2422
+ subtitleField: subtitleFieldProp
2423
+ });
2424
+ const actions = useMemo(() => {
2425
+ if (isClientMode) {
2426
+ return {
2427
+ update: resourceHook.update,
2428
+ remove: resourceHook.remove,
2429
+ refetch: () => resourceHook.list.refetch()
2430
+ };
2431
+ }
2432
+ return {
2433
+ update: async () => {
2434
+ throw new Error("ResourceActions.update not available in server-data mode");
2435
+ },
2436
+ remove: async () => {
2437
+ throw new Error("ResourceActions.remove not available in server-data mode");
2438
+ },
2439
+ refetch: () => {
2440
+ }
2441
+ };
2442
+ }, [isClientMode, resourceHook.update, resourceHook.remove, resourceHook.list]);
2443
+ const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
2444
+ const handleToggleSelect = useCallback((id) => {
2445
+ setSelectedIds((prev) => {
2446
+ const next = new Set(prev);
2447
+ if (next.has(id)) next.delete(id);
2448
+ else next.add(id);
2449
+ return next;
2450
+ });
2451
+ }, []);
2452
+ const handleToggleAll = useCallback(() => {
2453
+ setSelectedIds((prev) => {
2454
+ if (prev.size === page.data.length) return /* @__PURE__ */ new Set();
2455
+ return new Set(
2456
+ page.data.map(
2457
+ (item) => String(item.id ?? "")
2458
+ )
2459
+ );
2460
+ });
2461
+ }, [page.data]);
2462
+ const handleClearSelection = useCallback(() => {
2463
+ setSelectedIds(/* @__PURE__ */ new Set());
2464
+ }, []);
2465
+ const [formOpen, setFormOpen] = useState(false);
2466
+ const [formAction, setFormAction] = useState("create");
2467
+ const [formItem, setFormItem] = useState(null);
2468
+ const useBuiltInCreate = allowCreate && !onCreate && isClientMode;
2469
+ const useBuiltInEdit = allowEdit && !onEdit && isClientMode;
2470
+ const handleCreate = useCallback(() => {
2471
+ if (useBuiltInCreate) {
2472
+ setFormAction("create");
2473
+ setFormItem(null);
2474
+ setFormOpen(true);
2475
+ } else if (onCreate) {
2476
+ onCreate();
2477
+ }
2478
+ }, [useBuiltInCreate, onCreate]);
2479
+ const handleEdit = useCallback(
2480
+ (item) => {
2481
+ if (useBuiltInEdit) {
2482
+ setFormAction("edit");
2483
+ setFormItem(item);
2484
+ setFormOpen(true);
2485
+ } else if (onEdit) {
2486
+ onEdit(item);
2487
+ }
2488
+ },
2489
+ [useBuiltInEdit, onEdit]
2490
+ );
2491
+ const tableColumns = useMemo(() => {
2492
+ if (!isClientMode) return page.tableColumns;
2493
+ return page.columns.map((col) => ({
2494
+ key: col.key,
2495
+ header: col.header ?? col.key,
2496
+ sortable: col.sortable ?? true,
2497
+ hideBelow: col.hideBelow,
2498
+ render: col.render ? (item, value) => col.render(item, value, actions) : void 0
2499
+ }));
2500
+ }, [isClientMode, page.columns, page.tableColumns, actions]);
2501
+ const detailTitle = page.selectedItem ? String(
2502
+ page.selectedItem[page.titleField] ?? ""
2503
+ ) : "";
2504
+ const detailSubtitle = page.selectedItem && page.subtitleField ? String(
2505
+ page.selectedItem[page.subtitleField] ?? ""
2506
+ ) : void 0;
2507
+ const handleRowClick = (item) => {
2508
+ if (detail === "route") {
2509
+ const id = item.id;
2510
+ if (resourceName && id) {
2511
+ onNavigate(`/${resourceName}/${id}`);
2512
+ }
2513
+ return;
2514
+ }
2515
+ page.handleRowClick(item);
2516
+ };
2517
+ const showCreateButton = onCreate || useBuiltInCreate;
2518
+ const createButton = showCreateButton ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: handleCreate, children: [
2519
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
2520
+ createLabel
2521
+ ] }) : null;
2522
+ const createAction = toolbar || createButton ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2523
+ toolbar,
2524
+ createButton
2525
+ ] }) : void 0;
2526
+ const isEmpty = !isLoading && !hasError && page.data.length === 0;
2527
+ const modalDetailContent = page.selectedItem ? renderDetailProp ? renderDetailProp(page.selectedItem) : detailFields ? /* @__PURE__ */ jsx("dl", { className: "space-y-3", children: detailFields.map((field) => /* @__PURE__ */ jsxs("div", { children: [
2528
+ /* @__PURE__ */ jsx("dt", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: field.label }),
2529
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: field.render ? field.render(
2530
+ page.selectedItem[field.key],
2531
+ page.selectedItem
2532
+ ) : String(
2533
+ page.selectedItem[field.key] ?? "\u2014"
2534
+ ) })
2535
+ ] }, field.key)) }) : null : null;
2536
+ return /* @__PURE__ */ jsxs(
2537
+ DataPageShell,
2538
+ {
2539
+ title,
2540
+ subtitle,
2541
+ icon,
2542
+ count: page.data.length,
2543
+ totalCount: displayTotal,
2544
+ isLoading,
2545
+ hasError,
2546
+ isEmpty,
2547
+ onRetry: isClientMode ? () => resourceHook.list.refetch() : void 0,
2548
+ viewMode: page.viewMode,
2549
+ onViewModeChange: page.setViewMode,
2550
+ createAction,
2551
+ hasActiveFilters: page.hasActiveFilters,
2552
+ onClearFilters: page.clearFilters,
2553
+ emptyAction: emptyState,
2554
+ filters: /* @__PURE__ */ jsx(
2555
+ DataFilters,
2556
+ {
2557
+ search: page.filterConfig.search,
2558
+ onSearchChange: page.filterConfig.onSearchChange,
2559
+ searchPlaceholder,
2560
+ sort: page.filterConfig.sort,
2561
+ onSortChange: page.filterConfig.onSortChange,
2562
+ sortOptions: page.filterConfig.sortOptions,
2563
+ statusChips: page.filterConfig.statusChips,
2564
+ onClearAll: page.hasActiveFilters ? page.clearFilters : void 0
2565
+ }
2566
+ ),
2567
+ children: [
2568
+ bulkActions && bulkActions.length > 0 && /* @__PURE__ */ jsx(
2569
+ DataBulkBar,
2570
+ {
2571
+ selected: selectedIds,
2572
+ totalCount: page.data.length,
2573
+ onToggleAll: handleToggleAll,
2574
+ onClearSelection: handleClearSelection,
2575
+ actions: bulkActions.map((a) => ({
2576
+ label: a.label,
2577
+ icon: a.icon ?? Trash2,
2578
+ variant: a.variant,
2579
+ onClick: () => {
2580
+ void a.onAction(Array.from(selectedIds));
2581
+ }
2582
+ }))
2583
+ }
2584
+ ),
2585
+ /* @__PURE__ */ jsx(
2586
+ DataGrid,
2587
+ {
2588
+ items: page.data,
2589
+ viewMode: page.viewMode,
2590
+ getKey: (item) => String(item.id ?? ""),
2591
+ renderTable: (items) => /* @__PURE__ */ jsx(
2592
+ DataTable,
2593
+ {
2594
+ items,
2595
+ columns: tableColumns,
2596
+ getKey: (item) => String(item.id ?? ""),
2597
+ onRowClick: detail !== "none" ? handleRowClick : void 0,
2598
+ selectable: !!bulkActions && bulkActions.length > 0,
2599
+ selected: selectedIds,
2600
+ onToggleSelect: handleToggleSelect,
2601
+ onToggleAll: handleToggleAll
2602
+ }
2603
+ ),
2604
+ renderCard: (item) => {
2605
+ const cols = page.columns;
2606
+ const titleCol = cols[0];
2607
+ const titleVal = titleCol ? String(item[titleCol.key] ?? "") : "";
2608
+ const badgeCol = cols[1];
2609
+ const badgeVal = badgeCol ? item[badgeCol.key] : null;
2610
+ const priceCol = cols.find(
2611
+ (c, i) => i > 1 && /price|cost|amount|total/i.test(c.key)
2612
+ );
2613
+ const priceVal = priceCol ? item[priceCol.key] : null;
2614
+ const metaCols = cols.filter(
2615
+ (c) => c !== titleCol && c !== badgeCol && c !== priceCol
2616
+ );
2617
+ return /* @__PURE__ */ jsxs(
2618
+ Card,
2619
+ {
2620
+ className: "h-full cursor-pointer hover:shadow-md transition-shadow",
2621
+ onClick: () => handleRowClick(item),
2622
+ children: [
2623
+ /* @__PURE__ */ jsx(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base truncate", children: titleVal }) }),
2624
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2625
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2626
+ badgeCol && /* @__PURE__ */ jsx("span", { children: badgeCol.render ? badgeCol.render(item, badgeVal, actions) : badgeVal != null ? String(badgeVal) : "\u2014" }),
2627
+ priceVal != null && /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: String(priceVal) })
2628
+ ] }),
2629
+ metaCols.map((col) => {
2630
+ const val = item[col.key];
2631
+ return /* @__PURE__ */ jsx(
2632
+ "p",
2633
+ {
2634
+ className: "text-xs text-muted-foreground truncate",
2635
+ children: col.render ? col.render(item, val, actions) : val != null ? String(val) : "\u2014"
2636
+ },
2637
+ col.key
2638
+ );
2639
+ })
2640
+ ] }) })
2641
+ ]
2642
+ }
2643
+ );
2644
+ },
2645
+ renderListItem: (item) => {
2646
+ const cols = page.columns;
2647
+ const titleCol = cols[0];
2648
+ const titleVal = titleCol ? String(item[titleCol.key] ?? "") : "";
2649
+ return /* @__PURE__ */ jsxs(
2650
+ "div",
2651
+ {
2652
+ className: "flex items-center justify-between px-3 py-2 border-b cursor-pointer hover:bg-muted/50 transition-colors",
2653
+ onClick: () => handleRowClick(item),
2654
+ children: [
2655
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-sm truncate", children: titleVal }),
2656
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: cols.slice(1, 4).map((col) => {
2657
+ const val = item[col.key];
2658
+ return /* @__PURE__ */ jsx("span", { children: col.render ? col.render(item, val, actions) : val != null ? String(val) : "\u2014" }, col.key);
2659
+ }) })
2660
+ ]
2661
+ }
2662
+ );
2663
+ }
2664
+ }
2665
+ ),
2666
+ detail === "panel" && /* @__PURE__ */ jsx(
2667
+ DetailPanel,
2668
+ {
2669
+ item: page.selectedItem,
2670
+ open: page.detailOpen,
2671
+ onClose: () => {
2672
+ page.setDetailOpen(false);
2673
+ page.setSelectedItem(null);
2674
+ },
2675
+ title: detailTitle,
2676
+ subtitle: detailSubtitle,
2677
+ onEdit: onEdit ?? (useBuiltInEdit ? handleEdit : void 0),
2678
+ onDelete,
2679
+ fields: detailFields,
2680
+ width: detailWidth,
2681
+ children: renderDetailProp ? (item) => renderDetailProp(item) : void 0
2682
+ }
2683
+ ),
2684
+ detail === "modal" && /* @__PURE__ */ jsx(
2685
+ Dialog,
2686
+ {
2687
+ open: page.detailOpen,
2688
+ onOpenChange: (open) => {
2689
+ page.setDetailOpen(open);
2690
+ if (!open) page.setSelectedItem(null);
2691
+ },
2692
+ children: /* @__PURE__ */ jsxs(DialogContent, { children: [
2693
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2694
+ /* @__PURE__ */ jsx(DialogTitle, { children: detailTitle }),
2695
+ detailSubtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: detailSubtitle })
2696
+ ] }),
2697
+ /* @__PURE__ */ jsxs("div", { className: "py-4", children: [
2698
+ modalDetailContent,
2699
+ (onEdit || useBuiltInEdit || onDelete) && page.selectedItem && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-4 pt-4 border-t", children: [
2700
+ (onEdit || useBuiltInEdit) && /* @__PURE__ */ jsx(
2701
+ Button,
2702
+ {
2703
+ variant: "outline",
2704
+ size: "sm",
2705
+ onClick: () => handleEdit(page.selectedItem),
2706
+ children: "Edit"
2707
+ }
2708
+ ),
2709
+ onDelete && /* @__PURE__ */ jsx(
2710
+ Button,
2711
+ {
2712
+ variant: "destructive",
2713
+ size: "sm",
2714
+ onClick: () => onDelete(page.selectedItem),
2715
+ children: "Delete"
2716
+ }
2717
+ )
2718
+ ] })
2719
+ ] })
2720
+ ] })
2721
+ }
2722
+ ),
2723
+ (useBuiltInCreate || useBuiltInEdit) && resourceName && /* @__PURE__ */ jsx(
2724
+ ResourceForm,
2725
+ {
2726
+ resource: resourceName,
2727
+ action: formAction,
2728
+ layout: formLayoutProp ?? (formAction === "edit" ? "sheet" : "dialog"),
2729
+ mode: formMode,
2730
+ steps: formSteps,
2731
+ item: formAction === "edit" ? formItem ?? void 0 : page.data.length > 0 ? Object.fromEntries(
2732
+ Object.keys(page.data[0]).filter((k) => k !== "id").map((k) => [k, ""])
2733
+ ) : void 0,
2734
+ open: formOpen,
2735
+ onOpenChange: setFormOpen,
2736
+ fields: formFields,
2737
+ listData: page.data,
2738
+ renderForm: renderFormProp,
2739
+ onSuccess: () => {
2740
+ page.setDetailOpen(false);
2741
+ page.setSelectedItem(null);
2742
+ }
2743
+ }
2744
+ )
2745
+ ]
2746
+ }
2747
+ );
2748
+ }
2749
+ function ThemeToggle() {
2750
+ const [dark, setDark] = useState(false);
2751
+ useEffect(() => {
2752
+ const stored = localStorage.getItem("theme");
2753
+ const prefersDark = window.matchMedia(
2754
+ "(prefers-color-scheme: dark)"
2755
+ ).matches;
2756
+ const isDark = stored === "dark" || !stored && prefersDark;
2757
+ setDark(isDark);
2758
+ document.documentElement.classList.toggle("dark", isDark);
2759
+ }, []);
2760
+ function toggle() {
2761
+ const next = !dark;
2762
+ setDark(next);
2763
+ document.documentElement.classList.toggle("dark", next);
2764
+ localStorage.setItem("theme", next ? "dark" : "light");
2765
+ }
2766
+ return /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", onClick: toggle, className: "h-8 w-8", children: dark ? /* @__PURE__ */ jsx(Sun, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Moon, { className: "h-4 w-4" }) });
2767
+ }
2768
+ function SidebarNav({
2769
+ navItems,
2770
+ onNavClick,
2771
+ linkComponent: LinkComp,
2772
+ activePath,
2773
+ isActive
2774
+ }) {
2775
+ return /* @__PURE__ */ jsx("nav", { className: "flex-1 p-3 space-y-1", children: navItems.map((item) => {
2776
+ const active = isActive(item.to, activePath);
2777
+ return /* @__PURE__ */ jsxs(
2778
+ LinkComp,
2779
+ {
2780
+ href: item.to,
2781
+ onClick: onNavClick,
2782
+ "aria-current": active ? "page" : void 0,
2783
+ className: cn(
2784
+ "flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
2785
+ active ? "bg-sidebar-accent text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
2786
+ ),
2787
+ children: [
2788
+ /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4" }),
2789
+ item.label
2790
+ ]
2791
+ },
2792
+ item.to
2793
+ );
2794
+ }) });
2795
+ }
2796
+ function SidebarLogo({
2797
+ appName,
2798
+ appLogo
2799
+ }) {
2800
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2801
+ appLogo && /* @__PURE__ */ jsx("div", { className: "h-7 w-7 rounded-lg bg-primary flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-primary-foreground text-sm font-bold", children: appLogo }) }),
2802
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-sidebar-foreground", children: appName })
2803
+ ] });
2804
+ }
2805
+ function AppShell({
2806
+ appName,
2807
+ appLogo,
2808
+ navItems,
2809
+ children,
2810
+ showThemeToggle = true,
2811
+ linkComponent = DefaultLink,
2812
+ activePath,
2813
+ isActive = isPathActive
2814
+ }) {
2815
+ const [mobileOpen, setMobileOpen] = useState(false);
2816
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-screen overflow-hidden", children: [
2817
+ /* @__PURE__ */ jsxs("aside", { className: "hidden md:flex md:w-60 flex-col border-r border-sidebar-border bg-sidebar", children: [
2818
+ /* @__PURE__ */ jsx("div", { className: "flex h-14 items-center gap-2 px-4 border-b border-sidebar-border", children: /* @__PURE__ */ jsx(SidebarLogo, { appName, appLogo }) }),
2819
+ /* @__PURE__ */ jsx(
2820
+ SidebarNav,
2821
+ {
2822
+ navItems,
2823
+ linkComponent,
2824
+ activePath,
2825
+ isActive
2826
+ }
2827
+ ),
2828
+ showThemeToggle && /* @__PURE__ */ jsx("div", { className: "border-t border-sidebar-border p-3 flex items-center justify-end", children: /* @__PURE__ */ jsx(ThemeToggle, {}) })
2829
+ ] }),
2830
+ mobileOpen && /* @__PURE__ */ jsx(
2831
+ "div",
2832
+ {
2833
+ className: "fixed inset-0 z-40 bg-black/50 md:hidden",
2834
+ onClick: () => setMobileOpen(false)
2835
+ }
2836
+ ),
2837
+ /* @__PURE__ */ jsxs(
2838
+ "aside",
2839
+ {
2840
+ className: cn(
2841
+ "fixed inset-y-0 left-0 z-50 w-60 flex flex-col border-r border-sidebar-border bg-sidebar transition-transform md:hidden",
2842
+ mobileOpen ? "translate-x-0" : "-translate-x-full"
2843
+ ),
2844
+ children: [
2845
+ /* @__PURE__ */ jsxs("div", { className: "flex h-14 items-center justify-between px-4 border-b border-sidebar-border", children: [
2846
+ /* @__PURE__ */ jsx(SidebarLogo, { appName, appLogo }),
2847
+ /* @__PURE__ */ jsx(
2848
+ Button,
2849
+ {
2850
+ variant: "ghost",
2851
+ size: "icon",
2852
+ onClick: () => setMobileOpen(false),
2853
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
2854
+ }
2855
+ )
2856
+ ] }),
2857
+ /* @__PURE__ */ jsx(
2858
+ SidebarNav,
2859
+ {
2860
+ navItems,
2861
+ onNavClick: () => setMobileOpen(false),
2862
+ linkComponent,
2863
+ activePath,
2864
+ isActive
2865
+ }
2866
+ )
2867
+ ]
2868
+ }
2869
+ ),
2870
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
2871
+ /* @__PURE__ */ jsxs("header", { className: "flex h-14 items-center gap-3 border-b px-4 md:hidden", children: [
2872
+ /* @__PURE__ */ jsx(
2873
+ Button,
2874
+ {
2875
+ variant: "ghost",
2876
+ size: "icon",
2877
+ onClick: () => setMobileOpen(true),
2878
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
2879
+ }
2880
+ ),
2881
+ /* @__PURE__ */ jsx("span", { className: "flex-1 font-semibold", children: appName }),
2882
+ showThemeToggle && /* @__PURE__ */ jsx(ThemeToggle, {})
2883
+ ] }),
2884
+ /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-y-auto pb-16 md:pb-0", children: /* @__PURE__ */ jsx("div", { className: "p-4 sm:p-6 lg:p-8", children }) }),
2885
+ /* @__PURE__ */ jsx("nav", { className: "fixed bottom-0 left-0 right-0 z-30 flex border-t bg-background md:hidden", children: navItems.map((item) => {
2886
+ const LinkComp = linkComponent;
2887
+ const active = isActive(item.to, activePath);
2888
+ return /* @__PURE__ */ jsxs(
2889
+ LinkComp,
2890
+ {
2891
+ href: item.to,
2892
+ "aria-current": active ? "page" : void 0,
2893
+ className: cn(
2894
+ "flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium transition-colors",
2895
+ active ? "text-primary" : "text-muted-foreground"
2896
+ ),
2897
+ children: [
2898
+ /* @__PURE__ */ jsx(item.icon, { className: "h-5 w-5" }),
2899
+ item.label
2900
+ ]
2901
+ },
2902
+ item.to
2903
+ );
2904
+ }) })
2905
+ ] })
2906
+ ] });
2907
+ }
2908
+ function PageHeader({ title, subtitle, icon: Icon, actions }) {
2909
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
2910
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
2911
+ Icon && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 overflow-hidden", children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-primary" }) }),
2912
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2913
+ /* @__PURE__ */ jsx("h1", { className: "text-xl sm:text-2xl font-bold font-display tracking-tight truncate", children: title }),
2914
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground truncate", children: subtitle })
2915
+ ] })
2916
+ ] }),
2917
+ actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 shrink-0", children: actions })
2918
+ ] });
2919
+ }
2920
+
2921
+ export { AppShell, BadgeCell, CurrencyCell, DataBulkBar, DataFilters, DataGrid, DataList, DataPageShell, DataTable, DateCell, DetailPanel, EmptyState, FieldArrayRenderer, FileUploadField, FilterCombobox, KanbanBoard, LinkCell, NumberCell, PageHeader, RelationField, ResourceForm, ResourcePage, StatCard, ThumbnailCell, ViewSettings, ViewToggle, WizardForm };
2922
+ //# sourceMappingURL=index.js.map
2923
+ //# sourceMappingURL=index.js.map