diff --git a/dashboard/src/components/dashboard/MetaCampaigns.jsx b/dashboard/src/components/dashboard/MetaCampaigns.jsx
index 3b7861c..6ae7841 100644
--- a/dashboard/src/components/dashboard/MetaCampaigns.jsx
+++ b/dashboard/src/components/dashboard/MetaCampaigns.jsx
@@ -6,7 +6,6 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import { DateTime } from "luxon";
import {
Select,
SelectContent,
@@ -14,153 +13,249 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { Button } from "@/components/ui/button";
-import { TIME_RANGES } from "@/lib/constants";
-import { Play, Pause, DollarSign, BarChart3 } from "lucide-react";
+import { Instagram, Loader2 } from "lucide-react";
// Helper functions for formatting
-const formatRate = (value) => {
- if (typeof value !== "number") return "0.00%";
- return `${(value * 100).toFixed(2)}%`;
-};
-
-const formatCurrency = (value) => {
- if (typeof value !== "number") return "$0";
- return new Intl.NumberFormat("en-US", {
+const formatCurrency = (value, decimalPlaces = 2) =>
+ new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- }).format(value);
+ minimumFractionDigits: decimalPlaces,
+ maximumFractionDigits: decimalPlaces,
+ }).format(value || 0);
+
+const formatNumber = (value, decimalPlaces = 0) => {
+ return new Intl.NumberFormat("en-US", {
+ minimumFractionDigits: decimalPlaces,
+ maximumFractionDigits: decimalPlaces,
+ }).format(value || 0);
};
-// Loading skeleton component
-const TableSkeleton = () => (
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
-);
+const formatPercent = (value, decimalPlaces = 2) =>
+ `${(value || 0).toFixed(decimalPlaces)}%`;
-// Error alert component
-const ErrorAlert = ({ description }) => (
-
- {description}
-
-);
+const summaryCard = (label, value, options = {}) => {
+ const {
+ isMonetary = false,
+ isPercentage = false,
+ decimalPlaces = 0,
+ } = options;
+
+ let displayValue;
+ if (isMonetary) {
+ displayValue = formatCurrency(value, decimalPlaces);
+ } else if (isPercentage) {
+ displayValue = formatPercent(value, decimalPlaces);
+ } else {
+ displayValue = formatNumber(value, decimalPlaces);
+ }
-// MetricCell component for displaying campaign metrics
-const MetricCell = ({
- value,
- count,
- isMonetary = false,
- label = "",
- tooltipText = "",
-}) => {
return (
-
-
-
-
-
- {isMonetary ? formatCurrency(value) : formatRate(value)}
-
-
- {count?.toLocaleString() || 0} {label}
-
- |
-
-
- {tooltipText}
-
-
-
+
+
{label}
+
+ {displayValue}
+
+
);
};
-const MetaCampaigns = ({ className }) => {
- const [campaigns, setCampaigns] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
+const MetricCell = ({
+ value,
+ label,
+ sublabel,
+ isMonetary = false,
+ isPercentage = false,
+ decimalPlaces = 0,
+}) => (
+
+
+ {isMonetary
+ ? formatCurrency(value, decimalPlaces)
+ : isPercentage
+ ? formatPercent(value, decimalPlaces)
+ : formatNumber(value, decimalPlaces)}
+
+ {label && (
+
+ {label}
+
+ )}
+ {sublabel && (
+
+ {sublabel}
+
+ )}
+ |
+);
+
+const getActionValue = (campaign, actionType) => {
+ if (actionType === "impressions" || actionType === "reach") {
+ return campaign.metrics[actionType] || 0;
+ }
+
+ const actions = campaign.metrics.actions;
+ if (Array.isArray(actions)) {
+ const action = actions.find((a) => a.action_type === actionType);
+ return action ? parseInt(action.value) || 0 : 0;
+ }
+
+ return 0;
+};
+
+const CampaignName = ({ name }) => {
+ if (name.startsWith("Instagram post: ")) {
+ return (
+
+
+ {name.replace("Instagram post: ", "")}
+
+ );
+ }
+ return {name};
+};
+
+const MetaCampaigns = () => {
+ const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const [selectedTimeRange, setSelectedTimeRange] = useState("last7days");
- const [sortConfig, setSortConfig] = useState({
- key: "spend",
- direction: "desc",
- });
+ const [campaigns, setCampaigns] = useState([]);
+ const [timeframe, setTimeframe] = useState("30");
+ const [summaryMetrics, setSummaryMetrics] = useState(null);
- const handleSort = (key) => {
- setSortConfig((prev) => ({
- key,
- direction: prev.key === key && prev.direction === "desc" ? "asc" : "desc",
- }));
- };
+ const computeDateRange = (timeframe) => {
+ // Create date in Eastern Time
+ const now = new Date();
+ const easternTime = new Date(
+ now.toLocaleString("en-US", { timeZone: "America/New_York" })
+ );
+ easternTime.setHours(0, 0, 0, 0); // Set to start of day
- const fetchCampaigns = async () => {
- try {
- setIsLoading(true);
- const today = DateTime.now();
- const startDate = today.minus({ days: 7 }).toISODate();
- const endDate = today.toISODate();
+ let sinceDate, untilDate;
- const response = await fetch(
- `/api/meta/campaigns?since=${startDate}&until=${endDate}`
- );
-
- if (!response.ok) {
- throw new Error(`Failed to fetch campaigns: ${response.status}`);
- }
-
- const data = await response.json();
- setCampaigns(data || []);
- setError(null);
- } catch (err) {
- console.error("Error fetching campaigns:", err);
- setError(err.message);
- } finally {
- setIsLoading(false);
+ if (timeframe === "today") {
+ // For today, both dates should be the current date in Eastern Time
+ sinceDate = untilDate = new Date(easternTime);
+ } else {
+ // For other periods, calculate the date range
+ untilDate = new Date(easternTime);
+ untilDate.setDate(untilDate.getDate() - 1); // Yesterday
+
+ sinceDate = new Date(untilDate);
+ sinceDate.setDate(sinceDate.getDate() - parseInt(timeframe) + 1);
}
+
+ return {
+ since: sinceDate.toISOString().split("T")[0],
+ until: untilDate.toISOString().split("T")[0],
+ };
};
useEffect(() => {
- fetchCampaigns();
- const interval = setInterval(fetchCampaigns, 10 * 60 * 1000); // Refresh every 10 minutes
- return () => clearInterval(interval);
- }, [selectedTimeRange]);
+ const fetchMetaAdsData = async () => {
+ try {
+ setLoading(true);
+ setError(null);
- // Sort campaigns
- const sortedCampaigns = [...campaigns].sort((a, b) => {
- const direction = sortConfig.direction === "desc" ? -1 : 1;
- const insights_a = a.insights?.data?.[0] || {};
- const insights_b = b.insights?.data?.[0] || {};
-
- switch (sortConfig.key) {
- case "spend":
- return direction * ((insights_a.spend || 0) - (insights_b.spend || 0));
- case "impressions":
- return direction * ((insights_a.impressions || 0) - (insights_b.impressions || 0));
- case "clicks":
- return direction * ((insights_a.clicks || 0) - (insights_b.clicks || 0));
- case "ctr":
- return direction * ((insights_a.ctr || 0) - (insights_b.ctr || 0));
- case "cpc":
- return direction * ((insights_a.cpc || 0) - (insights_b.cpc || 0));
- default:
- return 0;
- }
- });
+ const { since, until } = computeDateRange(timeframe);
- if (isLoading) {
+ const [campaignData, accountInsights] = await Promise.all([
+ fetch(`/api/meta/campaigns?since=${since}&until=${until}`),
+ fetch(`/api/meta/account-insights?since=${since}&until=${until}`)
+ ]);
+
+ const [campaignsJson, accountJson] = await Promise.all([
+ campaignData.json(),
+ accountInsights.json()
+ ]);
+
+ // Process campaigns to match the expected format
+ const processedCampaigns = campaignsJson.map(campaign => {
+ const insights = campaign.insights?.data?.[0] || {};
+ const frequency = parseFloat(insights.frequency) || 0;
+ return {
+ id: campaign.id,
+ name: campaign.name,
+ objective: campaign.objective,
+ status: campaign.status,
+ budget: campaign.daily_budget / 100,
+ budgetType: 'daily',
+ metrics: {
+ spend: parseFloat(insights.spend) || 0,
+ impressions: parseInt(insights.impressions) || 0,
+ reach: parseInt(insights.reach) || 0,
+ clicks: parseInt(insights.clicks) || 0,
+ ctr: parseFloat(insights.ctr) || 0,
+ cpc: parseFloat(insights.cpc) || 0,
+ cpm: parseFloat(insights.cpm) || 0,
+ frequency,
+ totalPurchases: parseInt(insights.purchase) || 0,
+ purchaseValue: parseFloat(insights.purchase_value) || 0,
+ totalPostEngagements: parseInt(insights.post_engagement) || 0,
+ costPerResult: parseFloat(insights.cost_per_action_type?.find(c => c.action_type === 'purchase')?.value) || 0,
+ actions: insights.actions || []
+ }
+ };
+ });
+
+ const activeCampaigns = processedCampaigns.filter(c => c.metrics.spend > 0);
+ setCampaigns(activeCampaigns);
+
+ if (activeCampaigns.length > 0) {
+ const totalSpend = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.spend, 0);
+ const totalImpressions = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.impressions, 0);
+ const totalReach = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.reach, 0);
+ const totalPurchases = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.totalPurchases, 0);
+ const totalPurchaseValue = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.purchaseValue, 0);
+ const totalLinkClicks = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.clicks, 0);
+ const totalPostEngagements = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.totalPostEngagements, 0);
+
+ const numCampaigns = activeCampaigns.length;
+ const avgFrequency = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.frequency, 0) / numCampaigns;
+ const avgCpm = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.cpm, 0) / numCampaigns;
+ const avgCtr = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.ctr, 0) / numCampaigns;
+ const avgCpc = activeCampaigns.reduce((sum, camp) => sum + camp.metrics.cpc, 0) / numCampaigns;
+
+ setSummaryMetrics({
+ totalSpend,
+ totalPurchaseValue,
+ totalLinkClicks,
+ totalImpressions,
+ totalReach,
+ totalPurchases,
+ avgFrequency,
+ avgCpm,
+ avgCtr,
+ avgCpc,
+ totalPostEngagements,
+ totalCampaigns: numCampaigns,
+ });
+ }
+ } catch (err) {
+ console.error("Meta Ads fetch error:", err);
+ setError(`Failed to fetch Meta Ads data: ${err.message}`);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchMetaAdsData();
+ }, [timeframe]);
+
+ if (loading) {
return (
-
-
-
-
-
-
+
+
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ {error}
);
@@ -168,155 +263,182 @@ const MetaCampaigns = ({ className }) => {
return (
- {error && }
-
+
- Meta Ad Campaigns
+ Meta Ads Performance
-
-
-
+
+
+
+ {[
+ {
+ label: "Active Campaigns",
+ value: summaryMetrics?.totalCampaigns,
+ },
+ {
+ label: "Total Spend",
+ value: summaryMetrics?.totalSpend,
+ options: { isMonetary: true, decimalPlaces: 0 },
+ },
+ { label: "Total Reach", value: summaryMetrics?.totalReach },
+ {
+ label: "Total Impressions",
+ value: summaryMetrics?.totalImpressions,
+ },
+ {
+ label: "Avg Frequency",
+ value: summaryMetrics?.avgFrequency,
+ options: { decimalPlaces: 2 },
+ },
+ {
+ label: "Total Engagements",
+ value: summaryMetrics?.totalPostEngagements,
+ },
+ {
+ label: "Avg CPM",
+ value: summaryMetrics?.avgCpm,
+ options: { isMonetary: true, decimalPlaces: 2 },
+ },
+ {
+ label: "Avg CTR",
+ value: summaryMetrics?.avgCtr,
+ options: { isPercentage: true, decimalPlaces: 2 },
+ },
+ {
+ label: "Avg CPC",
+ value: summaryMetrics?.avgCpc,
+ options: { isMonetary: true, decimalPlaces: 2 },
+ },
+ {
+ label: "Total Link Clicks",
+ value: summaryMetrics?.totalLinkClicks,
+ },
+ { label: "Total Purchases", value: summaryMetrics?.totalPurchases },
+ {
+ label: "Purchase Value",
+ value: summaryMetrics?.totalPurchaseValue,
+ options: { isMonetary: true, decimalPlaces: 0 },
+ },
+ ].map((card) => (
+
+ {summaryCard(card.label, card.value, card.options)}
+
+ ))}
-
-
-
-
- |
- Campaign
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
-
- {sortedCampaigns.map((campaign) => {
- const insights = campaign.insights?.data?.[0] || {};
- return (
-
-
-
-
-
-
- {campaign.status === 'ACTIVE' ? (
-
- ) : (
-
- )}
-
- {campaign.name}
-
-
-
- {campaign.objective}
-
-
- Budget: {formatCurrency(campaign.daily_budget / 100)}
-
- |
-
-
- {campaign.name}
- {campaign.objective}
-
- Daily Budget: {formatCurrency(campaign.daily_budget / 100)}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ |
+ Campaign
+ |
+
+ Spend
+ |
+
+ Reach
+ |
+
+ Impressions
+ |
+
+ CPM
+ |
+
+ CTR
+ |
+
+ Results
+ |
+
+ Value
+ |
+
+ Engagements
+ |
- );
- })}
-
-
+
+
+ {campaigns.map((campaign) => (
+
+ |
+
+
+
+
+ {campaign.objective}
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
);
diff --git a/examples DO NOT USE OR EDIT/EXAMPLE ONLY MetaAdsOverview.jsx b/examples DO NOT USE OR EDIT/EXAMPLE ONLY MetaAdsOverview.jsx
new file mode 100644
index 0000000..870c1d0
--- /dev/null
+++ b/examples DO NOT USE OR EDIT/EXAMPLE ONLY MetaAdsOverview.jsx
@@ -0,0 +1,448 @@
+import React, { useState, useEffect } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Instagram, Loader2 } from "lucide-react";
+import { metaAdsService } from "@/services/metaAdsService";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+const formatCurrency = (value, decimalPlaces = 2) =>
+ new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ minimumFractionDigits: decimalPlaces,
+ maximumFractionDigits: decimalPlaces,
+ }).format(value || 0);
+
+const formatNumber = (value, decimalPlaces = 0) => {
+ return new Intl.NumberFormat("en-US", {
+ minimumFractionDigits: decimalPlaces,
+ maximumFractionDigits: decimalPlaces,
+ }).format(value || 0);
+};
+
+const formatPercent = (value, decimalPlaces = 2) =>
+ `${(value || 0).toFixed(decimalPlaces)}%`;
+
+const summaryCard = (label, value, options = {}) => {
+ const {
+ isMonetary = false,
+ isPercentage = false,
+ decimalPlaces = 0,
+ } = options;
+
+ let displayValue;
+ if (isMonetary) {
+ displayValue = formatCurrency(value, decimalPlaces);
+ } else if (isPercentage) {
+ displayValue = formatPercent(value, decimalPlaces);
+ } else {
+ displayValue = formatNumber(value, decimalPlaces);
+ }
+
+ return (
+
+
{label}
+
+ {displayValue}
+
+
+ );
+};
+
+const MetricCell = ({
+ value,
+ label,
+ sublabel,
+ isMonetary = false,
+ isPercentage = false,
+ decimalPlaces = 0,
+}) => (
+
+
+ {isMonetary
+ ? formatCurrency(value, decimalPlaces)
+ : isPercentage
+ ? formatPercent(value, decimalPlaces)
+ : formatNumber(value, decimalPlaces)}
+
+ {label && (
+
+ {label}
+
+ )}
+ {sublabel && (
+
+ {sublabel}
+
+ )}
+ |
+);
+
+const getActionValue = (campaign, actionType) => {
+ if (actionType === "impressions" || actionType === "reach") {
+ return campaign.metrics[actionType] || 0;
+ }
+
+ const actions = campaign.metrics.actions;
+ if (Array.isArray(actions)) {
+ const action = actions.find((a) => a.action_type === actionType);
+ return action ? parseInt(action.value) || 0 : 0;
+ }
+
+ return 0;
+};
+
+const CampaignName = ({ name }) => {
+ if (name.startsWith("Instagram post: ")) {
+ return (
+
+
+ {name.replace("Instagram post: ", "")}
+
+ );
+ }
+ return {name};
+};
+
+const MetaAdsOverview = () => {
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [campaigns, setCampaigns] = useState([]);
+ const [timeframe, setTimeframe] = useState("30");
+ const [summaryMetrics, setSummaryMetrics] = useState(null);
+ const computeDateRange = (timeframe) => {
+ // Create date in Eastern Time
+ const now = new Date();
+ const easternTime = new Date(
+ now.toLocaleString("en-US", { timeZone: "America/New_York" })
+ );
+ easternTime.setHours(0, 0, 0, 0); // Set to start of day
+
+ let sinceDate, untilDate;
+
+ if (timeframe === "today") {
+ // For today, both dates should be the current date in Eastern Time
+ sinceDate = untilDate = new Date(easternTime);
+ } else {
+ // For other periods, calculate the date range
+ untilDate = new Date(easternTime);
+ untilDate.setDate(untilDate.getDate() - 1); // Yesterday
+
+ sinceDate = new Date(untilDate);
+ sinceDate.setDate(sinceDate.getDate() - parseInt(timeframe) + 1);
+ }
+
+ return {
+ since: sinceDate.toISOString().split("T")[0],
+ until: untilDate.toISOString().split("T")[0],
+ };
+ };
+ useEffect(() => {
+ const fetchMetaAdsData = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const { since, until } = computeDateRange(timeframe);
+
+ const [campaignData, accountInsights] = await Promise.all([
+ metaAdsService.getCampaigns({ since, until }),
+ metaAdsService.getAccountInsights({ since, until }),
+ ]);
+
+ const activeCampaigns = campaignData.filter((c) => c.metrics.spend > 0);
+ setCampaigns(activeCampaigns);
+
+ const totalReach = parseInt(accountInsights.reach || 0);
+
+ if (activeCampaigns.length > 0) {
+ const totalSpend = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.spend,
+ 0
+ );
+ const totalImpressions = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.impressions,
+ 0
+ );
+ const totalPurchases = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.totalPurchases,
+ 0
+ );
+ const totalPurchaseValue = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.purchaseValue,
+ 0
+ );
+ const totalLinkClicks = activeCampaigns.reduce(
+ (sum, camp) => sum + (camp.metrics.clicks || 0),
+ 0
+ );
+ const totalPostEngagements = activeCampaigns.reduce(
+ (sum, camp) => sum + (camp.metrics.totalPostEngagements || 0),
+ 0
+ );
+ const totalFrequency = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.frequency,
+ 0
+ );
+ const totalCpm = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.cpm,
+ 0
+ );
+ const totalCtr = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.ctr,
+ 0
+ );
+ const totalCpc = activeCampaigns.reduce(
+ (sum, camp) => sum + camp.metrics.cpc,
+ 0
+ );
+ const numCampaigns = activeCampaigns.length;
+
+ setSummaryMetrics({
+ totalSpend,
+ totalPurchaseValue,
+ totalLinkClicks,
+ totalImpressions,
+ totalReach,
+ totalPurchases,
+ avgFrequency: totalFrequency / numCampaigns,
+ avgCpm: totalCpm / numCampaigns,
+ avgCtr: totalCtr / numCampaigns,
+ avgCpc: totalCpc / numCampaigns,
+ totalPostEngagements,
+ totalCampaigns: numCampaigns,
+ });
+ }
+ } catch (err) {
+ console.error("Meta Ads fetch error:", err);
+ setError(`Failed to fetch Meta Ads data: ${err.message}`);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchMetaAdsData();
+ }, [timeframe]);
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ {error}
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ Meta Ads Performance
+
+
+
+
+ {[
+ {
+ label: "Active Campaigns",
+ value: summaryMetrics?.totalCampaigns,
+ },
+ {
+ label: "Total Spend",
+ value: summaryMetrics?.totalSpend,
+ options: { isMonetary: true, decimalPlaces: 0 },
+ },
+ { label: "Total Reach", value: summaryMetrics?.totalReach },
+ {
+ label: "Total Impressions",
+ value: summaryMetrics?.totalImpressions,
+ },
+ {
+ label: "Avg Frequency",
+ value: summaryMetrics?.avgFrequency,
+ options: { decimalPlaces: 2 },
+ },
+ {
+ label: "Total Engagements",
+ value: summaryMetrics?.totalPostEngagements,
+ },
+ {
+ label: "Avg CPM",
+ value: summaryMetrics?.avgCpm,
+ options: { isMonetary: true, decimalPlaces: 2 },
+ },
+ {
+ label: "Avg CTR",
+ value: summaryMetrics?.avgCtr,
+ options: { isPercentage: true, decimalPlaces: 2 },
+ },
+ {
+ label: "Avg CPC",
+ value: summaryMetrics?.avgCpc,
+ options: { isMonetary: true, decimalPlaces: 2 },
+ },
+ {
+ label: "Total Link Clicks",
+ value: summaryMetrics?.totalLinkClicks,
+ },
+ { label: "Total Purchases", value: summaryMetrics?.totalPurchases },
+ {
+ label: "Purchase Value",
+ value: summaryMetrics?.totalPurchaseValue,
+ options: { isMonetary: true, decimalPlaces: 0 },
+ },
+ ].map((card) => (
+
+ {summaryCard(card.label, card.value, card.options)}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ |
+ Campaign
+ |
+
+ Spend
+ |
+
+ Reach
+ |
+
+ Impressions
+ |
+
+ CPM
+ |
+
+ CTR
+ |
+
+ Results
+ |
+
+ Value
+ |
+
+ Engagements
+ |
+
+
+
+ {campaigns.map((campaign) => (
+
+ |
+
+
+
+
+ {campaign.objective}
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default MetaAdsOverview;