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