@mohasinac/appkit 2.7.11 → 2.7.13

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 (152) hide show
  1. package/dist/_internal/server/jobs/core/assignSpinPrize.js +8 -7
  2. package/dist/_internal/server/jobs/core/bundleStockSync.js +17 -20
  3. package/dist/_internal/server/jobs/core/countersReconcile.js +3 -2
  4. package/dist/_internal/server/jobs/core/onProductStockChange.js +18 -20
  5. package/dist/_internal/server/jobs/core/onProductWrite.js +3 -2
  6. package/dist/_internal/server/jobs/core/prizeRevealClose.js +7 -5
  7. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +11 -6
  8. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +13 -7
  9. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +9 -4
  10. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +3 -2
  11. package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
  12. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +3 -6
  13. package/dist/_internal/shared/features/categories/bundle-copy.js +3 -6
  14. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
  15. package/dist/_internal/shared/features/events/schema.d.ts +4 -4
  16. package/dist/_internal/shared/features/products/schema.d.ts +20 -20
  17. package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
  18. package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
  19. package/dist/constants/field-names.d.ts +824 -0
  20. package/dist/constants/field-names.js +929 -0
  21. package/dist/constants/sort.d.ts +7 -0
  22. package/dist/constants/sort.js +5 -0
  23. package/dist/constants/table-keys.d.ts +48 -0
  24. package/dist/constants/table-keys.js +46 -0
  25. package/dist/features/account/components/AddressesIndexListing.js +9 -6
  26. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
  27. package/dist/features/admin/components/AdminBidsView.js +4 -1
  28. package/dist/features/admin/components/AdminBlogView.js +9 -2
  29. package/dist/features/admin/components/AdminBrandsView.js +9 -2
  30. package/dist/features/admin/components/AdminBundlesView.js +0 -1
  31. package/dist/features/admin/components/AdminCarouselView.js +4 -1
  32. package/dist/features/admin/components/AdminCartsView.js +4 -1
  33. package/dist/features/admin/components/AdminCategoriesView.js +9 -2
  34. package/dist/features/admin/components/AdminContactView.js +4 -1
  35. package/dist/features/admin/components/AdminCouponsView.js +4 -1
  36. package/dist/features/admin/components/AdminFaqsView.js +5 -1
  37. package/dist/features/admin/components/AdminFeaturesView.js +5 -2
  38. package/dist/features/admin/components/AdminHistoryView.js +5 -1
  39. package/dist/features/admin/components/AdminNewsletterView.js +4 -1
  40. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  41. package/dist/features/admin/components/AdminOrdersView.js +14 -12
  42. package/dist/features/admin/components/AdminPayoutsView.js +4 -1
  43. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
  44. package/dist/features/admin/components/AdminProductsView.js +14 -3
  45. package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
  46. package/dist/features/admin/components/AdminReviewsView.js +4 -1
  47. package/dist/features/admin/components/AdminScammersView.js +4 -1
  48. package/dist/features/admin/components/AdminSessionsView.js +4 -1
  49. package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
  50. package/dist/features/admin/components/AdminStoresView.js +10 -3
  51. package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
  52. package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
  53. package/dist/features/admin/components/AdminTeamView.js +4 -1
  54. package/dist/features/admin/components/AdminUsersView.js +9 -12
  55. package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
  56. package/dist/features/admin/components/AdminViewCards.js +53 -0
  57. package/dist/features/admin/components/AdminWishlistsView.js +4 -1
  58. package/dist/features/auctions/repository/bid.repository.js +1 -6
  59. package/dist/features/auctions/schemas/index.d.ts +4 -4
  60. package/dist/features/blog/actions/blog-actions.d.ts +16 -16
  61. package/dist/features/blog/components/BlogFilters.d.ts +28 -28
  62. package/dist/features/blog/components/BlogFilters.js +18 -15
  63. package/dist/features/blog/components/BlogIndexListing.js +20 -16
  64. package/dist/features/blog/schemas/index.d.ts +10 -10
  65. package/dist/features/categories/components/BundleDetailView.js +0 -1
  66. package/dist/features/categories/components/CategoriesIndexListing.js +25 -21
  67. package/dist/features/categories/components/CategoryBundlesListing.js +2 -4
  68. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  69. package/dist/features/categories/schemas/index.d.ts +2 -2
  70. package/dist/features/events/components/AdminEventsView.js +5 -1
  71. package/dist/features/events/components/EventFilters.d.ts +24 -24
  72. package/dist/features/events/components/EventFilters.js +16 -13
  73. package/dist/features/events/components/EventStatusBadge.js +1 -0
  74. package/dist/features/events/components/EventsIndexListing.js +31 -26
  75. package/dist/features/events/components/EventsListPageView.js +13 -10
  76. package/dist/features/events/schemas/firestore.d.ts +1 -0
  77. package/dist/features/events/schemas/firestore.js +1 -0
  78. package/dist/features/events/schemas/index.d.ts +1 -1
  79. package/dist/features/events/types/index.d.ts +1 -1
  80. package/dist/features/faq/actions/faq-actions.d.ts +10 -10
  81. package/dist/features/faq/schemas/index.d.ts +4 -4
  82. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -3
  83. package/dist/features/orders/components/OrderFilters.d.ts +20 -20
  84. package/dist/features/orders/components/OrderFilters.js +33 -30
  85. package/dist/features/orders/repository/orders.repository.js +1 -15
  86. package/dist/features/orders/schemas/index.d.ts +2 -2
  87. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +35 -31
  88. package/dist/features/pre-orders/repository/preorders.repository.js +8 -4
  89. package/dist/features/pre-orders/schemas/index.d.ts +3 -3
  90. package/dist/features/products/components/AuctionsIndexListing.js +34 -30
  91. package/dist/features/products/components/BidHistory.d.ts +0 -1
  92. package/dist/features/products/components/BidHistory.js +1 -1
  93. package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -40
  94. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  95. package/dist/features/products/components/ProductFilters.d.ts +20 -20
  96. package/dist/features/products/components/ProductFilters.js +62 -59
  97. package/dist/features/products/components/ProductsIndexListing.js +28 -24
  98. package/dist/features/products/components/ProductsIndexPageView.js +18 -10
  99. package/dist/features/products/hooks/useProducts.js +2 -0
  100. package/dist/features/products/repository/products.repository.js +2 -22
  101. package/dist/features/products/schemas/index.d.ts +28 -28
  102. package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
  103. package/dist/features/products/types/index.d.ts +2 -0
  104. package/dist/features/promotions/components/CouponsIndexListing.js +57 -32
  105. package/dist/features/promotions/schemas/index.d.ts +4 -4
  106. package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
  107. package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
  108. package/dist/features/reviews/components/ReviewFilters.js +10 -7
  109. package/dist/features/reviews/components/ReviewsIndexListing.js +21 -17
  110. package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
  111. package/dist/features/reviews/components/ReviewsList.js +11 -5
  112. package/dist/features/reviews/schemas/firestore.d.ts +1 -0
  113. package/dist/features/reviews/schemas/index.d.ts +8 -8
  114. package/dist/features/reviews/types/index.d.ts +1 -0
  115. package/dist/features/search/schemas/index.d.ts +2 -2
  116. package/dist/features/seller/components/SellerAuctionsView.js +5 -1
  117. package/dist/features/seller/components/SellerBidsView.js +4 -1
  118. package/dist/features/seller/components/SellerCouponsView.js +4 -1
  119. package/dist/features/seller/components/SellerOffersView.js +5 -1
  120. package/dist/features/seller/components/SellerOrdersView.js +4 -1
  121. package/dist/features/seller/components/SellerPayoutsView.js +5 -1
  122. package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
  123. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
  124. package/dist/features/seller/components/SellerProductsView.js +4 -1
  125. package/dist/features/seller/schemas/index.d.ts +12 -12
  126. package/dist/features/stores/api/route.js +10 -1
  127. package/dist/features/stores/components/StoreReviewsListing.js +1 -1
  128. package/dist/features/stores/components/StoresIndexListing.js +24 -20
  129. package/dist/features/stores/repository/stores.repository.js +8 -5
  130. package/dist/features/stores/schemas/firestore.d.ts +1 -0
  131. package/dist/features/stores/schemas/index.d.ts +4 -4
  132. package/dist/index.d.ts +8 -1
  133. package/dist/index.js +12 -1
  134. package/dist/providers/firebase-client/auth.d.ts +7 -0
  135. package/dist/providers/firebase-client/auth.js +15 -8
  136. package/dist/react/hooks/useUrlTable.js +2 -1
  137. package/dist/seed/bids-seed-data.js +45 -44
  138. package/dist/seed/categories-seed-data.js +41 -40
  139. package/dist/seed/coupons-seed-data.js +41 -40
  140. package/dist/seed/products-auctions-seed-data.js +54 -53
  141. package/dist/seed/products-preorders-seed-data.js +25 -24
  142. package/dist/seed/products-prize-draws-seed-data.js +24 -23
  143. package/dist/seed/products-standard-seed-data.js +294 -293
  144. package/dist/seed/reviews-seed-data.js +61 -60
  145. package/dist/seed/scammers-seed-data.js +3 -2
  146. package/dist/seed/stores-seed-data.js +9 -8
  147. package/dist/seed/support-tickets-seed-data.js +13 -12
  148. package/dist/seed/users-seed-data.js +28 -27
  149. package/dist/tailwind-utilities.css +1 -1
  150. package/dist/utils/sieve-builder.d.ts +41 -0
  151. package/dist/utils/sieve-builder.js +66 -0
  152. package/package.json +2 -2
