Separate out blog posts for filtering + standardize campaign table styling

This commit is contained in:
2024-12-29 09:11:38 -05:00
parent 90b0dfa700
commit 5504390fc5

View File

@@ -16,7 +16,7 @@ import {
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { TIME_RANGES } from "@/lib/constants";
import { Mail, MessageSquare, ArrowUpDown } from "lucide-react";
import { Mail, MessageSquare, ArrowUpDown, BookOpen } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
// Helper functions for formatting
@@ -41,67 +41,67 @@ const TableSkeleton = () => (
<table className="w-full">
<thead>
<tr>
<th className="p-2 text-left font-medium sticky top-0 bg-white dark:bg-gray-900 z-10">
<Skeleton className="h-8 w-24 dark:bg-gray-700" />
<th className="p-2 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-8 w-24 bg-muted" />
</th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10">
<Skeleton className="h-8 w-20 mx-auto dark:bg-gray-700" />
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-8 w-20 mx-auto bg-muted" />
</th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10">
<Skeleton className="h-8 w-20 mx-auto dark:bg-gray-700" />
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-8 w-20 mx-auto bg-muted" />
</th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10">
<Skeleton className="h-8 w-20 mx-auto dark:bg-gray-700" />
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-8 w-20 mx-auto bg-muted" />
</th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10">
<Skeleton className="h-8 w-20 mx-auto dark:bg-gray-700" />
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-8 w-20 mx-auto bg-muted" />
</th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10">
<Skeleton className="h-8 w-20 mx-auto dark:bg-gray-700" />
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-8 w-20 mx-auto bg-muted" />
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-800">
{[...Array(15)].map((_, i) => (
<tr key={i} className="hover:bg-gray-50 dark:hover:bg-gray-800">
<tr key={i} className="hover:bg-muted/50 transition-colors">
<td className="p-2">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 dark:bg-gray-700" />
<Skeleton className="h-4 w-4 bg-muted" />
<div className="space-y-2">
<Skeleton className="h-4 w-48 dark:bg-gray-700" />
<Skeleton className="h-3 w-64 dark:bg-gray-700" />
<Skeleton className="h-3 w-32 dark:bg-gray-700" />
<Skeleton className="h-4 w-48 bg-muted" />
<Skeleton className="h-3 w-64 bg-muted" />
<Skeleton className="h-3 w-32 bg-muted" />
</div>
</div>
</td>
<td className="p-2 text-center">
<div className="flex flex-col items-center gap-1">
<Skeleton className="h-4 w-16 dark:bg-gray-700" />
<Skeleton className="h-3 w-24 dark:bg-gray-700" />
<Skeleton className="h-4 w-16 bg-muted" />
<Skeleton className="h-3 w-24 bg-muted" />
</div>
</td>
<td className="p-2 text-center">
<div className="flex flex-col items-center gap-1">
<Skeleton className="h-4 w-16 dark:bg-gray-700" />
<Skeleton className="h-3 w-24 dark:bg-gray-700" />
<Skeleton className="h-4 w-16 bg-muted" />
<Skeleton className="h-3 w-24 bg-muted" />
</div>
</td>
<td className="p-2 text-center">
<div className="flex flex-col items-center gap-1">
<Skeleton className="h-4 w-16 dark:bg-gray-700" />
<Skeleton className="h-3 w-24 dark:bg-gray-700" />
<Skeleton className="h-4 w-16 bg-muted" />
<Skeleton className="h-3 w-24 bg-muted" />
</div>
</td>
<td className="p-2 text-center">
<div className="flex flex-col items-center gap-1">
<Skeleton className="h-4 w-16 dark:bg-gray-700" />
<Skeleton className="h-3 w-24 dark:bg-gray-700" />
<Skeleton className="h-4 w-16 bg-muted" />
<Skeleton className="h-3 w-24 bg-muted" />
</div>
</td>
<td className="p-2 text-center">
<div className="flex flex-col items-center gap-1">
<Skeleton className="h-4 w-16 dark:bg-gray-700" />
<Skeleton className="h-3 w-24 dark:bg-gray-700" />
<Skeleton className="h-4 w-16 bg-muted" />
<Skeleton className="h-3 w-24 bg-muted" />
</div>
</td>
</tr>
@@ -112,7 +112,7 @@ const TableSkeleton = () => (
// Error alert component
const ErrorAlert = ({ description }) => (
<div className="p-4 m-6 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/10 rounded-lg">
<div className="p-4 m-6 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/10 rounded-lg border border-red-200 dark:border-red-900/20">
{description}
</div>
);
@@ -130,8 +130,8 @@ const MetricCell = ({
if (isSMS && hideForSMS) {
return (
<td className="p-2 text-center">
<div className="text-gray-500 dark:text-gray-400 text-lg font-semibold">N/A</div>
<div className="text-gray-400 dark:text-gray-500 text-sm">-</div>
<div className="text-muted-foreground text-lg font-semibold">N/A</div>
<div className="text-muted-foreground text-sm">-</div>
</td>
);
}
@@ -141,7 +141,7 @@ const MetricCell = ({
<div className="text-blue-600 dark:text-blue-400 text-lg font-semibold">
{isMonetary ? formatCurrency(value) : formatRate(value, isSMS, hideForSMS)}
</div>
<div className="text-gray-600 dark:text-gray-400 text-sm">
<div className="text-muted-foreground text-sm">
{count?.toLocaleString() || 0} {count === 1 ? "recipient" : "recipients"}
{showConversionRate &&
totalRecipients > 0 &&
@@ -156,7 +156,7 @@ const KlaviyoCampaigns = ({ className }) => {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [searchTerm, setSearchTerm] = useState("");
const [selectedChannels, setSelectedChannels] = useState({ email: true, sms: true });
const [selectedChannels, setSelectedChannels] = useState({ email: true, sms: true, blog: true });
const [selectedTimeRange, setSelectedTimeRange] = useState("last7days");
const [sortConfig, setSortConfig] = useState({
key: "send_time",
@@ -222,25 +222,28 @@ const KlaviyoCampaigns = ({ className }) => {
// Filter campaigns by search term and channels
const filteredCampaigns = sortedCampaigns.filter(
(campaign) =>
campaign?.name?.toLowerCase().includes((searchTerm || "").toLowerCase()) &&
selectedChannels[campaign?.channel]
(campaign) => {
const isBlog = campaign?.name?.includes("_Blog");
const channelType = isBlog ? "blog" : campaign?.channel;
return campaign?.name?.toLowerCase().includes((searchTerm || "").toLowerCase()) &&
selectedChannels[channelType];
}
);
if (isLoading) {
return (
<Card className="h-full bg-white dark:bg-gray-900">
<Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="pb-2">
<div className="flex justify-between items-center">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
<Skeleton className="h-6 w-48 dark:bg-gray-700" />
<Skeleton className="h-6 w-48 bg-muted" />
</CardTitle>
<div className="flex gap-2">
<div className="flex ml-1 gap-1 items-center">
<Skeleton className="h-8 w-20 dark:bg-gray-700" />
<Skeleton className="h-8 w-20 dark:bg-gray-700" />
<Skeleton className="h-8 w-20 bg-muted" />
<Skeleton className="h-8 w-20 bg-muted" />
</div>
<Skeleton className="h-8 w-[130px] dark:bg-gray-700" />
<Skeleton className="h-8 w-[130px] bg-muted" />
</div>
</div>
</CardHeader>
@@ -252,7 +255,7 @@ const KlaviyoCampaigns = ({ className }) => {
}
return (
<Card className="h-full bg-white dark:bg-gray-900">
<Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
{error && <ErrorAlert description={error} />}
<CardHeader className="pb-2">
<div className="flex justify-between items-center">
@@ -277,6 +280,14 @@ const KlaviyoCampaigns = ({ className }) => {
<MessageSquare className="h-4 w-4" />
<span className="hidden sm:inline">SMS</span>
</Button>
<Button
variant={selectedChannels.blog ? "default" : "outline"}
size="sm"
onClick={() => setSelectedChannels(prev => ({ ...prev, blog: !prev.blog }))}
>
<BookOpen className="h-4 w-4" />
<span className="hidden sm:inline">Blog</span>
</Button>
</div>
<Select value={selectedTimeRange} onValueChange={setSelectedTimeRange}>
<SelectTrigger className="w-[130px]">
@@ -297,7 +308,7 @@ const KlaviyoCampaigns = ({ className }) => {
<table className="w-full">
<thead>
<tr>
<th className="p-2 text-left font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100">
<th className="p-2 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button
variant="ghost"
onClick={() => handleSort("send_time")}
@@ -306,7 +317,7 @@ const KlaviyoCampaigns = ({ className }) => {
Campaign
</Button>
</th>
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100">
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button
variant={sortConfig.key === "delivery_rate" ? "default" : "ghost"}
onClick={() => handleSort("delivery_rate")}
@@ -315,7 +326,7 @@ const KlaviyoCampaigns = ({ className }) => {
Delivery
</Button>
</th>
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100">
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button
variant={sortConfig.key === "open_rate" ? "default" : "ghost"}
onClick={() => handleSort("open_rate")}
@@ -324,7 +335,7 @@ const KlaviyoCampaigns = ({ className }) => {
Opens
</Button>
</th>
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100">
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button
variant={sortConfig.key === "click_rate" ? "default" : "ghost"}
onClick={() => handleSort("click_rate")}
@@ -333,7 +344,7 @@ const KlaviyoCampaigns = ({ className }) => {
Clicks
</Button>
</th>
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100">
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button
variant={sortConfig.key === "click_to_open_rate" ? "default" : "ghost"}
onClick={() => handleSort("click_to_open_rate")}
@@ -342,7 +353,7 @@ const KlaviyoCampaigns = ({ className }) => {
CTR
</Button>
</th>
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100">
<th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button
variant={sortConfig.key === "conversion_value" ? "default" : "ghost"}
onClick={() => handleSort("conversion_value")}
@@ -357,26 +368,28 @@ const KlaviyoCampaigns = ({ className }) => {
{filteredCampaigns.map((campaign) => (
<tr
key={campaign.id}
className="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
className="hover:bg-muted/50 transition-colors"
>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<td className="p-2 align-top">
<div className="flex items-center gap-2">
{campaign.channel === 'sms' ? (
<MessageSquare className="h-4 w-4 text-gray-500" />
{campaign.name?.includes("_Blog") ? (
<BookOpen className="h-4 w-4 text-muted-foreground" />
) : campaign.channel === 'sms' ? (
<MessageSquare className="h-4 w-4 text-muted-foreground" />
) : (
<Mail className="h-4 w-4 text-gray-500" />
<Mail className="h-4 w-4 text-muted-foreground" />
)}
<div className="font-medium text-gray-900 dark:text-gray-100">
{campaign.name}
</div>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 truncate max-w-[300px]">
<div className="text-sm text-muted-foreground truncate max-w-[300px]">
{campaign.subject}
</div>
<div className="text-xs text-gray-400 dark:text-gray-500">
<div className="text-xs text-muted-foreground">
{campaign.send_time
? DateTime.fromISO(campaign.send_time).toLocaleString(DateTime.DATETIME_MED)
: "No date"}
@@ -385,11 +398,11 @@ const KlaviyoCampaigns = ({ className }) => {
</TooltipTrigger>
<TooltipContent
side="top"
className="break-words bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 border dark:border-gray-700"
className="break-words bg-white dark:bg-gray-900/60 backdrop-blur-sm text-gray-900 dark:text-gray-100 border dark:border-gray-800"
>
<p className="font-medium">{campaign.name}</p>
<p>{campaign.subject}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-muted-foreground">
{campaign.send_time
? DateTime.fromISO(campaign.send_time).toLocaleString(DateTime.DATETIME_MED)
: "No date"}