Add SMS campaigns
This commit is contained in:
@@ -7,9 +7,19 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { DateTime } from "luxon";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { TIME_RANGES } from "@/lib/constants";
|
||||
import { Mail, MessageSquare } from "lucide-react";
|
||||
|
||||
// Helper functions for formatting
|
||||
const formatRate = (value) => {
|
||||
const formatRate = (value, isSMS = false, hideForSMS = false) => {
|
||||
if (isSMS && hideForSMS) return "N/A";
|
||||
if (typeof value !== "number") return "0.0%";
|
||||
return `${(value * 100).toFixed(1)}%`;
|
||||
};
|
||||
@@ -50,25 +60,46 @@ const MetricCell = ({
|
||||
isMonetary = false,
|
||||
showConversionRate = false,
|
||||
totalRecipients = 0,
|
||||
}) => (
|
||||
<td className="p-2 text-center">
|
||||
<div className="text-blue-600 dark:text-blue-400 text-lg font-semibold">
|
||||
{isMonetary ? formatCurrency(value) : formatRate(value)}
|
||||
</div>
|
||||
<div className="text-gray-600 dark:text-gray-400 text-sm">
|
||||
{count?.toLocaleString() || 0} {count === 1 ? "recipient" : "recipients"}
|
||||
{showConversionRate &&
|
||||
totalRecipients > 0 &&
|
||||
` (${((count / totalRecipients) * 100).toFixed(2)}%)`}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
isSMS = false,
|
||||
hideForSMS = false,
|
||||
}) => {
|
||||
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>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
return (
|
||||
<td className="p-2 text-center">
|
||||
<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">
|
||||
{count?.toLocaleString() || 0} {count === 1 ? "recipient" : "recipients"}
|
||||
{showConversionRate &&
|
||||
totalRecipients > 0 &&
|
||||
` (${((count / totalRecipients) * 100).toFixed(2)}%)`}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
const CHANNEL_OPTIONS = [
|
||||
{ value: "all", label: "All Campaigns" },
|
||||
{ value: "email", label: "Email Only" },
|
||||
{ value: "sms", label: "SMS Only" },
|
||||
];
|
||||
|
||||
const KlaviyoCampaigns = ({ className }) => {
|
||||
const [campaigns, setCampaigns] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedChannel, setSelectedChannel] = useState("all");
|
||||
const [selectedTimeRange, setSelectedTimeRange] = useState("last7days");
|
||||
const [sortConfig, setSortConfig] = useState({
|
||||
key: "send_time",
|
||||
direction: "desc",
|
||||
@@ -77,7 +108,9 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
const fetchCampaigns = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetch(`/api/klaviyo/reporting/campaigns/${timeRange}`);
|
||||
const response = await fetch(
|
||||
`/api/klaviyo/reporting/campaigns/${selectedTimeRange}${selectedChannel !== 'all' ? `?channel=${selectedChannel}` : ''}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch campaigns: ${response.status}`);
|
||||
@@ -98,7 +131,7 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
fetchCampaigns();
|
||||
const interval = setInterval(fetchCampaigns, 10 * 60 * 1000); // Refresh every 10 minutes
|
||||
return () => clearInterval(interval);
|
||||
}, [timeRange]);
|
||||
}, [selectedTimeRange, selectedChannel]);
|
||||
|
||||
// Sort campaigns
|
||||
const sortedCampaigns = [...campaigns].sort((a, b) => {
|
||||
@@ -109,10 +142,11 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
return direction * (a[sortConfig.key] - b[sortConfig.key]);
|
||||
});
|
||||
|
||||
// Filter campaigns by search term
|
||||
// Filter campaigns by search term and channel
|
||||
const filteredCampaigns = sortedCampaigns.filter(
|
||||
(campaign) =>
|
||||
campaign?.name?.toLowerCase().includes((searchTerm || "").toLowerCase())
|
||||
campaign?.name?.toLowerCase().includes((searchTerm || "").toLowerCase()) &&
|
||||
(selectedChannel === "all" || campaign?.channel === selectedChannel)
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -132,9 +166,37 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
<Card className="h-full bg-white dark:bg-gray-900">
|
||||
{error && <ErrorAlert description={error} />}
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
Email Campaigns
|
||||
</CardTitle>
|
||||
<div className="flex justify-between items-center">
|
||||
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
Campaigns
|
||||
</CardTitle>
|
||||
<div className="flex gap-2">
|
||||
<Select value={selectedChannel} onValueChange={setSelectedChannel}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select channel" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{CHANNEL_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={selectedTimeRange} onValueChange={setSelectedTimeRange}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select time range" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TIME_RANGES.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="overflow-y-auto pl-4 max-h-[350px] mb-4">
|
||||
<table className="w-full">
|
||||
@@ -170,8 +232,15 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<td className="p-2 align-top">
|
||||
<div className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{campaign.name}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{campaign.name}
|
||||
</div>
|
||||
{campaign.channel === 'sms' ? (
|
||||
<MessageSquare className="h-4 w-4 text-gray-500" />
|
||||
) : (
|
||||
<Mail className="h-4 w-4 text-gray-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 truncate max-w-[300px]">
|
||||
{campaign.subject}
|
||||
@@ -201,21 +270,27 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
value={campaign.stats.delivery_rate}
|
||||
count={campaign.stats.delivered}
|
||||
totalRecipients={campaign.stats.recipients}
|
||||
isSMS={campaign.channel === 'sms'}
|
||||
/>
|
||||
<MetricCell
|
||||
value={campaign.stats.open_rate}
|
||||
count={campaign.stats.opens_unique}
|
||||
totalRecipients={campaign.stats.recipients}
|
||||
isSMS={campaign.channel === 'sms'}
|
||||
hideForSMS={true}
|
||||
/>
|
||||
<MetricCell
|
||||
value={campaign.stats.click_rate}
|
||||
count={campaign.stats.clicks_unique}
|
||||
totalRecipients={campaign.stats.recipients}
|
||||
isSMS={campaign.channel === 'sms'}
|
||||
/>
|
||||
<MetricCell
|
||||
value={campaign.stats.click_to_open_rate}
|
||||
count={campaign.stats.clicks_unique}
|
||||
totalRecipients={campaign.stats.opens_unique}
|
||||
isSMS={campaign.channel === 'sms'}
|
||||
hideForSMS={true}
|
||||
/>
|
||||
<MetricCell
|
||||
value={campaign.stats.conversion_value}
|
||||
@@ -223,6 +298,7 @@ const KlaviyoCampaigns = ({ className, timeRange = "last7days" }) => {
|
||||
isMonetary={true}
|
||||
showConversionRate={true}
|
||||
totalRecipients={campaign.stats.recipients}
|
||||
isSMS={campaign.channel === 'sms'}
|
||||
/>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user