@mohasinac/appkit 2.7.49 → 2.7.52
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/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
- package/dist/_internal/client/features/layout/index.d.ts +2 -0
- package/dist/_internal/client/features/layout/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.js +25 -2
- package/dist/_internal/server/features/promotions/actions.js +3 -1
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/_internal/shared/actions/action-registry.js +149 -0
- package/dist/client.d.ts +45 -6
- package/dist/client.js +27 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/account/components/UserSidebar.js +2 -1
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +102 -75
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSidebar.js +2 -1
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/auth/repository/session.repository.js +16 -1
- package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
- package/dist/features/cart/hooks/useAddToCart.js +2 -0
- package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
- package/dist/features/cart/utils/guest-cart.d.ts +7 -0
- package/dist/features/cart/utils/pending-ops.d.ts +6 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -25
- package/dist/features/categories/components/CategoryProductsListing.js +6 -10
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/categories/schemas/firestore.d.ts +9 -0
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
- package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
- package/dist/features/events/components/SpinWheelView.js +3 -3
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/layout/BottomNavLayout.d.ts +5 -0
- package/dist/features/layout/BottomNavLayout.js +21 -0
- package/dist/features/layout/TitleBar.js +7 -1
- package/dist/features/layout/TitleBarLayout.d.ts +8 -2
- package/dist/features/layout/TitleBarLayout.js +11 -7
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +12 -6
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
- package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
- package/dist/features/products/components/ProductDetailActions.js +3 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
- package/dist/features/products/components/ProductDetailPageView.js +4 -0
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +18 -9
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/components/CouponCard.d.ts +11 -1
- package/dist/features/promotions/components/CouponCard.js +36 -2
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
- package/dist/features/promotions/schemas/firestore.d.ts +34 -0
- package/dist/features/promotions/schemas/firestore.js +14 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAddressesView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBidsView.js +33 -3
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +53 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +3 -1
- package/dist/features/seller/components/SellerProductShell.js +35 -11
- package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
- package/dist/features/seller/components/SellerProductsCards.js +19 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +22 -0
- package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
- package/dist/features/seller/components/SellerProductsView.js +92 -49
- package/dist/features/seller/components/SellerReviewsView.js +67 -4
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerSidebar.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
- package/dist/features/seller/components/seller-products-styles.js +14 -0
- package/dist/features/shell/FormShell.d.ts +7 -1
- package/dist/features/shell/FormShell.js +5 -2
- package/dist/features/store-extensions/index.d.ts +4 -0
- package/dist/features/store-extensions/index.js +4 -0
- package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
- package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
- package/dist/features/store-extensions/schemas/firestore.js +158 -0
- package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
- package/dist/features/store-extensions/schemas/rbac.js +43 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreProductsListing.js +6 -10
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +2 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +60 -9
- package/dist/index.js +66 -15
- package/dist/next/routing/route-map.d.ts +134 -0
- package/dist/next/routing/route-map.js +59 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
- package/dist/react/hooks/useFormStatePreservation.js +62 -0
- package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
- package/dist/react/hooks/useInlineRowEdit.js +68 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +4 -0
- package/dist/repositories/index.d.ts +5 -0
- package/dist/repositories/index.js +7 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
- package/dist/seed/claimed-coupons-seed-data.js +79 -0
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +3 -0
- package/dist/seed/manifest.js +17 -0
- package/dist/seed/store-extensions-seed-data.d.ts +19 -0
- package/dist/seed/store-extensions-seed-data.js +421 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.style.css +20 -12
- package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
- package/dist/ui/components/ClaimCouponButton.js +89 -0
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/QuickCreateModal.d.ts +15 -0
- package/dist/ui/components/QuickCreateModal.js +48 -0
- package/dist/ui/components/QuickCreateModal.style.css +84 -0
- package/dist/ui/components/StickyBottomBar.d.ts +22 -0
- package/dist/ui/components/StickyBottomBar.js +20 -0
- package/dist/ui/components/VacationBanner.d.ts +11 -0
- package/dist/ui/components/VacationBanner.js +16 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +9 -1
- package/dist/ui/index.js +4 -0
- package/package.json +8 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { Alert, Badge, Button, Div, Form, FormActions, Heading, Input, Row, Section, Select, Text, Toggle, useToast, } from "../../../ui";
|
|
6
|
+
import { apiClient } from "../../../http";
|
|
7
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Config
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const OPERATOR_OPTIONS = [
|
|
12
|
+
{ value: ">", label: "Greater than (>)" },
|
|
13
|
+
{ value: "<", label: "Less than (<)" },
|
|
14
|
+
{ value: ">=", label: "≥" },
|
|
15
|
+
{ value: "<=", label: "≤" },
|
|
16
|
+
{ value: "==", label: "Equals (==)" },
|
|
17
|
+
];
|
|
18
|
+
const METRIC_OPTIONS = [
|
|
19
|
+
{ value: "daily_revenue", label: "Daily Revenue (₹)" },
|
|
20
|
+
{ value: "daily_orders", label: "Daily Orders" },
|
|
21
|
+
{ value: "low_stock", label: "Low Stock (products)" },
|
|
22
|
+
{ value: "cancelled_orders", label: "Cancelled Orders" },
|
|
23
|
+
{ value: "pending_payouts", label: "Pending Payouts (₹)" },
|
|
24
|
+
{ value: "avg_rating", label: "Average Rating" },
|
|
25
|
+
{ value: "reviews_count", label: "New Reviews" },
|
|
26
|
+
];
|
|
27
|
+
const WINDOW_OPTIONS = [
|
|
28
|
+
{ value: "1", label: "Last 1 hour" },
|
|
29
|
+
{ value: "6", label: "Last 6 hours" },
|
|
30
|
+
{ value: "24", label: "Last 24 hours" },
|
|
31
|
+
{ value: "168", label: "Last 7 days" },
|
|
32
|
+
];
|
|
33
|
+
const CHANNEL_OPTIONS = [
|
|
34
|
+
{ value: "in-app", label: "In-App notification" },
|
|
35
|
+
{ value: "email", label: "Email" },
|
|
36
|
+
{ value: "whatsapp", label: "WhatsApp" },
|
|
37
|
+
];
|
|
38
|
+
const OPERATOR_BADGE = {
|
|
39
|
+
">": "danger",
|
|
40
|
+
"<": "warning",
|
|
41
|
+
">=": "danger",
|
|
42
|
+
"<=": "warning",
|
|
43
|
+
"==": "info",
|
|
44
|
+
"!=": "default",
|
|
45
|
+
};
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Sub-components
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
function AlertCard({ alert, onToggle, onDelete, }) {
|
|
50
|
+
const metricLabel = METRIC_OPTIONS.find((m) => m.value === alert.metric)?.label ?? alert.metric;
|
|
51
|
+
const opLabel = OPERATOR_OPTIONS.find((o) => o.value === alert.operator)?.label ?? alert.operator;
|
|
52
|
+
const windowLabel = WINDOW_OPTIONS.find((w) => w.value === String(alert.windowHours))?.label ?? `${alert.windowHours}h`;
|
|
53
|
+
return (_jsxs(Div, { className: "flex items-start justify-between gap-4 p-4 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "items-center gap-2 mb-1 flex-wrap", children: [_jsx(Text, { className: "font-medium text-zinc-900 dark:text-zinc-100 text-sm", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), _jsx(Row, { className: "gap-1 flex-wrap", children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500 mt-1", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDelete(alert.id), className: "text-red-500 hover:text-red-600", children: "Delete" })] })] }));
|
|
54
|
+
}
|
|
55
|
+
const EMPTY_DRAFT = {
|
|
56
|
+
label: "",
|
|
57
|
+
metric: "daily_orders",
|
|
58
|
+
operator: ">",
|
|
59
|
+
threshold: "",
|
|
60
|
+
windowHours: "24",
|
|
61
|
+
notifyChannels: ["in-app"],
|
|
62
|
+
};
|
|
63
|
+
export function SellerAnalyticsAlertsView({ labels = {}, }) {
|
|
64
|
+
const queryClient = useQueryClient();
|
|
65
|
+
const { showToast } = useToast();
|
|
66
|
+
const [showForm, setShowForm] = useState(false);
|
|
67
|
+
const [draft, setDraft] = useState(EMPTY_DRAFT);
|
|
68
|
+
const { data, isLoading } = useQuery({
|
|
69
|
+
queryKey: ["seller", "analytics-alerts"],
|
|
70
|
+
queryFn: async () => {
|
|
71
|
+
const res = await apiClient.get(SELLER_ENDPOINTS.ANALYTICS_ALERTS);
|
|
72
|
+
return (res?.items ?? []);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const alerts = data ?? [];
|
|
76
|
+
const createMutation = useMutation({
|
|
77
|
+
mutationFn: async () => {
|
|
78
|
+
const payload = {
|
|
79
|
+
label: draft.label,
|
|
80
|
+
metric: draft.metric,
|
|
81
|
+
operator: draft.operator,
|
|
82
|
+
threshold: Number(draft.threshold),
|
|
83
|
+
windowHours: Number(draft.windowHours),
|
|
84
|
+
notifyChannels: draft.notifyChannels,
|
|
85
|
+
scope: "seller",
|
|
86
|
+
isActive: true,
|
|
87
|
+
};
|
|
88
|
+
return apiClient.post(SELLER_ENDPOINTS.ANALYTICS_ALERTS, payload);
|
|
89
|
+
},
|
|
90
|
+
onSuccess: () => {
|
|
91
|
+
showToast("Alert created", "success");
|
|
92
|
+
void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
|
|
93
|
+
setShowForm(false);
|
|
94
|
+
setDraft(EMPTY_DRAFT);
|
|
95
|
+
},
|
|
96
|
+
onError: () => showToast("Failed to create alert", "error"),
|
|
97
|
+
});
|
|
98
|
+
const toggleMutation = useMutation({
|
|
99
|
+
mutationFn: async ({ id, isActive }) => apiClient.patch(SELLER_ENDPOINTS.ANALYTICS_ALERT_BY_ID(id), { isActive }),
|
|
100
|
+
onSuccess: () => {
|
|
101
|
+
void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
|
|
102
|
+
},
|
|
103
|
+
onError: () => showToast("Failed to update alert", "error"),
|
|
104
|
+
});
|
|
105
|
+
const deleteMutation = useMutation({
|
|
106
|
+
mutationFn: async (id) => apiClient.delete(SELLER_ENDPOINTS.ANALYTICS_ALERT_BY_ID(id)),
|
|
107
|
+
onSuccess: () => {
|
|
108
|
+
showToast("Alert deleted", "success");
|
|
109
|
+
void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
|
|
110
|
+
},
|
|
111
|
+
onError: () => showToast("Failed to delete alert", "error"),
|
|
112
|
+
});
|
|
113
|
+
const toggleChannel = useCallback((ch) => {
|
|
114
|
+
setDraft((d) => ({
|
|
115
|
+
...d,
|
|
116
|
+
notifyChannels: d.notifyChannels.includes(ch)
|
|
117
|
+
? d.notifyChannels.filter((c) => c !== ch)
|
|
118
|
+
: [...d.notifyChannels, ch],
|
|
119
|
+
}));
|
|
120
|
+
}, []);
|
|
121
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Row, { className: "items-center justify-between", children: [_jsx(Heading, { level: 2, className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: labels.title ?? "Analytics Alerts" }), _jsx(Button, { size: "sm", onClick: () => setShowForm((v) => !v), children: showForm ? "Cancel" : "+ New Alert" })] }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Get notified when a key metric crosses a threshold. Alerts check the metric over the selected window and fire through your chosen channels." }), showForm && (_jsxs(Section, { className: "border border-zinc-200 dark:border-zinc-700 rounded-xl p-5 space-y-4", children: [_jsx(Heading, { level: 3, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Create Alert" }), _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); createMutation.mutate(); }, children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Alert label", value: draft.label, onChange: (e) => setDraft((d) => ({ ...d, label: e.target.value })), placeholder: "e.g. Low daily orders", required: true }), _jsx(Select, { label: "Metric", value: draft.metric, onValueChange: (v) => setDraft((d) => ({ ...d, metric: v })), options: METRIC_OPTIONS }), _jsx(Select, { label: "Operator", value: draft.operator, onValueChange: (v) => setDraft((d) => ({ ...d, operator: v })), options: OPERATOR_OPTIONS }), _jsx(Input, { label: "Threshold", type: "number", value: draft.threshold, onChange: (e) => setDraft((d) => ({ ...d, threshold: e.target.value })), placeholder: "e.g. 10", required: true }), _jsx(Select, { label: "Check window", value: draft.windowHours, onValueChange: (v) => setDraft((d) => ({ ...d, windowHours: v })), options: WINDOW_OPTIONS })] }), _jsxs("div", { className: "mt-3", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300 mb-2", children: "Notify via" }), _jsx(Row, { className: "gap-2 flex-wrap", children: CHANNEL_OPTIONS.map((ch) => (_jsx("button", { type: "button", onClick: () => toggleChannel(ch.value), className: `px-3 py-1.5 rounded-full text-xs border transition-colors ${draft.notifyChannels.includes(ch.value)
|
|
122
|
+
? "bg-[var(--appkit-color-primary)] text-white border-transparent"
|
|
123
|
+
: "bg-white dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 border-zinc-200 dark:border-zinc-700"}`, children: ch.label }, ch.value))) })] }), _jsx(FormActions, { align: "right", className: "mt-4", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, children: createMutation.isPending ? "Creating…" : "Create Alert" }) })] })] })), isLoading && (_jsx("div", { className: "space-y-2", children: [1, 2, 3].map((i) => (_jsx("div", { className: "h-16 rounded-lg bg-zinc-100 dark:bg-zinc-800 animate-pulse" }, i))) })), !isLoading && alerts.length === 0 && !showForm && (_jsx(Alert, { variant: "info", children: "No alerts configured. Create an alert to get notified when a key metric crosses a threshold." })), !isLoading && alerts.length > 0 && (_jsx("div", { className: "space-y-3", children: alerts.map((alert) => (_jsx(AlertCard, { alert: alert, onToggle: (id, isActive) => toggleMutation.mutate({ id, isActive }), onDelete: (id) => deleteMutation.mutate(id) }, alert.id))) }))] }));
|
|
124
|
+
}
|
|
@@ -3,6 +3,8 @@ import type { StackedViewShellProps } from "../../../ui";
|
|
|
3
3
|
export interface SellerAnalyticsViewProps extends Omit<StackedViewShellProps, "sections"> {
|
|
4
4
|
labels?: {
|
|
5
5
|
title?: string;
|
|
6
|
+
dateFrom?: string;
|
|
7
|
+
dateTo?: string;
|
|
6
8
|
};
|
|
7
9
|
renderDateRange?: (from: string, to: string, onChange: (from: string, to: string) => void) => React.ReactNode;
|
|
8
10
|
renderStats?: (isLoading: boolean) => React.ReactNode;
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
|
-
import { StackedViewShell } from "../../../ui";
|
|
4
|
+
import { StackedViewShell, Row, Text } from "../../../ui";
|
|
5
|
+
function DefaultDateRange({ from, to, onChange, labels, }) {
|
|
6
|
+
return (_jsxs(Row, { gap: "md", align: "center", wrap: true, className: "flex-wrap", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsx(Text, { size: "sm", variant: "secondary", children: labels.dateFrom ?? "From" }), _jsx("input", { type: "date", value: from, onChange: (e) => onChange(e.target.value, to), className: "appkit-input text-sm py-1.5 px-3" })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsx(Text, { size: "sm", variant: "secondary", children: labels.dateTo ?? "To" }), _jsx("input", { type: "date", value: to, onChange: (e) => onChange(from, e.target.value), className: "appkit-input text-sm py-1.5 px-3" })] })] }));
|
|
7
|
+
}
|
|
5
8
|
export function SellerAnalyticsView({ labels = {}, renderDateRange, renderStats, renderCharts, renderTopProducts, isLoading = false, ...rest }) {
|
|
6
9
|
const [from, setFrom] = React.useState("");
|
|
7
10
|
const [to, setTo] = React.useState("");
|
|
11
|
+
const handleDateChange = (f, t) => {
|
|
12
|
+
setFrom(f);
|
|
13
|
+
setTo(t);
|
|
14
|
+
};
|
|
15
|
+
const dateRange = (renderDateRange ?? ((f, t, onCh) => (_jsx(DefaultDateRange, { from: f, to: t, onChange: onCh, labels: labels }))))(from, to, handleDateChange);
|
|
16
|
+
const statsSection = (renderStats ?? (() => null))(isLoading);
|
|
17
|
+
const chartsSection = (renderCharts ?? (() => null))();
|
|
18
|
+
const topProducts = (renderTopProducts ?? (() => null))();
|
|
8
19
|
return (_jsx(StackedViewShell, { portal: "seller", ...rest, title: labels.title, sections: [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
renderStats?.(isLoading),
|
|
14
|
-
renderCharts?.(),
|
|
15
|
-
renderTopProducts?.(),
|
|
20
|
+
dateRange,
|
|
21
|
+
statsSection,
|
|
22
|
+
chartsSection,
|
|
23
|
+
topProducts,
|
|
16
24
|
] }));
|
|
17
25
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback } from "react";
|
|
3
|
+
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
-
import { Badge, Div, FilterChipGroup, ListingToolbar, Pagination, Text } from "../../../ui";
|
|
7
|
+
import { Badge, BulkActionBar, Div, FilterChipGroup, ListingToolbar, Pagination, Text } from "../../../ui";
|
|
8
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
9
|
import { SELLER_BID_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
10
10
|
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
@@ -122,5 +122,35 @@ export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
|
|
|
122
122
|
},
|
|
123
123
|
];
|
|
124
124
|
const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
|
|
125
|
-
|
|
125
|
+
// S-STORE-4-B — group bids by productId (auction) with collapsible sections.
|
|
126
|
+
const [collapsedGroups, setCollapsedGroups] = useState(new Set());
|
|
127
|
+
const groupedRows = React.useMemo(() => {
|
|
128
|
+
const groups = new Map();
|
|
129
|
+
for (const row of rows ?? []) {
|
|
130
|
+
const key = row.productId || row.productTitle;
|
|
131
|
+
const g = groups.get(key) ?? { title: row.productTitle, bids: [] };
|
|
132
|
+
g.bids.push(row);
|
|
133
|
+
groups.set(key, g);
|
|
134
|
+
}
|
|
135
|
+
return Array.from(groups.entries()).map(([id, g]) => ({ id, ...g }));
|
|
136
|
+
}, [rows]);
|
|
137
|
+
const toggleGroup = (id) => setCollapsedGroups((s) => {
|
|
138
|
+
const next = new Set(s);
|
|
139
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
140
|
+
return next;
|
|
141
|
+
});
|
|
142
|
+
// S-STORE-4-B — bulk actions: cancel / retract.
|
|
143
|
+
const bulkCancel = useCallback(async () => {
|
|
144
|
+
if (!selection.selectedIds.length)
|
|
145
|
+
return;
|
|
146
|
+
await Promise.all(selection.selectedIds.map((id) => fetch(`/api/store/bids/${id}`, { method: "DELETE" }).catch(() => null)));
|
|
147
|
+
selection.clearSelection();
|
|
148
|
+
}, [selection]);
|
|
149
|
+
const bulkActions = [
|
|
150
|
+
{ id: "cancel", label: "Cancel selected", onClick: () => void bulkCancel(), variant: "danger" },
|
|
151
|
+
];
|
|
152
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by bidder name", 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) }) })), selection.selectedIds.length > 0 && (_jsx("div", { className: "sticky z-20 px-3 sm:px-4 py-2 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700", style: { top: "calc(var(--header-height, 0px) + 88px)" }, children: _jsx(BulkActionBar, { selectedCount: selection.selectedIds.length, onClearSelection: selection.clearSelection, actions: bulkActions }) })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), table.get("grouped") === "0" ? (_jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No bids found for your auctions", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: () => selection.toggleAll() })) : (_jsxs("div", { className: "space-y-3", children: [groupedRows.length === 0 && !isLoading && (_jsx(Text, { className: "text-sm text-zinc-500", children: "No bids found for your auctions." })), groupedRows.map((group) => {
|
|
153
|
+
const collapsed = collapsedGroups.has(group.id);
|
|
154
|
+
return (_jsxs(Div, { className: "rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: [_jsxs("button", { type: "button", onClick: () => toggleGroup(group.id), className: "w-full flex items-center justify-between px-4 py-2.5 text-sm font-semibold text-[var(--appkit-color-text)] hover:bg-[var(--appkit-color-surface-raised)]", children: [_jsxs("span", { className: "truncate", children: [group.title, " ", _jsxs("span", { className: "text-xs text-[var(--appkit-color-text-muted)] font-normal", children: ["\u00B7 ", group.bids.length, " bid", group.bids.length === 1 ? "" : "s"] })] }), _jsx("span", { "aria-hidden": true, className: "text-[var(--appkit-color-text-muted)]", children: collapsed ? "▸" : "▾" })] }), !collapsed && (_jsx("div", { className: "border-t border-[var(--appkit-color-border)]", children: _jsx(DataTable, { rows: group.bids, columns: columns, isLoading: false, emptyLabel: "", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle }) }))] }, group.id));
|
|
155
|
+
})] }))] }), 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 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: "Bid Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })), allId: "" }) }), _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-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
126
156
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
8
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ROUTES } from "../../..";
|
|
10
|
+
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
12
|
+
const PAGE_SIZE = 25;
|
|
13
|
+
const DEFAULT_SORT = "-createdAt";
|
|
14
|
+
const SORT_OPTIONS = [
|
|
15
|
+
{ value: "-createdAt", label: "Newest" },
|
|
16
|
+
{ value: "createdAt", label: "Oldest" },
|
|
17
|
+
{ value: "productTitle", label: "Name A–Z" },
|
|
18
|
+
{ value: "-productTitle", label: "Name Z–A" },
|
|
19
|
+
{ value: "price", label: "Price: Low–High" },
|
|
20
|
+
{ value: "-price", label: "Price: High–Low" },
|
|
21
|
+
];
|
|
22
|
+
const COLUMNS = [
|
|
23
|
+
{
|
|
24
|
+
key: "title",
|
|
25
|
+
header: "Bundle",
|
|
26
|
+
render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "price",
|
|
30
|
+
header: "Price",
|
|
31
|
+
render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "itemCount",
|
|
35
|
+
header: "Items",
|
|
36
|
+
render: (row) => (_jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300 tabular-nums", children: row.itemCount })),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "status",
|
|
40
|
+
header: "Status",
|
|
41
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
|
|
42
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
43
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "createdAt",
|
|
47
|
+
header: "Created",
|
|
48
|
+
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
export function SellerBundlesView({ onCreateClick, onBulkDelete, }) {
|
|
52
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
53
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
54
|
+
const [view] = useState("table");
|
|
55
|
+
const commitSearch = useCallback(() => {
|
|
56
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
57
|
+
}, [searchInput, table]);
|
|
58
|
+
const resetAll = useCallback(() => {
|
|
59
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
60
|
+
setSearchInput("");
|
|
61
|
+
}, [table]);
|
|
62
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
63
|
+
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
64
|
+
queryKey: ["seller", "bundles"],
|
|
65
|
+
endpoint: SELLER_ENDPOINTS.PRODUCTS,
|
|
66
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
67
|
+
pageSize: PAGE_SIZE,
|
|
68
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
69
|
+
filters: "listingType==bundle",
|
|
70
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
71
|
+
mapRows: (response) => toRecordArray(response.products).map((item, index) => ({
|
|
72
|
+
id: toStringValue(item.id, `bundle-${index}`),
|
|
73
|
+
raw: item,
|
|
74
|
+
title: toStringValue(item.productTitle ?? item.title, "Untitled bundle"),
|
|
75
|
+
price: toRupees(item.price),
|
|
76
|
+
itemCount: Array.isArray(item.bundleProductIds)
|
|
77
|
+
? item.bundleProductIds.length
|
|
78
|
+
: Number(item.bundleItemCount ?? 0),
|
|
79
|
+
status: toStringValue(item.status, "draft"),
|
|
80
|
+
createdAt: toRelativeDate(item.createdAt),
|
|
81
|
+
})),
|
|
82
|
+
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
83
|
+
});
|
|
84
|
+
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
85
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
86
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
87
|
+
const bulkActions = onBulkDelete
|
|
88
|
+
? [{
|
|
89
|
+
id: "bulk-delete",
|
|
90
|
+
label: "Delete selected",
|
|
91
|
+
variant: "danger",
|
|
92
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
93
|
+
}]
|
|
94
|
+
: [];
|
|
95
|
+
const handleCreate = useCallback(() => {
|
|
96
|
+
if (onCreateClick) {
|
|
97
|
+
onCreateClick();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
window.location.href = String(ROUTES.STORE.PRODUCTS_NEW);
|
|
101
|
+
}
|
|
102
|
+
}, [onCreateClick]);
|
|
103
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Bundle" })] }) }), 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) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No bundles yet \u2014 create a bundle to group multiple products together" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
|
|
104
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface SellerClassifiedViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function SellerClassifiedView({ onCreateClick, onBulkDelete, }: SellerClassifiedViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
8
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ROUTES } from "../../..";
|
|
10
|
+
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
|
+
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
12
|
+
const PAGE_SIZE = 25;
|
|
13
|
+
const DEFAULT_SORT = "-createdAt";
|
|
14
|
+
const SORT_OPTIONS = [
|
|
15
|
+
{ value: "-createdAt", label: "Newest" },
|
|
16
|
+
{ value: "createdAt", label: "Oldest" },
|
|
17
|
+
{ value: "productTitle", label: "Name A–Z" },
|
|
18
|
+
{ value: "price", label: "Price: Low–High" },
|
|
19
|
+
{ value: "-price", label: "Price: High–Low" },
|
|
20
|
+
];
|
|
21
|
+
const COLUMNS = [
|
|
22
|
+
{
|
|
23
|
+
key: "title",
|
|
24
|
+
header: "Listing",
|
|
25
|
+
render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: "price",
|
|
29
|
+
header: "Price",
|
|
30
|
+
render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "city",
|
|
34
|
+
header: "Location",
|
|
35
|
+
render: (row) => (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.city || "—" })),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "acceptsShipping",
|
|
39
|
+
header: "Shipping",
|
|
40
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.acceptsShipping
|
|
41
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
42
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.acceptsShipping ? "Ships" : "Meetup only" })),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "status",
|
|
46
|
+
header: "Status",
|
|
47
|
+
render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
|
|
48
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
|
|
49
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "createdAt",
|
|
53
|
+
header: "Created",
|
|
54
|
+
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
export function SellerClassifiedView({ onCreateClick, onBulkDelete, }) {
|
|
58
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
59
|
+
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
60
|
+
const [view] = useState("table");
|
|
61
|
+
const commitSearch = useCallback(() => {
|
|
62
|
+
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
63
|
+
}, [searchInput, table]);
|
|
64
|
+
const resetAll = useCallback(() => {
|
|
65
|
+
table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
66
|
+
setSearchInput("");
|
|
67
|
+
}, [table]);
|
|
68
|
+
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
|
|
69
|
+
const { rows, total, isLoading, errorMessage } = useSellerListingData({
|
|
70
|
+
queryKey: ["seller", "classified"],
|
|
71
|
+
endpoint: SELLER_ENDPOINTS.PRODUCTS,
|
|
72
|
+
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
73
|
+
pageSize: PAGE_SIZE,
|
|
74
|
+
sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
|
|
75
|
+
filters: "listingType==classified",
|
|
76
|
+
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
77
|
+
mapRows: (response) => toRecordArray(response.products).map((item, index) => {
|
|
78
|
+
const classified = (item.classified ?? {});
|
|
79
|
+
const meetupArea = (classified.meetupArea ?? {});
|
|
80
|
+
return {
|
|
81
|
+
id: toStringValue(item.id, `classified-${index}`),
|
|
82
|
+
raw: item,
|
|
83
|
+
title: toStringValue(item.productTitle ?? item.title, "Untitled"),
|
|
84
|
+
price: toRupees(item.price),
|
|
85
|
+
city: toStringValue(meetupArea.city ?? classified.city, ""),
|
|
86
|
+
acceptsShipping: Boolean(classified.acceptsShipping),
|
|
87
|
+
status: toStringValue(item.status, "draft"),
|
|
88
|
+
createdAt: toRelativeDate(item.createdAt),
|
|
89
|
+
};
|
|
90
|
+
}),
|
|
91
|
+
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
92
|
+
});
|
|
93
|
+
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
94
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
95
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
96
|
+
const bulkActions = onBulkDelete
|
|
97
|
+
? [{
|
|
98
|
+
id: "bulk-delete",
|
|
99
|
+
label: "Delete selected",
|
|
100
|
+
variant: "danger",
|
|
101
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
102
|
+
}]
|
|
103
|
+
: [];
|
|
104
|
+
const handleCreate = useCallback(() => {
|
|
105
|
+
if (onCreateClick) {
|
|
106
|
+
onCreateClick();
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
window.location.href = String(ROUTES.STORE.CLASSIFIED_NEW);
|
|
110
|
+
}
|
|
111
|
+
}, [onCreateClick]);
|
|
112
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search classified listings...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Classified" })] }) }), 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) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No classified listings yet \u2014 post your first buy/sell/trade ad" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
|
|
113
|
+
}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { StackedViewShell, Div, Text } from "../../../ui";
|
|
4
|
+
function DefaultStatsPlaceholder({ isLoading }) {
|
|
5
|
+
return (_jsx("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [1, 2, 3, 4].map((i) => (_jsx(Div, { className: "rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-5", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-3 w-16 animate-pulse rounded bg-[var(--appkit-color-border)]" }), _jsx("div", { className: "mt-3 h-6 w-20 animate-pulse rounded bg-[var(--appkit-color-border)]" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { size: "xs", variant: "secondary", className: "uppercase tracking-widest font-semibold", children: ["Stat ", i] }), _jsx(Text, { size: "xl", className: "mt-2 font-bold tabular-nums", children: "\u2014" })] })) }, i))) }));
|
|
6
|
+
}
|
|
7
|
+
function DefaultQuickActionsPlaceholder() {
|
|
8
|
+
return (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: [1, 2, 3, 4].map((i) => (_jsx(Div, { className: "h-12 animate-pulse rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-border)]" }, i))) }));
|
|
9
|
+
}
|
|
3
10
|
export function SellerDashboardView({ labels = {}, renderStats, renderQuickActions, renderRecentListings, renderRevenueChart, renderTopProducts, isLoading = false, ...rest }) {
|
|
11
|
+
const stats = (renderStats ?? ((busy) => _jsx(DefaultStatsPlaceholder, { isLoading: busy })))(isLoading);
|
|
12
|
+
const quickActions = (renderQuickActions ?? (() => _jsx(DefaultQuickActionsPlaceholder, {})))();
|
|
13
|
+
const revenueChart = (renderRevenueChart ?? (() => null))();
|
|
14
|
+
const topProducts = (renderTopProducts ?? (() => null))();
|
|
15
|
+
const recentListings = (renderRecentListings ?? (() => null))();
|
|
4
16
|
return (_jsx(StackedViewShell, { portal: "seller", ...rest, title: labels.title, sections: [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
stats,
|
|
18
|
+
quickActions,
|
|
19
|
+
revenueChart,
|
|
20
|
+
topProducts,
|
|
21
|
+
recentListings,
|
|
10
22
|
] }));
|
|
11
23
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface SellerDigitalCodesViewProps {
|
|
2
|
+
onCreateClick?: () => void;
|
|
3
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function SellerDigitalCodesView({ onCreateClick, onBulkDelete, }: SellerDigitalCodesViewProps): import("react/jsx-runtime").JSX.Element;
|