@@ -3,11 +3,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { Plus, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
7
8
  import { Button, ListingToolbar, Pagination, ListingViewShell, SideDrawer } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
10
11
  import { DataTable } from "./DataTable";
12
+ import { AdminViewCards } from "./AdminViewCards";
11
13
  import { AdminFaqEditorView } from "./AdminFaqEditorView";
12
14
  const PAGE_SIZE = 25;
13
15
  const FILTER_KEYS = ["isActive"];
@@ -19,6 +21,7 @@ const SORT_OPTIONS = [
19
21
  ];
20
22
  export function AdminFaqsView({ children, getRowHref, ...props }) {
21
23
  const hasChildren = React.Children.count(children) > 0;
24
+ const [view, setView] = useState("table");
22
25
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
23
26
  const { openCreatePanel, openEditPanel, closePanel, isCreateOpen, isEditOpen, editId } = usePanelUrlSync();
24
27
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -71,8 +74,9 @@ export function AdminFaqsView({ children, getRowHref, ...props }) {
71
74
  });
72
75
  const currentPage = table.getNumber("page", 1);
73
76
  const totalPages = Math.ceil(total / PAGE_SIZE);
77
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
74
78
  if (hasChildren) {
75
79
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
76
80
  }
77
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search questions, categories, or tokens", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add FAQ"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: (row) => openEditPanel(row.id) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Published", value: "true" }, { label: "Draft", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value ? "bg-primary text-white border-primary" : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add FAQ" : "Edit FAQ", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFaqEditorView, { faqId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
81
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search questions, categories, or tokens", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add FAQ"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Published", value: "true" }, { label: "Draft", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value ? "bg-primary text-white border-primary" : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add FAQ" : "Edit FAQ", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFaqEditorView, { faqId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
78
82
  }
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import React from "react";
3
+ import React, { useState } from "react";
4
4
  import { Plus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
7
8
  import { Button, Div, ListingToolbar, ListingViewShell, Pagination, Row, SideDrawer, Text, } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -48,6 +49,7 @@ function mapFeatureRow(item, index) {
48
49
  }
49
50
  export function AdminFeaturesView({ children, ...props }) {
50
51
  const hasChildren = React.Children.count(children) > 0;
52
+ const [view, setView] = useState("table");
51
53
  const table = useUrlTable({
52
54
  defaults: {
53
55
  pageSize: String(PAGE_SIZE),
@@ -81,13 +83,14 @@ export function AdminFeaturesView({ children, ...props }) {
81
83
  });
82
84
  const currentPage = table.getNumber("page", 1);
83
85
  const totalPages = Math.ceil(total / PAGE_SIZE);
86
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
84
87
  if (hasChildren) {
85
88
  return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
86
89
  }
87
90
  const tabClass = (value) => `${TAB_BASE_CLASS} ${scopeFilter === value ? TAB_ACTIVE_CLASS : TAB_INACTIVE_CLASS}`;
88
91
  return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { searchValue: searchInput, searchPlaceholder: "Search features by label", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
89
92
  table.set("sort", v);
90
- }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Feature"] }) }), _jsx(Row, { className: STICKY_TABS_CLASS, gap: "xs", children: PRODUCT_FEATURE_SCOPE_TABS.map((tab) => (_jsx("button", { type: "button", onClick: () => {
93
+ }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Feature"] }) }), _jsx(Row, { className: STICKY_TABS_CLASS, gap: "xs", children: PRODUCT_FEATURE_SCOPE_TABS.map((tab) => (_jsx("button", { type: "button", onClick: () => {
91
94
  table.set("scope", tab.value);
92
95
  }, className: tabClass(tab.value), children: tab.label }, tab.value))) }), totalPages > 1 && (_jsx(Div, { className: PAGINATION_BAR_CLASS, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs(Div, { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Text, { as: "div", className: ERROR_BANNER_CLASS, children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No features found", onRowClick: (row) => openEditPanel(row.id) })] }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Feature" : "Edit Feature", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFeatureEditorView, { featureId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
93
96
  }
@@ -6,10 +6,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  */
7
7
  import React, { useState, useCallback } from "react";
8
8
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
9
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
9
10
  import { ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
10
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
13
  import { DataTable } from "./DataTable";
14
+ import { AdminViewCards } from "./AdminViewCards";
13
15
  const PAGE_SIZE = 25;
14
16
  const DEFAULT_SORT = "-updatedAt";
15
17
  const SORT_OPTIONS = [
@@ -18,6 +20,7 @@ const SORT_OPTIONS = [
18
20
  ];
19
21
  export function AdminHistoryView({ children, ...props }) {
20
22
  const hasChildren = React.Children.count(children) > 0;
23
+ const [view, setView] = useState("table");
21
24
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
22
25
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
23
26
  const resetAll = useCallback(() => {
@@ -50,8 +53,9 @@ export function AdminHistoryView({ children, ...props }) {
50
53
  });
51
54
  const currentPage = table.getNumber("page", 1);
52
55
  const totalPages = Math.ceil(total / PAGE_SIZE);
56
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
53
57
  if (hasChildren) {
54
58
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
55
59
  }
56
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by user ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No user history records found" })] })] }));
60
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by user ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No user history records found" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No user history records found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] })] }));
57
61
  }
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
8
  import { Button, ConfirmDeleteModal, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, useToast, } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { ADMIN_NEWSLETTER_STATUS_TABS } from "../constants/filter-tabs";
@@ -20,6 +21,7 @@ const SORT_OPTIONS = [
20
21
  const STATUS_OPTIONS = ADMIN_NEWSLETTER_STATUS_TABS;
21
22
  export function AdminNewsletterView({ children, ...props }) {
22
23
  const hasChildren = React.Children.count(children) > 0;
24
+ const [view, setView] = useState("table");
23
25
  const queryClient = useQueryClient();
24
26
  const { showToast } = useToast();
25
27
  const [unsubscribeOpen, setUnsubscribeOpen] = useState(false);
@@ -115,10 +117,11 @@ export function AdminNewsletterView({ children, ...props }) {
115
117
  }, [showToast]);
116
118
  const currentPage = table.getNumber("page", 1);
117
119
  const totalPages = Math.ceil(total / PAGE_SIZE);
120
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
118
121
  if (hasChildren) {
119
122
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
120
123
  }
121
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by email or source", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: "Export CSV" }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No subscribers found", renderRowActions: (row) => {
124
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by email or source", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: "Export CSV" }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No subscribers found", renderRowActions: (row) => {
122
125
  const nr = row;
123
126
  return (_jsx(RowActionMenu, { actions: [
124
127
  {
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
8
  import { ListingToolbar, Pagination, ListingViewShell, RowActionMenu, ConfirmDeleteModal, useToast } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -22,6 +23,7 @@ const NOTIF_TYPES = [
22
23
  ];
23
24
  export function AdminNotificationsView({ children, ...props }) {
24
25
  const hasChildren = React.Children.count(children) > 0;
26
+ const [view, setView] = useState("table");
25
27
  const queryClient = useQueryClient();
26
28
  const { showToast } = useToast();
27
29
  const [deleteTarget, setDeleteTarget] = useState(null);
@@ -105,10 +107,11 @@ export function AdminNotificationsView({ children, ...props }) {
105
107
  });
106
108
  const currentPage = table.getNumber("page", 1);
107
109
  const totalPages = Math.ceil(total / PAGE_SIZE);
110
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
108
111
  if (hasChildren) {
109
112
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
110
113
  }
111
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by title or user ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No notifications found", renderRowActions: (row) => {
114
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by title or user ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No notifications found", renderRowActions: (row) => {
112
115
  const nr = row;
113
116
  return (_jsx(RowActionMenu, { actions: [
114
117
  { label: "Resend", onClick: () => resendMutation.mutate(nr.id) },
@@ -3,9 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
- import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, useToast } from "../../../ui";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, useToast } from "../../../ui";
7
8
  import { apiClient } from "../../../http";
8
9
  import { QuickEditMenu } from "./QuickEditMenu";
10
+ import { AdminViewCards } from "./AdminViewCards";
9
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
12
  import { ADMIN_ORDER_STATUS_TABS } from "../constants/filter-tabs";
11
13
  import { toRecordArray, toRelativeDate, toRupees, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -22,15 +24,7 @@ const STATUS_OPTIONS = ADMIN_ORDER_STATUS_TABS;
22
24
  export function AdminOrdersView({ children, ...props }) {
23
25
  const hasChildren = React.Children.count(children) > 0;
24
26
  const { showToast } = useToast();
25
- const [selectedIds, setSelectedIds] = useState(new Set());
26
- const toggleSelect = (id, next) => setSelectedIds((prev) => {
27
- const s = new Set(prev);
28
- if (next)
29
- s.add(id);
30
- else
31
- s.delete(id);
32
- return s;
33
- });
27
+ const [view, setView] = useState("table");
34
28
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
35
29
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
36
30
  const [filterOpen, setFilterOpen] = useState(false);
@@ -98,10 +92,18 @@ export function AdminOrdersView({ children, ...props }) {
98
92
  }, [showToast]);
99
93
  const currentPage = table.getNumber("page", 1);
100
94
  const totalPages = Math.ceil(total / PAGE_SIZE);
95
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
101
96
  if (hasChildren) {
102
97
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
103
98
  }
104
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search orders, buyers, or tracking IDs", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No orders found", selectedIds: selectedIds, onToggleSelect: toggleSelect, onToggleSelectAll: (next) => setSelectedIds(next ? new Set(rows.map((r) => r.id)) : new Set()), renderRowActions: (row) => (_jsx(QuickEditMenu, { actions: [
99
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search orders, buyers, or tracking IDs", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
100
+ { id: "mark-shipped", label: "Mark as Shipped", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
101
+ void handleQuickStatus(id, "SHIPPED"); selection.clearSelection(); } },
102
+ { id: "mark-delivered", label: "Mark as Delivered", variant: "primary", onClick: () => { for (const id of selection.selectedIds)
103
+ void handleQuickStatus(id, "DELIVERED"); selection.clearSelection(); } },
104
+ { id: "mark-cancelled", label: "Cancel Orders", variant: "danger", onClick: () => { for (const id of selection.selectedIds)
105
+ void handleQuickStatus(id, "CANCELLED"); selection.clearSelection(); } },
106
+ ] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No orders found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(QuickEditMenu, { actions: [
105
107
  {
106
108
  label: "View full details",
107
109
  onClick: () => { setSelectedRow(row); setDrawerOpen(true); },
@@ -118,5 +120,5 @@ export function AdminOrdersView({ children, ...props }) {
118
120
  onSubmit: (vals) => handleQuickStatus(row.id, String(vals.status ?? "")),
119
121
  submitLabel: "Update",
120
122
  },
121
- ] })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }), _jsx(AdminOrderEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), orderId: selectedRow?.id, orderLabel: selectedRow?.primary, currentStatus: selectedRow?.status })] }));
123
+ ] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No orders found", onRowClick: (row) => { setSelectedRow(row); setDrawerOpen(true); }, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }), _jsx(AdminOrderEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), orderId: selectedRow?.id, orderLabel: selectedRow?.primary, currentStatus: selectedRow?.status })] }));
122
124
  }
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
8
  import { Button, Form, FormActions, FilterChipGroup, Input, ListingToolbar, ListingViewShell, Modal, Pagination, RowActionMenu, useToast, } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { ADMIN_PAYOUT_STATUS_TABS } from "../constants/filter-tabs";
@@ -21,6 +22,7 @@ const SORT_OPTIONS = [
21
22
  const STATUS_OPTIONS = ADMIN_PAYOUT_STATUS_TABS;
22
23
  export function AdminPayoutsView({ children, ...props }) {
23
24
  const hasChildren = React.Children.count(children) > 0;
25
+ const [view, setView] = useState("table");
24
26
  const [markPaidOpen, setMarkPaidOpen] = useState(false);
25
27
  const [selectedPayoutId, setSelectedPayoutId] = useState(null);
26
28
  const [transactionId, setTransactionId] = useState("");
@@ -118,10 +120,11 @@ export function AdminPayoutsView({ children, ...props }) {
118
120
  };
119
121
  const currentPage = table.getNumber("page", 1);
120
122
  const totalPages = Math.ceil(total / PAGE_SIZE);
123
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
121
124
  if (hasChildren) {
122
125
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
123
126
  }
124
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, payout IDs, or order groups", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: "Export CSV" }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No payouts found", renderRowActions: (row) => {
127
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, payout IDs, or order groups", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: "Export CSV" }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No payouts found", renderRowActions: (row) => {
125
128
  const pr = row;
126
129
  return (_jsx(RowActionMenu, { actions: [
127
130
  {
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { Pencil, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Alert, Badge, Button, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ADMIN_PRODUCT_STATUS_TABS } from "../constants/filter-tabs";
@@ -65,6 +66,7 @@ const PRIZE_DRAW_COLUMNS = [
65
66
  ];
66
67
  export function AdminPrizeDrawsView({ children, ...props }) {
67
68
  const hasChildren = React.Children.count(children) > 0;
69
+ const [view, setView] = useState("table");
68
70
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
69
71
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
70
72
  const [filterOpen, setFilterOpen] = useState(false);
@@ -125,10 +127,11 @@ export function AdminPrizeDrawsView({ children, ...props }) {
125
127
  });
126
128
  const currentPage = table.getNumber("page", 1);
127
129
  const totalPages = Math.ceil(total / PAGE_SIZE);
130
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
128
131
  if (hasChildren) {
129
132
  return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
130
133
  }
131
134
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws by name or store\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
132
135
  table.set("sort", v);
133
- }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws found", getRowHref: (row) => String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx("a", { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)] transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white hover:opacity-90 transition-opacity active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
136
+ }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws found", getRowHref: (row) => String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx("a", { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)] transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white hover:opacity-90 transition-opacity active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
134
137
  }
@@ -4,11 +4,13 @@ import React, { useState, useCallback } from "react";
4
4
  import { Plus, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
7
- import { Button, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Toggle, useToast } from "../../../ui";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
+ import { Button, BulkActionBar, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Toggle, useToast } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { ADMIN_PRODUCT_STATUS_TABS, ADMIN_PRODUCT_LISTING_TYPE_TABS, } from "../constants/filter-tabs";
10
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
12
  import { DataTable } from "./DataTable";
13
+ import { AdminViewCards } from "./AdminViewCards";
12
14
  import { apiClient } from "../../../http";
13
15
  import { AdminProductEditorView } from "./AdminProductEditorView";
14
16
  import { QuickEditMenu } from "./QuickEditMenu";
@@ -57,6 +59,7 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
57
59
  const hasChildren = React.Children.count(children) > 0;
58
60
  const [overrides, setOverrides] = useState({});
59
61
  const { showToast } = useToast();
62
+ const [view, setView] = useState("table");
60
63
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
61
64
  const { openCreatePanel, openEditPanel, closePanel, isCreateOpen, isEditOpen, editId } = usePanelUrlSync();
62
65
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -129,6 +132,7 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
129
132
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
130
133
  });
131
134
  const rows = fetchedRows.map((r) => overrides[r.id] ? { ...r, ...overrides[r.id] } : r);
135
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
132
136
  const handleToggle = async (id, field, value) => {
133
137
  const prev = fetchedRows.find((r) => r.id === id)?.[field];
134
138
  setOverrides((o) => ({ ...o, [id]: { ...o[id], [field]: value } }));
@@ -165,7 +169,14 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
165
169
  className: "w-56",
166
170
  render: (row) => (_jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1", onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), role: "presentation", children: FLAG_DEFS.map(({ key, label }) => (_jsx(Toggle, { size: "sm", label: label, checked: !!row[key], onChange: (v) => { void handleToggle(row.id, key, v); } }, key))) })),
167
171
  };
168
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Product"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: [...buildBaseColumns(), flagColumn], isLoading: isLoading, emptyLabel: "No products found", onRowClick: (row) => openEditPanel(row.id), renderRowActions: (row) => (_jsx(QuickEditMenu, { actions: [
172
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Product"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
173
+ { id: "feature", label: "Toggle Featured", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
174
+ void handleToggle(id, "featured", !rows.find(r => r.id === id)?.featured); selection.clearSelection(); } },
175
+ { id: "promote", label: "Toggle Promoted", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
176
+ void handleToggle(id, "isPromoted", !rows.find(r => r.id === id)?.isPromoted); selection.clearSelection(); } },
177
+ { id: "sale", label: "Toggle On Sale", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
178
+ void handleToggle(id, "isOnSale", !rows.find(r => r.id === id)?.isOnSale); selection.clearSelection(); } },
179
+ ] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, columns: [...buildBaseColumns(), flagColumn], isLoading: isLoading, emptyLabel: "No products found", onRowClick: (row) => openEditPanel(row.id), selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(QuickEditMenu, { actions: [
169
180
  {
170
181
  label: "Quick edit",
171
182
  formTitle: "Quick Edit Product",
@@ -184,5 +195,5 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
184
195
  separator: true,
185
196
  onClick: () => openEditPanel(row.id),
186
197
  },
187
- ] })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: [_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })] }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Product" : "Edit Product", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminProductEditorView, { productId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
198
+ ] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No products found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: [_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })] }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Product" : "Edit Product", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminProductEditorView, { productId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
188
199
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useCallback } from "react";
4
4
  import { useMutation, useQueryClient } from "@tanstack/react-query";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { ListingToolbar, Pagination, ConfirmDeleteModal, RowActionMenu, useToast } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { toRecordArray, toRelativeDate, toRupees, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -22,6 +23,7 @@ export function AdminReturnRequestsView({ children: _children }) {
22
23
  const [selectedRow, setSelectedRow] = useState(null);
23
24
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
24
25
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
26
+ const [view, setView] = useState("table");
25
27
  const resetAll = useCallback(() => {
26
28
  table.setMany({ q: "", sort: "" });
27
29
  setSearchInput("");
@@ -82,7 +84,8 @@ export function AdminReturnRequestsView({ children: _children }) {
82
84
  });
83
85
  const currentPage = table.getNumber("page", 1);
84
86
  const totalPages = Math.ceil(total / PAGE_SIZE);
85
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by order ID or buyer", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No return requests", renderRowActions: (row) => {
87
+ const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
88
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by order ID or buyer", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No return requests", renderRowActions: (row) => {
86
89
  const rr = row;
87
90
  return (_jsx(RowActionMenu, { actions: [
88
91
  {
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
8
  import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, Modal, RowActionMenu, Button, useToast } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS } from "../constants/filter-tabs";
@@ -24,6 +25,7 @@ const STATUS_OPTIONS = ADMIN_REVIEW_STATUS_TABS;
24
25
  const RATING_OPTIONS = ADMIN_REVIEW_RATING_TABS;
25
26
  export function AdminReviewsView({ renderDetailView, children, ...props }) {
26
27
  const hasChildren = React.Children.count(children) > 0;
28
+ const [view, setView] = useState("table");
27
29
  const hasDetailView = Boolean(renderDetailView);
28
30
  const queryClient = useQueryClient();
29
31
  const { showToast } = useToast();
@@ -120,7 +122,8 @@ export function AdminReviewsView({ renderDetailView, children, ...props }) {
120
122
  if (hasChildren || hasDetailView) {
121
123
  return (_jsx(ListingViewShell, { portal: "admin", ...props, detailView: renderDetailView?.(), children: children }));
122
124
  }
123
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search reviews, products, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No reviews found", renderRowActions: (row) => {
125
+ const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
126
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search reviews, products, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No reviews found", renderRowActions: (row) => {
124
127
  const rr = row;
125
128
  return (_jsx(RowActionMenu, { actions: [
126
129
  { label: "Approve", onClick: () => patchMutation.mutate({ id: rr.id, payload: { status: "approved" } }) },
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ADMIN_SCAMMER_STATUS_TABS } from "../constants/filter-tabs";
@@ -26,6 +27,7 @@ const STATUS_BADGE = {
26
27
  };
27
28
  export function AdminScammersView({ children, ...props }) {
28
29
  const hasChildren = React.Children.count(children) > 0;
30
+ const [view, setView] = useState("table");
29
31
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
30
32
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
31
33
  const [filterOpen, setFilterOpen] = useState(false);
@@ -99,12 +101,13 @@ export function AdminScammersView({ children, ...props }) {
99
101
  });
100
102
  const currentPage = table.getNumber("page", 1);
101
103
  const totalPages = Math.ceil(total / PAGE_SIZE);
104
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
102
105
  if (hasChildren) {
103
106
  return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
104
107
  }
105
108
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by name, phone, UPI ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
106
109
  table.set("sort", v);
107
- }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No scammer profiles found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
110
+ }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No scammer profiles found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
108
111
  {
109
112
  label: "Review",
110
113
  onClick: () => {
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
8
  import { ListingToolbar, Pagination, ListingViewShell, RowActionMenu, ConfirmDeleteModal, useToast } from "../../../ui";
8
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -27,6 +28,7 @@ function maskIp(ip) {
27
28
  }
28
29
  export function AdminSessionsView({ children, ...props }) {
29
30
  const hasChildren = React.Children.count(children) > 0;
31
+ const [view, setView] = useState("table");
30
32
  const queryClient = useQueryClient();
31
33
  const { showToast } = useToast();
32
34
  const [revokeTarget, setRevokeTarget] = useState(null);
@@ -105,10 +107,11 @@ export function AdminSessionsView({ children, ...props }) {
105
107
  });
106
108
  const currentPage = table.getNumber("page", 1);
107
109
  const totalPages = Math.ceil(total / PAGE_SIZE);
110
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
108
111
  if (hasChildren) {
109
112
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
110
113
  }
111
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by user or device", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sessions found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
114
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by user or device", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sessions found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
112
115
  {
113
116
  label: "Revoke",
114
117
  destructive: true,
@@ -2,9 +2,11 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
5
6
  import { ListingToolbar, Pagination } from "../../../ui";
6
7
  import { toRecordArray, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
7
8
  import { DataTable } from "./DataTable";
9
+ import { AdminViewCards } from "./AdminViewCards";
8
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
11
  const PAGE_SIZE = 25;
10
12
  const DEFAULT_SORT = "storeId";
@@ -15,6 +17,7 @@ const SORT_OPTIONS = [
15
17
  export function AdminStoreAddressesView({ children: _children }) {
16
18
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
17
19
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
20
+ const [view, setView] = useState("table");
18
21
  const resetAll = useCallback(() => {
19
22
  table.setMany({ q: "", sort: "" });
20
23
  setSearchInput("");
@@ -48,5 +51,6 @@ export function AdminStoreAddressesView({ children: _children }) {
48
51
  });
49
52
  const currentPage = table.getNumber("page", 1);
50
53
  const totalPages = Math.ceil(total / PAGE_SIZE);
51
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by label, city, or store ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No store addresses found" })] })] }));
54
+ const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
55
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search by label, city, or store ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No store addresses found" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No store addresses found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] })] }));
52
56
  }