@mohasinac/appkit 2.7.10 → 2.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/jobs/core/assignSpinPrize.js +8 -7
- package/dist/_internal/server/jobs/core/bundleStockSync.js +11 -6
- package/dist/_internal/server/jobs/core/countersReconcile.js +3 -2
- package/dist/_internal/server/jobs/core/onProductStockChange.js +12 -7
- package/dist/_internal/server/jobs/core/onProductWrite.js +3 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +7 -5
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +11 -6
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +13 -7
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +9 -4
- package/dist/_internal/server/jobs/core/triggerEventRaffle.js +3 -2
- package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
- package/dist/_internal/shared/features/events/schema.d.ts +4 -4
- package/dist/_internal/shared/features/products/schema.d.ts +20 -20
- package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/configs/next.js +17 -1
- package/dist/constants/field-names.d.ts +813 -0
- package/dist/constants/field-names.js +918 -0
- package/dist/constants/sort.d.ts +7 -0
- package/dist/constants/sort.js +5 -0
- package/dist/constants/table-keys.d.ts +48 -0
- package/dist/constants/table-keys.js +46 -0
- package/dist/features/account/components/AddressesIndexListing.js +9 -6
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
- package/dist/features/admin/components/AdminBidsView.js +4 -1
- package/dist/features/admin/components/AdminBlogView.js +9 -2
- package/dist/features/admin/components/AdminBrandsView.js +9 -2
- package/dist/features/admin/components/AdminCarouselView.js +4 -1
- package/dist/features/admin/components/AdminCartsView.js +4 -1
- package/dist/features/admin/components/AdminCategoriesView.js +9 -2
- package/dist/features/admin/components/AdminContactView.js +4 -1
- package/dist/features/admin/components/AdminCouponsView.js +4 -1
- package/dist/features/admin/components/AdminFaqsView.js +5 -1
- package/dist/features/admin/components/AdminFeaturesView.js +5 -2
- package/dist/features/admin/components/AdminHistoryView.js +5 -1
- package/dist/features/admin/components/AdminNewsletterView.js +4 -1
- package/dist/features/admin/components/AdminNotificationsView.js +4 -1
- package/dist/features/admin/components/AdminOrdersView.js +14 -12
- package/dist/features/admin/components/AdminPayoutsView.js +4 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
- package/dist/features/admin/components/AdminProductsView.js +14 -3
- package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
- package/dist/features/admin/components/AdminReviewsView.js +4 -1
- package/dist/features/admin/components/AdminScammersView.js +4 -1
- package/dist/features/admin/components/AdminSessionsView.js +4 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
- package/dist/features/admin/components/AdminStoresView.js +10 -3
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
- package/dist/features/admin/components/AdminTeamView.js +4 -1
- package/dist/features/admin/components/AdminUsersView.js +9 -12
- package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
- package/dist/features/admin/components/AdminViewCards.js +53 -0
- package/dist/features/admin/components/AdminWishlistsView.js +4 -1
- package/dist/features/auctions/repository/bid.repository.js +1 -6
- package/dist/features/auctions/schemas/index.d.ts +4 -4
- package/dist/features/blog/actions/blog-actions.d.ts +16 -16
- package/dist/features/blog/components/BlogFilters.d.ts +28 -28
- package/dist/features/blog/components/BlogFilters.js +18 -15
- package/dist/features/blog/components/BlogIndexListing.js +24 -13
- package/dist/features/blog/schemas/index.d.ts +10 -10
- package/dist/features/categories/components/CategoriesIndexListing.js +30 -19
- package/dist/features/categories/schemas/index.d.ts +2 -2
- package/dist/features/events/components/AdminEventsView.js +5 -1
- package/dist/features/events/components/EventFilters.d.ts +24 -24
- package/dist/features/events/components/EventFilters.js +16 -13
- package/dist/features/events/components/EventStatusBadge.js +1 -0
- package/dist/features/events/components/EventsIndexListing.js +36 -24
- package/dist/features/events/components/EventsListPageView.js +13 -10
- package/dist/features/events/schemas/firestore.d.ts +1 -0
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/events/schemas/index.d.ts +1 -1
- package/dist/features/events/types/index.d.ts +1 -1
- package/dist/features/faq/actions/faq-actions.d.ts +10 -10
- package/dist/features/faq/schemas/index.d.ts +4 -4
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/orders/components/OrderFilters.d.ts +20 -20
- package/dist/features/orders/components/OrderFilters.js +33 -30
- package/dist/features/orders/repository/orders.repository.js +1 -15
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +44 -36
- package/dist/features/pre-orders/schemas/index.d.ts +3 -3
- package/dist/features/products/components/AuctionsIndexListing.js +63 -30
- package/dist/features/products/components/BidHistory.d.ts +0 -1
- package/dist/features/products/components/BidHistory.js +1 -1
- package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -37
- package/dist/features/products/components/ProductDetailPageView.js +1 -1
- package/dist/features/products/components/ProductFilters.d.ts +20 -20
- package/dist/features/products/components/ProductFilters.js +62 -59
- package/dist/features/products/components/ProductsIndexListing.js +37 -29
- package/dist/features/products/components/ProductsIndexPageView.js +18 -10
- package/dist/features/products/hooks/useProducts.js +2 -0
- package/dist/features/products/repository/products.repository.js +2 -22
- package/dist/features/products/schemas/index.d.ts +28 -28
- package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
- package/dist/features/products/types/index.d.ts +2 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +21 -17
- package/dist/features/promotions/schemas/index.d.ts +4 -4
- package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
- package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
- package/dist/features/reviews/components/ReviewFilters.js +10 -7
- package/dist/features/reviews/components/ReviewsIndexListing.js +25 -14
- package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
- package/dist/features/reviews/components/ReviewsList.js +11 -5
- package/dist/features/reviews/schemas/firestore.d.ts +1 -0
- package/dist/features/reviews/schemas/index.d.ts +8 -8
- package/dist/features/reviews/types/index.d.ts +1 -0
- package/dist/features/search/schemas/index.d.ts +2 -2
- package/dist/features/seller/components/SellerAuctionsView.js +5 -1
- package/dist/features/seller/components/SellerBidsView.js +4 -1
- package/dist/features/seller/components/SellerCouponsView.js +4 -1
- package/dist/features/seller/components/SellerOffersView.js +5 -1
- package/dist/features/seller/components/SellerOrdersView.js +4 -1
- package/dist/features/seller/components/SellerPayoutsView.js +5 -1
- package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
- package/dist/features/seller/components/SellerProductsView.js +4 -1
- package/dist/features/seller/schemas/index.d.ts +10 -10
- package/dist/features/stores/api/route.js +10 -1
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
- package/dist/features/stores/components/StorePreOrdersListing.js +2 -1
- package/dist/features/stores/components/StoreReviewsListing.js +1 -1
- package/dist/features/stores/components/StoresIndexListing.js +45 -27
- package/dist/features/stores/schemas/firestore.d.ts +1 -0
- package/dist/features/stores/schemas/index.d.ts +4 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.js +12 -1
- package/dist/react/hooks/useUrlTable.js +2 -1
- package/dist/seed/bids-seed-data.js +45 -44
- package/dist/seed/categories-seed-data.js +41 -40
- package/dist/seed/coupons-seed-data.js +41 -40
- package/dist/seed/products-auctions-seed-data.js +54 -53
- package/dist/seed/products-preorders-seed-data.js +25 -24
- package/dist/seed/products-prize-draws-seed-data.js +24 -23
- package/dist/seed/products-standard-seed-data.js +294 -293
- package/dist/seed/reviews-seed-data.js +61 -60
- package/dist/seed/scammers-seed-data.js +3 -2
- package/dist/seed/stores-seed-data.js +9 -8
- package/dist/seed/support-tickets-seed-data.js +13 -12
- package/dist/seed/users-seed-data.js +28 -27
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/ListingToolbar.d.ts +7 -4
- package/dist/ui/components/ListingToolbar.js +4 -3
- package/dist/ui/components/SiteLogo.js +1 -1
- package/dist/utils/sieve-builder.d.ts +41 -0
- package/dist/utils/sieve-builder.js +66 -0
- package/package.json +1 -1
|
@@ -5,27 +5,30 @@ import { FilterFacetSection } from "../../filters/FilterFacetSection";
|
|
|
5
5
|
import { RangeFilter } from "../../filters/RangeFilter";
|
|
6
6
|
import { SwitchFilter } from "../../filters/SwitchFilter";
|
|
7
7
|
import { Div } from "../../../ui";
|
|
8
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
9
|
+
import { BLOG_FIELDS } from "../../../constants/field-names";
|
|
10
|
+
import { sortBy } from "../../../constants/sort";
|
|
8
11
|
export const BLOG_FILTER_KEYS = {
|
|
9
|
-
admin: [
|
|
10
|
-
seller: [
|
|
11
|
-
public: [
|
|
12
|
+
admin: [TABLE_KEYS.STATUS, TABLE_KEYS.CATEGORY, TABLE_KEYS.IS_FEATURED],
|
|
13
|
+
seller: [TABLE_KEYS.STATUS, TABLE_KEYS.CATEGORY, TABLE_KEYS.IS_FEATURED],
|
|
14
|
+
public: [TABLE_KEYS.CATEGORY],
|
|
12
15
|
};
|
|
13
16
|
export const BLOG_ADMIN_SORT_OPTIONS = [
|
|
14
|
-
{ value:
|
|
15
|
-
{ value: "
|
|
16
|
-
{ value: "
|
|
17
|
-
{ value:
|
|
18
|
-
{ value:
|
|
19
|
-
{ value:
|
|
20
|
-
{ value:
|
|
21
|
-
{ value: "
|
|
17
|
+
{ value: sortBy(BLOG_FIELDS.CREATED_AT), label: "Newest First" },
|
|
18
|
+
{ value: sortBy(BLOG_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
19
|
+
{ value: sortBy(BLOG_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
|
|
20
|
+
{ value: sortBy(BLOG_FIELDS.TITLE), label: "Title Z–A" },
|
|
21
|
+
{ value: sortBy(BLOG_FIELDS.VIEWS), label: "Most Viewed" },
|
|
22
|
+
{ value: sortBy(BLOG_FIELDS.READ_TIME_MINUTES), label: "Longest Read" },
|
|
23
|
+
{ value: sortBy(BLOG_FIELDS.PUBLISHED_AT), label: "Published: Newest" },
|
|
24
|
+
{ value: sortBy(BLOG_FIELDS.PUBLISHED_AT, "ASC"), label: "Published: Oldest" },
|
|
22
25
|
];
|
|
23
26
|
export const BLOG_SELLER_SORT_OPTIONS = BLOG_ADMIN_SORT_OPTIONS;
|
|
24
27
|
export const BLOG_PUBLIC_SORT_OPTIONS = [
|
|
25
|
-
{ value:
|
|
26
|
-
{ value: "
|
|
27
|
-
{ value:
|
|
28
|
-
{ value: "
|
|
28
|
+
{ value: sortBy(BLOG_FIELDS.PUBLISHED_AT), label: "Published: Newest" },
|
|
29
|
+
{ value: sortBy(BLOG_FIELDS.PUBLISHED_AT, "ASC"), label: "Published: Oldest" },
|
|
30
|
+
{ value: sortBy(BLOG_FIELDS.VIEWS), label: "Most Viewed" },
|
|
31
|
+
{ value: sortBy(BLOG_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
|
|
29
32
|
];
|
|
30
33
|
// Backward-compatible alias.
|
|
31
34
|
export const BLOG_SORT_OPTIONS = BLOG_ADMIN_SORT_OPTIONS;
|
|
@@ -8,12 +8,23 @@ import { Pagination, ListingToolbar } from "../../../ui";
|
|
|
8
8
|
import { ROUTES } from "../../../next";
|
|
9
9
|
import { BlogCard } from "./BlogListView";
|
|
10
10
|
import { BlogFilters, BLOG_PUBLIC_SORT_OPTIONS } from "./BlogFilters";
|
|
11
|
+
import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
12
|
+
import { sortBy } from "../../../constants/sort";
|
|
13
|
+
import { BLOG_FIELDS } from "../../../constants/field-names";
|
|
11
14
|
const PAGE_SIZE = 24;
|
|
12
|
-
const
|
|
15
|
+
const DEFAULT_SORT = sortBy(BLOG_FIELDS.PUBLISHED_AT);
|
|
16
|
+
const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
|
|
13
17
|
export function BlogIndexListing({ initialData }) {
|
|
14
|
-
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort:
|
|
15
|
-
const [searchInput, setSearchInput] = useState(table.get(
|
|
18
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
19
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
16
20
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
21
|
+
const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
|
|
22
|
+
const handleViewToggle = (next) => {
|
|
23
|
+
if (next === VIEW_MODE.TABLE)
|
|
24
|
+
return;
|
|
25
|
+
setView(next);
|
|
26
|
+
table.set(TABLE_KEYS.VIEW, next);
|
|
27
|
+
};
|
|
17
28
|
// Pending filter state — buffered until "Apply Filters" clicked
|
|
18
29
|
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
19
30
|
const pendingTable = useMemo(() => ({
|
|
@@ -56,27 +67,27 @@ export function BlogIndexListing({ initialData }) {
|
|
|
56
67
|
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
57
68
|
}, []);
|
|
58
69
|
const resetAll = useCallback(() => {
|
|
59
|
-
const updates = {
|
|
70
|
+
const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" };
|
|
60
71
|
for (const k of FILTER_KEYS)
|
|
61
72
|
updates[k] = "";
|
|
62
73
|
table.setMany(updates);
|
|
63
74
|
setSearchInput("");
|
|
64
75
|
}, [table]);
|
|
65
76
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
66
|
-
const hasActiveState = !!table.get(
|
|
67
|
-
table.get(
|
|
77
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
78
|
+
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
68
79
|
activeFilterCount > 0;
|
|
69
80
|
const params = {
|
|
70
|
-
q: table.get(
|
|
71
|
-
category: (table.get(
|
|
72
|
-
sort: table.get(
|
|
73
|
-
page: table.getNumber(
|
|
74
|
-
perPage: table.getNumber(
|
|
81
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
82
|
+
category: (table.get(TABLE_KEYS.CATEGORY) || undefined),
|
|
83
|
+
sort: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
84
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
85
|
+
perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, PAGE_SIZE),
|
|
75
86
|
};
|
|
76
87
|
const { posts, total, totalPages, isLoading } = useBlogPosts(params, { initialData });
|
|
77
88
|
const currentPage = table.getNumber("page", 1);
|
|
78
89
|
const commitSearch = useCallback(() => {
|
|
79
|
-
table.set(
|
|
90
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
80
91
|
}, [searchInput, table]);
|
|
81
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search posts...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(
|
|
92
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search posts...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: BLOG_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, 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) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-5 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" })] })] }, i))) })) : posts.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No posts found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: posts.map((post) => (_jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id))) })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: posts.map((post) => (_jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.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: "flex items-center gap-2 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 filters", 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", children: _jsx(BlogFilters, { table: pendingTable, variant: "public" }) }), _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})` : ""] }) })] })] }))] }));
|
|
82
93
|
}
|
|
@@ -158,14 +158,14 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
158
158
|
updatedAt?: string | undefined;
|
|
159
159
|
content?: string | undefined;
|
|
160
160
|
tags?: string[] | undefined;
|
|
161
|
-
publishedAt?: string | undefined;
|
|
162
161
|
isFeatured?: boolean | undefined;
|
|
163
|
-
|
|
162
|
+
publishedAt?: string | undefined;
|
|
164
163
|
excerpt?: string | undefined;
|
|
165
164
|
authorId?: string | undefined;
|
|
166
165
|
authorName?: string | undefined;
|
|
167
|
-
authorAvatar?: string | undefined;
|
|
168
166
|
readTimeMinutes?: number | undefined;
|
|
167
|
+
views?: number | undefined;
|
|
168
|
+
authorAvatar?: string | undefined;
|
|
169
169
|
metaTitle?: string | undefined;
|
|
170
170
|
metaDescription?: string | undefined;
|
|
171
171
|
}, {
|
|
@@ -178,8 +178,8 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
178
178
|
updatedAt?: string | undefined;
|
|
179
179
|
content?: string | undefined;
|
|
180
180
|
tags?: string[] | undefined;
|
|
181
|
-
publishedAt?: string | undefined;
|
|
182
181
|
isFeatured?: boolean | undefined;
|
|
182
|
+
publishedAt?: string | undefined;
|
|
183
183
|
coverImage?: string | {
|
|
184
184
|
type: "video" | "image" | "file";
|
|
185
185
|
url: string;
|
|
@@ -188,8 +188,11 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
188
188
|
thumbnailUrl?: string | undefined;
|
|
189
189
|
youtubeId?: string | undefined;
|
|
190
190
|
} | null | undefined;
|
|
191
|
-
views?: number | undefined;
|
|
192
191
|
excerpt?: string | undefined;
|
|
192
|
+
authorId?: string | undefined;
|
|
193
|
+
authorName?: string | undefined;
|
|
194
|
+
readTimeMinutes?: number | undefined;
|
|
195
|
+
views?: number | undefined;
|
|
193
196
|
contentImages?: {
|
|
194
197
|
type: "video" | "image" | "file";
|
|
195
198
|
url: string;
|
|
@@ -206,10 +209,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
206
209
|
thumbnailUrl?: string | undefined;
|
|
207
210
|
youtubeId?: string | undefined;
|
|
208
211
|
}[] | undefined;
|
|
209
|
-
authorId?: string | undefined;
|
|
210
|
-
authorName?: string | undefined;
|
|
211
212
|
authorAvatar?: string | undefined;
|
|
212
|
-
readTimeMinutes?: number | undefined;
|
|
213
213
|
metaTitle?: string | undefined;
|
|
214
214
|
metaDescription?: string | undefined;
|
|
215
215
|
}>;
|
|
@@ -228,14 +228,14 @@ export declare const blogListParamsSchema: z.ZodObject<{
|
|
|
228
228
|
q?: string | undefined;
|
|
229
229
|
page?: number | undefined;
|
|
230
230
|
category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
|
|
231
|
-
featured?: boolean | undefined;
|
|
232
231
|
tags?: string | undefined;
|
|
232
|
+
featured?: boolean | undefined;
|
|
233
233
|
}, {
|
|
234
234
|
sort?: string | undefined;
|
|
235
235
|
perPage?: number | undefined;
|
|
236
236
|
q?: string | undefined;
|
|
237
237
|
page?: number | undefined;
|
|
238
238
|
category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
|
|
239
|
-
featured?: boolean | undefined;
|
|
240
239
|
tags?: string | undefined;
|
|
240
|
+
featured?: boolean | undefined;
|
|
241
241
|
}>;
|
|
@@ -8,19 +8,30 @@ import { Pagination, ListingToolbar } from "../../../ui";
|
|
|
8
8
|
import { CategoryCard } from "./CategoryGrid";
|
|
9
9
|
import { CategoryFilters } from "./CategoryFilters";
|
|
10
10
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
11
|
+
import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
12
|
+
import { sortBy } from "../../../constants/sort";
|
|
13
|
+
import { CATEGORY_FIELDS } from "../../../constants/field-names";
|
|
11
14
|
const PAGE_SIZE = 24;
|
|
12
|
-
const
|
|
15
|
+
const DEFAULT_SORT = sortBy(CATEGORY_FIELDS.NAME, "ASC");
|
|
16
|
+
const FILTER_KEYS = [TABLE_KEYS.IS_FEATURED, "isBrand", "rootOnly", "tier", "minItemCount", "maxItemCount"];
|
|
13
17
|
const SORT_OPTIONS = [
|
|
14
|
-
{ value: "
|
|
15
|
-
{ value:
|
|
18
|
+
{ value: sortBy(CATEGORY_FIELDS.NAME, "ASC"), label: "Name A–Z" },
|
|
19
|
+
{ value: sortBy(CATEGORY_FIELDS.NAME), label: "Name Z–A" },
|
|
16
20
|
{ value: "-productCount", label: "Most Products" },
|
|
17
21
|
];
|
|
18
22
|
export function CategoriesIndexListing({ initialData: _, brandsOnly = false }) {
|
|
19
|
-
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort:
|
|
20
|
-
const [searchInput, setSearchInput] = useState(table.get(
|
|
23
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
24
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
21
25
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
22
|
-
const
|
|
23
|
-
const
|
|
26
|
+
const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
|
|
27
|
+
const handleViewToggle = (next) => {
|
|
28
|
+
if (next === VIEW_MODE.TABLE)
|
|
29
|
+
return;
|
|
30
|
+
setView(next);
|
|
31
|
+
table.set(TABLE_KEYS.VIEW, next);
|
|
32
|
+
};
|
|
33
|
+
const sort = table.get(TABLE_KEYS.SORT) || DEFAULT_SORT;
|
|
34
|
+
const page = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
24
35
|
// Pending filter state — buffered until "Apply Filters" clicked
|
|
25
36
|
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
26
37
|
const pendingTable = useMemo(() => ({
|
|
@@ -63,35 +74,35 @@ export function CategoriesIndexListing({ initialData: _, brandsOnly = false }) {
|
|
|
63
74
|
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
64
75
|
}, []);
|
|
65
76
|
const resetAll = useCallback(() => {
|
|
66
|
-
const updates = {
|
|
77
|
+
const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" };
|
|
67
78
|
for (const k of FILTER_KEYS)
|
|
68
79
|
updates[k] = "";
|
|
69
80
|
table.setMany(updates);
|
|
70
81
|
setSearchInput("");
|
|
71
82
|
}, [table]);
|
|
72
83
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
73
|
-
const hasActiveState = !!table.get(
|
|
74
|
-
table.get(
|
|
84
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
85
|
+
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
75
86
|
activeFilterCount > 0;
|
|
76
87
|
const commitSearch = useCallback(() => {
|
|
77
|
-
table.set(
|
|
88
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
78
89
|
}, [searchInput, table]);
|
|
79
90
|
const clearSearch = () => {
|
|
80
91
|
setSearchInput("");
|
|
81
|
-
table.set(
|
|
92
|
+
table.set(TABLE_KEYS.QUERY, "");
|
|
82
93
|
};
|
|
83
94
|
// Tab state — "all" | "categories" | "brands" (not used when brandsOnly=true)
|
|
84
|
-
const activeTab = brandsOnly ? "brands" : (table.get(
|
|
95
|
+
const activeTab = brandsOnly ? "brands" : (table.get(TABLE_KEYS.TAB) || "all");
|
|
85
96
|
const isBrandParam = brandsOnly ? true :
|
|
86
97
|
activeTab === "brands" ? true :
|
|
87
98
|
activeTab === "categories" ? false :
|
|
88
99
|
undefined;
|
|
89
100
|
const switchTab = (key) => {
|
|
90
|
-
table.set(
|
|
101
|
+
table.set(TABLE_KEYS.TAB, key);
|
|
91
102
|
};
|
|
92
103
|
const { categories, total, totalPages, isLoading } = useCategoriesFiltered({
|
|
93
|
-
q: table.get(
|
|
94
|
-
isFeatured: table.get(
|
|
104
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
105
|
+
isFeatured: table.get(TABLE_KEYS.IS_FEATURED) === "true" || undefined,
|
|
95
106
|
isBrand: isBrandParam,
|
|
96
107
|
rootOnly: table.get("rootOnly") === "true" || undefined,
|
|
97
108
|
tier: table.get("tier") ? Number(table.get("tier")) : undefined,
|
|
@@ -101,7 +112,7 @@ export function CategoriesIndexListing({ initialData: _, brandsOnly = false }) {
|
|
|
101
112
|
page,
|
|
102
113
|
pageSize: PAGE_SIZE,
|
|
103
114
|
});
|
|
104
|
-
const activeSearch = table.get(
|
|
115
|
+
const activeSearch = table.get(TABLE_KEYS.QUERY) || "";
|
|
105
116
|
const TABS = [
|
|
106
117
|
{ key: "all", label: "All" },
|
|
107
118
|
{ key: "categories", label: "Categories" },
|
|
@@ -112,9 +123,9 @@ export function CategoriesIndexListing({ initialData: _, brandsOnly = false }) {
|
|
|
112
123
|
activeTab === tab.key
|
|
113
124
|
? "border-primary text-primary dark:text-primary-400 dark:border-primary-400"
|
|
114
125
|
: "border-transparent text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200",
|
|
115
|
-
].join(" "), children: tab.label }, tab.key))) })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: activeTab === "brands" || brandsOnly ? "Search brands..." : "Search categories...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(
|
|
126
|
+
].join(" "), children: tab.label }, tab.key))) })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: activeTab === "brands" || brandsOnly ? "Search brands..." : "Search categories...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4", children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-[4/3] bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3.5 space-y-2", children: [_jsx("div", { className: "h-3.5 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" })] })] }, i))) })) : categories.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: activeSearch
|
|
116
127
|
? `No ${activeTab === "brands" || brandsOnly ? "brands" : "categories"} matching "${activeSearch}"`
|
|
117
128
|
: activeTab === "brands" || brandsOnly
|
|
118
129
|
? "No brands found"
|
|
119
|
-
: "No categories found" })) : (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4", children: categories.map((category) => (_jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.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: "flex items-center gap-2 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 filters", 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", children: _jsx(CategoryFilters, { table: pendingTable, variant: "public" }) }), _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})` : ""] }) })] })] }))] }));
|
|
130
|
+
: "No categories found" })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: categories.map((category) => (_jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id))) })) : (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4", children: categories.map((category) => (_jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.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: "flex items-center gap-2 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 filters", 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", children: _jsx(CategoryFilters, { table: pendingTable, variant: "public" }) }), _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})` : ""] }) })] })] }))] }));
|
|
120
131
|
}
|
|
@@ -128,6 +128,7 @@ export declare const categoryItemSchema: z.ZodObject<{
|
|
|
128
128
|
coverImage?: string | undefined;
|
|
129
129
|
} | undefined;
|
|
130
130
|
order?: number | undefined;
|
|
131
|
+
isFeatured?: boolean | undefined;
|
|
131
132
|
description?: string | undefined;
|
|
132
133
|
seo?: {
|
|
133
134
|
title?: string | undefined;
|
|
@@ -138,7 +139,6 @@ export declare const categoryItemSchema: z.ZodObject<{
|
|
|
138
139
|
rootId?: string | undefined;
|
|
139
140
|
parentIds?: string[] | undefined;
|
|
140
141
|
isLeaf?: boolean | undefined;
|
|
141
|
-
isFeatured?: boolean | undefined;
|
|
142
142
|
featuredPriority?: number | undefined;
|
|
143
143
|
childrenIds?: string[] | undefined;
|
|
144
144
|
metrics?: {
|
|
@@ -161,6 +161,7 @@ export declare const categoryItemSchema: z.ZodObject<{
|
|
|
161
161
|
coverImage?: string | undefined;
|
|
162
162
|
} | undefined;
|
|
163
163
|
order?: number | undefined;
|
|
164
|
+
isFeatured?: boolean | undefined;
|
|
164
165
|
description?: string | undefined;
|
|
165
166
|
seo?: {
|
|
166
167
|
title?: string | undefined;
|
|
@@ -171,7 +172,6 @@ export declare const categoryItemSchema: z.ZodObject<{
|
|
|
171
172
|
rootId?: string | undefined;
|
|
172
173
|
parentIds?: string[] | undefined;
|
|
173
174
|
isLeaf?: boolean | undefined;
|
|
174
|
-
isFeatured?: boolean | undefined;
|
|
175
175
|
featuredPriority?: number | undefined;
|
|
176
176
|
childrenIds?: string[] | undefined;
|
|
177
177
|
metrics?: {
|
|
@@ -3,6 +3,8 @@ 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";
|
|
7
|
+
import { AdminViewCards } from "../../admin/components/AdminViewCards";
|
|
6
8
|
import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
|
|
7
9
|
import { Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, SideDrawer } from "../../../ui";
|
|
8
10
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -34,6 +36,7 @@ const TYPE_OPTIONS = [
|
|
|
34
36
|
];
|
|
35
37
|
export function AdminEventsView({ children, getRowHref, ...props }) {
|
|
36
38
|
const hasChildren = React.Children.count(children) > 0;
|
|
39
|
+
const [view, setView] = useState("table");
|
|
37
40
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
38
41
|
const { openCreatePanel, openEditPanel, closePanel, isCreateOpen, isEditOpen, editId } = usePanelUrlSync();
|
|
39
42
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
@@ -95,8 +98,9 @@ export function AdminEventsView({ children, getRowHref, ...props }) {
|
|
|
95
98
|
});
|
|
96
99
|
const currentPage = table.getNumber("page", 1);
|
|
97
100
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
101
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
98
102
|
if (hasChildren) {
|
|
99
103
|
return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
|
|
100
104
|
}
|
|
101
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search events by title or type", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); },
|
|
105
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search events by title or type", 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 Event"] }) }), 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 events found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No events 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" }) })] })] }), _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 Event" : "Edit Event", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminEventEditorView, { eventId: editId ?? undefined, onSaved: closePanel, embedded: true })) })] }));
|
|
102
106
|
}
|
|
@@ -6,91 +6,91 @@ export declare const EVENT_FILTER_KEYS: {
|
|
|
6
6
|
readonly public: readonly ["type", "status", "dateFrom", "dateTo"];
|
|
7
7
|
};
|
|
8
8
|
export declare const EVENT_ADMIN_SORT_OPTIONS: readonly [{
|
|
9
|
-
readonly value:
|
|
9
|
+
readonly value: string;
|
|
10
10
|
readonly label: "Title A–Z";
|
|
11
11
|
}, {
|
|
12
|
-
readonly value:
|
|
12
|
+
readonly value: string;
|
|
13
13
|
readonly label: "Title Z–A";
|
|
14
14
|
}, {
|
|
15
|
-
readonly value:
|
|
15
|
+
readonly value: string;
|
|
16
16
|
readonly label: "Starts Latest";
|
|
17
17
|
}, {
|
|
18
|
-
readonly value:
|
|
18
|
+
readonly value: string;
|
|
19
19
|
readonly label: "Starts Soonest";
|
|
20
20
|
}, {
|
|
21
|
-
readonly value:
|
|
21
|
+
readonly value: string;
|
|
22
22
|
readonly label: "Ends Latest";
|
|
23
23
|
}, {
|
|
24
|
-
readonly value:
|
|
24
|
+
readonly value: string;
|
|
25
25
|
readonly label: "Ends Soonest";
|
|
26
26
|
}, {
|
|
27
27
|
readonly value: "-stats.totalEntries";
|
|
28
28
|
readonly label: "Most Entries";
|
|
29
29
|
}, {
|
|
30
|
-
readonly value:
|
|
30
|
+
readonly value: string;
|
|
31
31
|
readonly label: "Newest First";
|
|
32
32
|
}];
|
|
33
33
|
export declare const EVENT_SELLER_SORT_OPTIONS: readonly [{
|
|
34
|
-
readonly value:
|
|
34
|
+
readonly value: string;
|
|
35
35
|
readonly label: "Title A–Z";
|
|
36
36
|
}, {
|
|
37
|
-
readonly value:
|
|
37
|
+
readonly value: string;
|
|
38
38
|
readonly label: "Title Z–A";
|
|
39
39
|
}, {
|
|
40
|
-
readonly value:
|
|
40
|
+
readonly value: string;
|
|
41
41
|
readonly label: "Starts Latest";
|
|
42
42
|
}, {
|
|
43
|
-
readonly value:
|
|
43
|
+
readonly value: string;
|
|
44
44
|
readonly label: "Starts Soonest";
|
|
45
45
|
}, {
|
|
46
|
-
readonly value:
|
|
46
|
+
readonly value: string;
|
|
47
47
|
readonly label: "Ends Latest";
|
|
48
48
|
}, {
|
|
49
|
-
readonly value:
|
|
49
|
+
readonly value: string;
|
|
50
50
|
readonly label: "Ends Soonest";
|
|
51
51
|
}, {
|
|
52
52
|
readonly value: "-stats.totalEntries";
|
|
53
53
|
readonly label: "Most Entries";
|
|
54
54
|
}, {
|
|
55
|
-
readonly value:
|
|
55
|
+
readonly value: string;
|
|
56
56
|
readonly label: "Newest First";
|
|
57
57
|
}];
|
|
58
58
|
export declare const EVENT_PUBLIC_SORT_OPTIONS: readonly [{
|
|
59
|
-
readonly value:
|
|
59
|
+
readonly value: string;
|
|
60
60
|
readonly label: "Starts Soonest";
|
|
61
61
|
}, {
|
|
62
|
-
readonly value:
|
|
62
|
+
readonly value: string;
|
|
63
63
|
readonly label: "Starts Latest";
|
|
64
64
|
}, {
|
|
65
|
-
readonly value:
|
|
65
|
+
readonly value: string;
|
|
66
66
|
readonly label: "Title A–Z";
|
|
67
67
|
}, {
|
|
68
68
|
readonly value: "-stats.totalEntries";
|
|
69
69
|
readonly label: "Most Entries";
|
|
70
70
|
}];
|
|
71
71
|
export declare const EVENT_SORT_OPTIONS: readonly [{
|
|
72
|
-
readonly value:
|
|
72
|
+
readonly value: string;
|
|
73
73
|
readonly label: "Title A–Z";
|
|
74
74
|
}, {
|
|
75
|
-
readonly value:
|
|
75
|
+
readonly value: string;
|
|
76
76
|
readonly label: "Title Z–A";
|
|
77
77
|
}, {
|
|
78
|
-
readonly value:
|
|
78
|
+
readonly value: string;
|
|
79
79
|
readonly label: "Starts Latest";
|
|
80
80
|
}, {
|
|
81
|
-
readonly value:
|
|
81
|
+
readonly value: string;
|
|
82
82
|
readonly label: "Starts Soonest";
|
|
83
83
|
}, {
|
|
84
|
-
readonly value:
|
|
84
|
+
readonly value: string;
|
|
85
85
|
readonly label: "Ends Latest";
|
|
86
86
|
}, {
|
|
87
|
-
readonly value:
|
|
87
|
+
readonly value: string;
|
|
88
88
|
readonly label: "Ends Soonest";
|
|
89
89
|
}, {
|
|
90
90
|
readonly value: "-stats.totalEntries";
|
|
91
91
|
readonly label: "Most Entries";
|
|
92
92
|
}, {
|
|
93
|
-
readonly value:
|
|
93
|
+
readonly value: string;
|
|
94
94
|
readonly label: "Newest First";
|
|
95
95
|
}];
|
|
96
96
|
export declare function getEventFilterKeys(variant: EventFilterVariant): readonly string[];
|
|
@@ -5,26 +5,29 @@ import { FilterFacetSection } from "../../filters/FilterFacetSection";
|
|
|
5
5
|
import { RangeFilter } from "../../filters/RangeFilter";
|
|
6
6
|
import { SwitchFilter } from "../../filters/SwitchFilter";
|
|
7
7
|
import { Div } from "../../../ui";
|
|
8
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
9
|
+
import { EVENT_FIELDS } from "../../../constants/field-names";
|
|
10
|
+
import { sortBy } from "../../../constants/sort";
|
|
8
11
|
export const EVENT_FILTER_KEYS = {
|
|
9
|
-
admin: ["type",
|
|
10
|
-
seller: ["type",
|
|
11
|
-
public: ["type",
|
|
12
|
+
admin: ["type", TABLE_KEYS.STATUS, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO],
|
|
13
|
+
seller: ["type", TABLE_KEYS.STATUS, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO],
|
|
14
|
+
public: ["type", TABLE_KEYS.STATUS, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO],
|
|
12
15
|
};
|
|
13
16
|
export const EVENT_ADMIN_SORT_OPTIONS = [
|
|
14
|
-
{ value: "
|
|
15
|
-
{ value:
|
|
16
|
-
{ value:
|
|
17
|
-
{ value: "
|
|
18
|
-
{ value:
|
|
19
|
-
{ value: "
|
|
17
|
+
{ value: sortBy(EVENT_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
|
|
18
|
+
{ value: sortBy(EVENT_FIELDS.TITLE), label: "Title Z–A" },
|
|
19
|
+
{ value: sortBy(EVENT_FIELDS.STARTS_AT), label: "Starts Latest" },
|
|
20
|
+
{ value: sortBy(EVENT_FIELDS.STARTS_AT, "ASC"), label: "Starts Soonest" },
|
|
21
|
+
{ value: sortBy(EVENT_FIELDS.ENDS_AT), label: "Ends Latest" },
|
|
22
|
+
{ value: sortBy(EVENT_FIELDS.ENDS_AT, "ASC"), label: "Ends Soonest" },
|
|
20
23
|
{ value: "-stats.totalEntries", label: "Most Entries" },
|
|
21
|
-
{ value:
|
|
24
|
+
{ value: sortBy(EVENT_FIELDS.CREATED_AT), label: "Newest First" },
|
|
22
25
|
];
|
|
23
26
|
export const EVENT_SELLER_SORT_OPTIONS = EVENT_ADMIN_SORT_OPTIONS;
|
|
24
27
|
export const EVENT_PUBLIC_SORT_OPTIONS = [
|
|
25
|
-
{ value: "
|
|
26
|
-
{ value:
|
|
27
|
-
{ value: "
|
|
28
|
+
{ value: sortBy(EVENT_FIELDS.STARTS_AT, "ASC"), label: "Starts Soonest" },
|
|
29
|
+
{ value: sortBy(EVENT_FIELDS.STARTS_AT), label: "Starts Latest" },
|
|
30
|
+
{ value: sortBy(EVENT_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
|
|
28
31
|
{ value: "-stats.totalEntries", label: "Most Entries" },
|
|
29
32
|
];
|
|
30
33
|
// Backward-compatible alias.
|
|
@@ -5,6 +5,7 @@ const STATUS_MAP = {
|
|
|
5
5
|
active: { label: "Active", color: "bg-green-100 text-green-700" },
|
|
6
6
|
paused: { label: "Paused", color: "bg-yellow-100 text-yellow-700" },
|
|
7
7
|
ended: { label: "Ended", color: "bg-red-100 text-red-600" },
|
|
8
|
+
cancelled: { label: "Cancelled", color: "bg-red-100 text-red-700" },
|
|
8
9
|
};
|
|
9
10
|
export function EventStatusBadge({ status, className = "", }) {
|
|
10
11
|
const { label, color } = STATUS_MAP[status] ?? STATUS_MAP.draft;
|