Add filters
This commit is contained in:
@@ -40,6 +40,14 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
const METRIC_IDS = {
|
||||
PLACED_ORDER: "Y8cqcF",
|
||||
@@ -1121,6 +1129,23 @@ const EventFeed = ({
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [lastUpdate, setLastUpdate] = useState(null);
|
||||
const [activeEventTypes, setActiveEventTypes] = useState({
|
||||
[METRIC_IDS.PLACED_ORDER]: true,
|
||||
[METRIC_IDS.SHIPPED_ORDER]: true,
|
||||
[METRIC_IDS.ACCOUNT_CREATED]: true,
|
||||
[METRIC_IDS.CANCELED_ORDER]: true,
|
||||
[METRIC_IDS.PAYMENT_REFUNDED]: true,
|
||||
[METRIC_IDS.NEW_BLOG_POST]: true,
|
||||
});
|
||||
const [orderFilters, setOrderFilters] = useState({
|
||||
hasPreorder: false,
|
||||
localPickup: false,
|
||||
isOnHold: false,
|
||||
hasDigiItem: false,
|
||||
hasNotions: false,
|
||||
hasGiftCard: false,
|
||||
stillOwes: false,
|
||||
});
|
||||
|
||||
const fetchEvents = useCallback(async () => {
|
||||
try {
|
||||
@@ -1168,40 +1193,357 @@ const EventFeed = ({
|
||||
};
|
||||
}, [fetchEvents]);
|
||||
|
||||
const filteredEvents = useMemo(() => {
|
||||
// Check if any order property filters are active
|
||||
const hasActiveOrderFilters = Object.values(orderFilters).some(filter => filter);
|
||||
|
||||
return events.filter(event => {
|
||||
// First check event type filter
|
||||
if (!activeEventTypes[event.metric_id]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then check order property filters if any are active
|
||||
if (hasActiveOrderFilters) {
|
||||
if (event.metric_id !== METRIC_IDS.PLACED_ORDER) return false;
|
||||
|
||||
const details = event.event_properties || {};
|
||||
if (orderFilters.hasPreorder && !details.HasPreorder) return false;
|
||||
if (orderFilters.localPickup && !details.LocalPickup) return false;
|
||||
if (orderFilters.isOnHold && !details.IsOnHold) return false;
|
||||
if (orderFilters.hasDigiItem && !details.HasDigiItem) return false;
|
||||
if (orderFilters.hasNotions && !details.HasNotions) return false;
|
||||
if (orderFilters.hasGiftCard && !details.HasDigitalGC) return false;
|
||||
if (orderFilters.stillOwes && !details.StillOwes) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [events, activeEventTypes, orderFilters]);
|
||||
|
||||
// Calculate counts for event types and order properties
|
||||
const counts = useMemo(() => {
|
||||
const eventTypeCounts = {
|
||||
[METRIC_IDS.PLACED_ORDER]: 0,
|
||||
[METRIC_IDS.SHIPPED_ORDER]: 0,
|
||||
[METRIC_IDS.ACCOUNT_CREATED]: 0,
|
||||
[METRIC_IDS.CANCELED_ORDER]: 0,
|
||||
[METRIC_IDS.PAYMENT_REFUNDED]: 0,
|
||||
[METRIC_IDS.NEW_BLOG_POST]: 0,
|
||||
};
|
||||
|
||||
const orderPropertyCounts = {
|
||||
hasPreorder: 0,
|
||||
localPickup: 0,
|
||||
isOnHold: 0,
|
||||
hasDigiItem: 0,
|
||||
hasNotions: 0,
|
||||
hasGiftCard: 0,
|
||||
stillOwes: 0,
|
||||
};
|
||||
|
||||
events.forEach(event => {
|
||||
// Count event types
|
||||
if (event.metric_id) {
|
||||
eventTypeCounts[event.metric_id]++;
|
||||
}
|
||||
|
||||
// Count order properties
|
||||
if (event.metric_id === METRIC_IDS.PLACED_ORDER) {
|
||||
const details = event.event_properties || {};
|
||||
if (details.HasPreorder) orderPropertyCounts.hasPreorder++;
|
||||
if (details.LocalPickup) orderPropertyCounts.localPickup++;
|
||||
if (details.IsOnHold) orderPropertyCounts.isOnHold++;
|
||||
if (details.HasDigiItem) orderPropertyCounts.hasDigiItem++;
|
||||
if (details.HasNotions) orderPropertyCounts.hasNotions++;
|
||||
if (details.HasDigitalGC) orderPropertyCounts.hasGiftCard++;
|
||||
if (details.StillOwes) orderPropertyCounts.stillOwes++;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
eventTypes: eventTypeCounts,
|
||||
orderProperties: orderPropertyCounts,
|
||||
};
|
||||
}, [events]);
|
||||
|
||||
const handleOrderPropertyClick = (property) => {
|
||||
setOrderFilters(prev => {
|
||||
// If clicking the active filter, clear all filters
|
||||
if (prev[property]) {
|
||||
return {
|
||||
hasPreorder: false,
|
||||
localPickup: false,
|
||||
isOnHold: false,
|
||||
hasDigiItem: false,
|
||||
hasNotions: false,
|
||||
hasGiftCard: false,
|
||||
stillOwes: false,
|
||||
};
|
||||
}
|
||||
// Otherwise, set only this filter to true
|
||||
return {
|
||||
hasPreorder: property === 'hasPreorder',
|
||||
localPickup: property === 'localPickup',
|
||||
isOnHold: property === 'isOnHold',
|
||||
hasDigiItem: property === 'hasDigiItem',
|
||||
hasNotions: property === 'hasNotions',
|
||||
hasGiftCard: property === 'hasGiftCard',
|
||||
stillOwes: property === 'stillOwes',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||
<CardHeader className="p-6 pb-0">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">{title}</CardTitle>
|
||||
{lastUpdate && (
|
||||
<CardDescription>
|
||||
Last updated: {format(lastUpdate, "hh:mm a")}
|
||||
</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
{lastUpdate && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Last updated: {format(lastUpdate, "hh:mm a")}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={activeEventTypes[METRIC_IDS.PLACED_ORDER] ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setActiveEventTypes(prev => ({
|
||||
...prev,
|
||||
[METRIC_IDS.PLACED_ORDER]: !prev[METRIC_IDS.PLACED_ORDER]
|
||||
}))}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Package className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Orders</span>
|
||||
<span className="text-muted-foreground">
|
||||
{counts.eventTypes[METRIC_IDS.PLACED_ORDER]}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={activeEventTypes[METRIC_IDS.SHIPPED_ORDER] ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setActiveEventTypes(prev => ({
|
||||
...prev,
|
||||
[METRIC_IDS.SHIPPED_ORDER]: !prev[METRIC_IDS.SHIPPED_ORDER]
|
||||
}))}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Truck className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Shipments</span>
|
||||
<span className="text-muted-foreground">
|
||||
{counts.eventTypes[METRIC_IDS.SHIPPED_ORDER]}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={activeEventTypes[METRIC_IDS.ACCOUNT_CREATED] ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setActiveEventTypes(prev => ({
|
||||
...prev,
|
||||
[METRIC_IDS.ACCOUNT_CREATED]: !prev[METRIC_IDS.ACCOUNT_CREATED]
|
||||
}))}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<UserPlus className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Accounts</span>
|
||||
<span className="text-muted-foreground">
|
||||
{counts.eventTypes[METRIC_IDS.ACCOUNT_CREATED]}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={activeEventTypes[METRIC_IDS.CANCELED_ORDER] ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setActiveEventTypes(prev => ({
|
||||
...prev,
|
||||
[METRIC_IDS.CANCELED_ORDER]: !prev[METRIC_IDS.CANCELED_ORDER]
|
||||
}))}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Cancellations</span>
|
||||
<span className="text-muted-foreground">
|
||||
{counts.eventTypes[METRIC_IDS.CANCELED_ORDER]}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={activeEventTypes[METRIC_IDS.PAYMENT_REFUNDED] ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setActiveEventTypes(prev => ({
|
||||
...prev,
|
||||
[METRIC_IDS.PAYMENT_REFUNDED]: !prev[METRIC_IDS.PAYMENT_REFUNDED]
|
||||
}))}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<DollarSign className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Refunds</span>
|
||||
<span className="text-muted-foreground">
|
||||
{counts.eventTypes[METRIC_IDS.PAYMENT_REFUNDED]}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={activeEventTypes[METRIC_IDS.NEW_BLOG_POST] ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setActiveEventTypes(prev => ({
|
||||
...prev,
|
||||
[METRIC_IDS.NEW_BLOG_POST]: !prev[METRIC_IDS.NEW_BLOG_POST]
|
||||
}))}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<FileText className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Blog Posts</span>
|
||||
<span className="text-muted-foreground">
|
||||
{counts.eventTypes[METRIC_IDS.NEW_BLOG_POST]}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Order Property Filters */}
|
||||
<div className="flex flex-wrap gap-2 justify-center mt-4">
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('hasPreorder')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.hasPreorder
|
||||
? 'bg-blue-800 text-blue-100'
|
||||
: 'bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
Pre-order {counts.orderProperties.hasPreorder > 0 && `(${counts.orderProperties.hasPreorder})`}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('localPickup')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.localPickup
|
||||
? 'bg-purple-800 text-purple-100'
|
||||
: 'bg-purple-100 dark:bg-purple-900/20 text-purple-800 dark:text-purple-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
Local {counts.orderProperties.localPickup > 0 && `(${counts.orderProperties.localPickup})`}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('isOnHold')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.isOnHold
|
||||
? 'bg-yellow-800 text-yellow-100'
|
||||
: 'bg-yellow-100 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
On Hold {counts.orderProperties.isOnHold > 0 && `(${counts.orderProperties.isOnHold})`}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('hasDigiItem')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.hasDigiItem
|
||||
? 'bg-green-800 text-green-100'
|
||||
: 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
Digital {counts.orderProperties.hasDigiItem > 0 && `(${counts.orderProperties.hasDigiItem})`}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('hasNotions')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.hasNotions
|
||||
? 'bg-pink-800 text-pink-100'
|
||||
: 'bg-pink-100 dark:bg-pink-900/20 text-pink-800 dark:text-pink-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
Notions {counts.orderProperties.hasNotions > 0 && `(${counts.orderProperties.hasNotions})`}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('hasGiftCard')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.hasGiftCard
|
||||
? 'bg-indigo-800 text-indigo-100'
|
||||
: 'bg-indigo-100 dark:bg-indigo-900/20 text-indigo-800 dark:text-indigo-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
Gift Card {counts.orderProperties.hasGiftCard > 0 && `(${counts.orderProperties.hasGiftCard})`}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => handleOrderPropertyClick('stillOwes')}
|
||||
className={`px-2 py-1 ${
|
||||
orderFilters.stillOwes
|
||||
? 'bg-red-800 text-red-100'
|
||||
: 'bg-red-100 dark:bg-red-900/20 text-red-800 dark:text-red-300'
|
||||
} rounded-full text-xs font-medium cursor-help`}
|
||||
>
|
||||
Owes {counts.orderProperties.stillOwes > 0 && `(${counts.orderProperties.stillOwes})`}
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-6 pt-4 flex-1 overflow-hidden">
|
||||
<ScrollArea className="h-full">
|
||||
{loading && !events.length ? (
|
||||
<div className="space-y-4 w-full">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="flex items-center space-x-4 p-4 border-b dark:border-gray-800">
|
||||
<div className="rounded-full">
|
||||
<Skeleton className="h-10 w-10 rounded-full" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 space-y-2">
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
<Skeleton className="h-3 w-[150px]" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Skeleton className="h-3 w-16" />
|
||||
<Skeleton className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<LoadingState />
|
||||
) : error ? (
|
||||
<div className="flex flex-col items-center justify-center p-6 text-center">
|
||||
<AlertCircle className="w-12 h-12 text-red-500 mb-4" />
|
||||
@@ -1218,7 +1560,7 @@ const EventFeed = ({
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
) : !events || events.length === 0 ? (
|
||||
) : !filteredEvents || filteredEvents.length === 0 ? (
|
||||
<div className="h-full flex flex-col items-center justify-center py-16 px-4">
|
||||
<div className="bg-gray-100 dark:bg-gray-800 rounded-full p-3 mb-4">
|
||||
<Activity className="h-8 w-8 text-muted-foreground" />
|
||||
@@ -1232,7 +1574,7 @@ const EventFeed = ({
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{events.map((event) => (
|
||||
{filteredEvents.map((event) => (
|
||||
<EventCard key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user