diff --git a/.gitignore b/.gitignore
index 3551917..04dc52f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ dist-ssr
dashboard/build/**
dashboard-server/frontend/build/**
**/build/**
+._*
# Build directories
build/
diff --git a/dashboard/src/App.jsx b/dashboard/src/App.jsx
index b279a5e..3bd4141 100644
--- a/dashboard/src/App.jsx
+++ b/dashboard/src/App.jsx
@@ -16,7 +16,6 @@ import Navigation from "@/components/dashboard/Navigation";
import { ScrollProvider } from "@/contexts/ScrollContext";
import DateTimeWeatherDisplay from "@/components/dashboard/DateTime";
import AircallDashboard from "@/components/dashboard/AircallDashboard";
-import KlaviyoApiTest from "@/components/dashboard/KlaviyoApiTest";
import EventFeed from "./components/dashboard/EventFeed";
import StatCards from "./components/dashboard/StatCards";
import ProductGrid from "./components/dashboard/ProductGrid";
diff --git a/dashboard/src/components/dashboard/AgentStatsCard.jsx b/dashboard/src/components/dashboard/AgentStatsCard.jsx
deleted file mode 100644
index eadc12b..0000000
--- a/dashboard/src/components/dashboard/AgentStatsCard.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-// components/dashboard/AgentStatsCard.jsx
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Progress } from "@/components/ui/progress";
-
-export const AgentStatsCard = ({ agent, formatDuration }) => {
- const answerRate = ((agent.answered / agent.total) * 100).toFixed(1);
-
- return (
-
-
- {agent.name}
-
-
-
- Answer Rate
- {answerRate}%
-
-
-
-
-
-
Total Calls
-
{agent.total}
-
-
-
Avg Duration
-
- {formatDuration(agent.average_duration)}
-
-
-
-
Answered
-
{agent.answered}
-
-
-
Missed
-
{agent.missed}
-
-
-
-
- );
-};
\ No newline at end of file
diff --git a/dashboard/src/components/dashboard/AircallDashboard.jsx b/dashboard/src/components/dashboard/AircallDashboard.jsx
index 6be79c5..663c6bf 100644
--- a/dashboard/src/components/dashboard/AircallDashboard.jsx
+++ b/dashboard/src/components/dashboard/AircallDashboard.jsx
@@ -34,8 +34,12 @@ import {
ArrowUpDown,
Timer,
Loader2,
+ Download,
+ Search,
} from "lucide-react";
import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Progress } from "@/components/ui/progress";
import {
Tooltip,
TooltipContent,
@@ -54,8 +58,6 @@ import {
BarChart,
Bar,
} from "recharts";
-import { AgentStatsCard } from "@/components/dashboard/AgentStatsCard";
-import { TableActions } from "@/components/dashboard/TableActions";
const COLORS = {
inbound: "hsl(262.1 83.3% 57.8%)", // Purple
@@ -223,6 +225,67 @@ const AgentPerformanceTable = ({ agents, onSort }) => {
);
};
+const TableActions = ({ onSearch, onExport }) => {
+ return (
+
+
+
+
+ onSearch(e.target.value)}
+ className="pl-8"
+ />
+
+
+
+
+ );
+};
+
+const AgentStatsCard = ({ agent, formatDuration }) => {
+ const answerRate = ((agent.answered / agent.total) * 100).toFixed(1);
+
+ return (
+
+
+ {agent.name}
+
+
+
+ Answer Rate
+ {answerRate}%
+
+
+
+
+
+
Total Calls
+
{agent.total}
+
+
+
Avg Duration
+
+ {formatDuration(agent.average_duration)}
+
+
+
+
Answered
+
{agent.answered}
+
+
+
Missed
+
{agent.missed}
+
+
+
+
+ );
+};
+
const AircallDashboard = () => {
const [timeRange, setTimeRange] = useState("last7days");
const [metrics, setMetrics] = useState(null);
diff --git a/dashboard/src/components/dashboard/KlaviyoApiTest.jsx b/dashboard/src/components/dashboard/KlaviyoApiTest.jsx
deleted file mode 100644
index 57ec853..0000000
--- a/dashboard/src/components/dashboard/KlaviyoApiTest.jsx
+++ /dev/null
@@ -1,862 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import axios from 'axios';
-import { DateTime } from 'luxon';
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Loader2 } from "lucide-react";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import {
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from "@/components/ui/tabs";
-import {
- BarChart,
- Bar,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- ResponsiveContainer
-} from 'recharts';
-
-const TIME_RANGES = [
- { value: 'today', label: 'Today' },
- { value: 'yesterday', label: 'Yesterday' },
- { value: 'thisWeek', label: 'This Week' },
- { value: 'lastWeek', label: 'Last Week' },
- { value: 'thisMonth', label: 'This Month' },
- { value: 'lastMonth', label: 'Last Month' }
-];
-
-const INTERVALS = [
- { value: 'hour', label: 'Hourly' },
- { value: 'day', label: 'Daily' },
- { value: 'week', label: 'Weekly' },
- { value: 'month', label: 'Monthly' }
-];
-
-const METRIC_IDS = {
- PLACED_ORDER: 'Y8cqcF',
- SHIPPED_ORDER: 'VExpdL',
- ACCOUNT_CREATED: 'TeeypV',
- CANCELED_ORDER: 'YjVMNg',
- NEW_BLOG_POST: 'YcxeDr',
- PAYMENT_REFUNDED: 'R7XUYh'
-};
-
-const KlaviyoApiTest = () => {
- // State
- const [loading, setLoading] = useState({});
- const [error, setError] = useState(null);
- const [activeEndpoint, setActiveEndpoint] = useState('metrics');
-
- // Shared state
- const [selectedTimeRange, setSelectedTimeRange] = useState('today');
- const [selectedInterval, setSelectedInterval] = useState('hour');
- const [customDateRange, setCustomDateRange] = useState({
- startDate: '',
- endDate: ''
- });
-
- // Metrics endpoint state
- const [metrics, setMetrics] = useState([]);
- const [selectedMetric, setSelectedMetric] = useState('');
-
- // Events endpoint state
- const [eventResults, setEventResults] = useState(null);
- const [eventStats, setEventStats] = useState(null);
-
- // Aggregation endpoint state
- const [aggregatedData, setAggregatedData] = useState([]);
-
- // Stats endpoint state
- const [periodStats, setPeriodStats] = useState(null);
-
- // Products endpoint state
- const [productStats, setProductStats] = useState(null);
-
- // Feed endpoint state
- const [feedEvents, setFeedEvents] = useState([]);
- const [selectedMetrics, setSelectedMetrics] = useState([METRIC_IDS.PLACED_ORDER]);
-
- useEffect(() => {
- setDefaultDates();
- }, []);
-
- const setDefaultDates = () => {
- const now = DateTime.now().setZone('America/New_York');
- const threeDaysAgo = now.minus({ days: 3 });
-
- setCustomDateRange({
- startDate: threeDaysAgo.toFormat("yyyy-MM-dd'T'HH:mm"),
- endDate: now.toFormat("yyyy-MM-dd'T'HH:mm")
- });
- };
-
- // Shared Components
- const renderTimeRangeControls = ({ showInterval = false }) => (
-
-
Time Range
-
-
-
- {showInterval && (
-
- )}
-
-
- {selectedTimeRange === 'custom' && (
-
- )}
-
- );
-
- const renderMetricSelector = () => (
-
-
-
Select Metric
-
-
-
- );
-
- // API Endpoint Functions
- const fetchMetrics = async () => {
- setLoading(prev => ({ ...prev, metrics: true }));
- setError(null);
- try {
- const response = await axios.get('/api/klaviyo/metrics');
- setMetrics(response.data.data || []);
- } catch (error) {
- console.error('Error fetching metrics:', error);
- setError(error.message);
- } finally {
- setLoading(prev => ({ ...prev, metrics: false }));
- }
- };
-
- const fetchEventsByTimeRange = async () => {
- if (!selectedMetric) return;
- setLoading(prev => ({ ...prev, events: true }));
- setError(null);
- try {
- const response = await axios.get(`/api/klaviyo/events/by-time/${selectedTimeRange}`, {
- params: {
- metricId: selectedMetric,
- includeStats: true,
- startDate: customDateRange.startDate,
- endDate: customDateRange.endDate
- }
- });
- setEventResults(response.data);
- setEventStats(response.data.stats);
- } catch (error) {
- console.error('Error fetching events:', error);
- setError(error.message);
- } finally {
- setLoading(prev => ({ ...prev, events: false }));
- }
- };
-
- const fetchAggregatedEvents = async () => {
- if (!selectedMetric) return;
- setLoading(prev => ({ ...prev, aggregation: true }));
- setError(null);
- try {
- const params = selectedTimeRange === 'custom'
- ? { startDate: customDateRange.startDate, endDate: customDateRange.endDate }
- : { timeRange: selectedTimeRange };
-
- const response = await axios.get('/api/klaviyo/events/aggregate', {
- params: {
- ...params,
- metricId: selectedMetric,
- interval: selectedInterval
- }
- });
- setAggregatedData(response.data.data);
- } catch (error) {
- console.error('Error fetching aggregated events:', error);
- setError(error.message);
- } finally {
- setLoading(prev => ({ ...prev, aggregation: false }));
- }
- };
-
- const fetchPeriodStats = async () => {
- setLoading(prev => ({ ...prev, stats: true }));
- setError(null);
- try {
- const params = selectedTimeRange === 'custom'
- ? { startDate: customDateRange.startDate, endDate: customDateRange.endDate }
- : { timeRange: selectedTimeRange };
-
- const response = await axios.get('/api/klaviyo/events/stats', { params });
- setPeriodStats(response.data);
- } catch (error) {
- console.error('Error fetching period stats:', error);
- setError(error.message);
- } finally {
- setLoading(prev => ({ ...prev, stats: false }));
- }
- };
-
- const fetchEventFeed = async () => {
- setLoading(prev => ({ ...prev, feed: true }));
- setError(null);
- try {
- const params = selectedTimeRange === 'custom'
- ? { startDate: customDateRange.startDate, endDate: customDateRange.endDate }
- : { timeRange: selectedTimeRange };
-
- const response = await axios.get('/api/klaviyo/events/feed', {
- params: {
- ...params,
- metricIds: JSON.stringify(selectedMetrics)
- }
- });
- setFeedEvents(response.data.data);
- } catch (error) {
- console.error('Error fetching event feed:', error);
- setError(error.message);
- } finally {
- setLoading(prev => ({ ...prev, feed: false }));
- }
- };
-
- const fetchProductStats = async () => {
- setLoading(prev => ({ ...prev, products: true }));
- setError(null);
- try {
- const params = selectedTimeRange === 'custom'
- ? { startDate: customDateRange.startDate, endDate: customDateRange.endDate }
- : { timeRange: selectedTimeRange };
-
- const response = await axios.get('/api/klaviyo/events/products', { params });
- setProductStats(response.data);
- } catch (error) {
- console.error('Error fetching product stats:', error);
- setError(error.message);
- } finally {
- setLoading(prev => ({ ...prev, products: false }));
- }
- };
-
- // Endpoint Content Components
- const renderMetricsEndpoint = () => (
-
-
-
- {metrics.length > 0 && (
-
-
- Available Metrics
-
-
-
- {metrics.map((metric) => (
-
-
{metric.attributes?.name || 'Unnamed Metric'}
-
ID: {metric.id}
-
- ))}
-
-
-
- )}
-
- );
-
- const renderEventsEndpoint = () => (
-
- {renderMetricSelector()}
- {renderTimeRangeControls({ showInterval: false })}
-
-
-
- {eventStats && (
-
-
- Event Statistics
-
-
-
-
-
Total Events
-
{eventStats.total}
-
-
-
Time Range
-
- {eventStats.timeRange?.displayStart} to {eventStats.timeRange?.displayEnd}
-
-
-
-
Average Per Day
-
{eventStats.averagePerDay?.toFixed(1)}
-
-
-
-
- )}
-
- {eventResults && (
-
-
- Raw Event Data
-
-
-
- {JSON.stringify(eventResults, null, 2)}
-
-
-
- )}
-
- );
-
- const renderAggregateEndpoint = () => (
-
- {renderMetricSelector()}
- {renderTimeRangeControls({ showInterval: true })}
-
-
-
- {aggregatedData.length > 0 && (
-
-
- Aggregated Events
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- );
-
- const renderStatsEndpoint = () => (
-
- {renderTimeRangeControls({ showInterval: false })}
-
-
-
- {periodStats && (
-
-
-
- Revenue Overview
-
-
-
-
-
Total Revenue
-
${periodStats.stats.revenue?.toFixed(2) || '0.00'}
- {periodStats.stats.prevPeriodRevenue && (
-
- Previous: ${periodStats.stats.prevPeriodRevenue.toFixed(2)}
-
- )}
-
-
-
Order Count
-
{periodStats.stats.orderCount || 0}
-
- Items: {periodStats.stats.itemCount || 0}
-
-
-
-
Average Order Value
-
${periodStats.stats.averageOrderValue?.toFixed(2) || '0.00'}
-
- Items per order: {periodStats.stats.averageItemsPerOrder?.toFixed(1) || '0.0'}
-
-
-
-
-
-
-
-
-
- Order Types
-
-
-
- {Object.entries(periodStats.stats.orderTypes || {}).map(([type, data]) => (
-
- {type.replace(/([A-Z])/g, ' $1').trim()}
- {data.count} ({data.percentage?.toFixed(1)}%)
-
- ))}
-
-
-
-
-
-
- Order Value Range
-
-
-
- {periodStats.stats.orderValueRange && (
- <>
-
- Largest Order
- ${periodStats.stats.orderValueRange.largest?.toFixed(2)} (#{periodStats.stats.orderValueRange.largestOrderId})
-
-
- Smallest Order
- ${periodStats.stats.orderValueRange.smallest?.toFixed(2)} (#{periodStats.stats.orderValueRange.smallestOrderId})
-
- >
- )}
-
-
-
-
-
-
-
-
- Products ({periodStats.stats.products?.total || 0})
-
-
-
- {periodStats.stats.products?.list?.map(product => (
-
- {product.name}
- {product.count} sold
-
- ))}
-
-
-
-
-
-
- Categories ({periodStats.stats.categories?.total || 0})
-
-
-
- {periodStats.stats.categories?.list?.map(category => (
-
- {category.name}
- {category.count} items
-
- ))}
-
-
-
-
-
-
-
-
- Shipping
-
-
- Shipped orders: {periodStats.stats.shipping?.shippedCount || 0}
-
- {periodStats.stats.shipping?.locations?.map(location => (
-
- {location.name}
- {location.count} orders
-
- ))}
-
-
-
-
-
-
- Peak Times
-
-
-
- {periodStats.stats.peakOrderHour && (
-
- Peak Hour
- {periodStats.stats.peakOrderHour.displayHour} ({periodStats.stats.peakOrderHour.count} orders)
-
- )}
- {periodStats.stats.bestRevenueDay && (
-
- Best Day
- {periodStats.stats.bestRevenueDay.displayDate} (${periodStats.stats.bestRevenueDay.amount?.toFixed(2)})
-
- )}
-
-
-
-
-
-
-
-
- Refunds
-
-
-
-
- Count
- {periodStats.stats.refunds?.count || 0}
-
-
- Total Amount
- ${periodStats.stats.refunds?.total?.toFixed(2) || '0.00'}
-
-
-
-
-
-
-
- Canceled Orders
-
-
-
-
- Count
- {periodStats.stats.canceledOrders?.count || 0}
-
-
- Total Amount
- ${periodStats.stats.canceledOrders?.total?.toFixed(2) || '0.00'}
-
-
-
-
-
-
- )}
-
- );
-
- const renderProductsEndpoint = () => (
-
- {renderTimeRangeControls({ showInterval: false })}
-
-
-
- {productStats && (
-
- {/* Products */}
-
-
- Products ({productStats.stats.products?.total || 0})
-
-
-
- {productStats.stats.products?.list?.map(product => (
-
-
- {product.imageUrl && (
-
-

e.target.style.display = 'none'}
- />
-
- )}
-
-
{product.name}
-
SKU: {product.sku}
-
Brand: {product.brand}
-
Price: ${product.price.toFixed(2)}
-
-
-
${product.totalRevenue.toFixed(2)}
-
{product.totalQuantity} units sold
-
{product.orderCount} orders
-
Avg: ${product.averageOrderValue.toFixed(2)}/order
-
-
-
- ))}
-
-
-
-
- {/* Brands */}
-
-
- Brands ({productStats.stats.brands?.total || 0})
-
-
-
- {productStats.stats.brands?.list?.map(brand => (
-
-
-
{brand.name}
-
- {brand.productCount} products, {brand.orderCount} orders
-
-
-
-
{brand.totalQuantity} units sold
-
${brand.totalRevenue.toFixed(2)} revenue
-
-
- ))}
-
-
-
-
- {/* Categories */}
-
-
- Categories ({productStats.stats.categories?.total || 0})
-
-
-
- {productStats.stats.categories?.list?.map(category => (
-
-
-
{category.name}
-
- {category.productCount} products, {category.orderCount} orders
-
-
-
-
{category.totalQuantity} units sold
-
${category.totalRevenue.toFixed(2)} revenue
-
-
- ))}
-
-
-
-
- )}
-
- );
-
- const renderFeedEndpoint = () => (
-
- {renderTimeRangeControls({ showInterval: false })}
-
-
-
Select Event Types
-
- {Object.entries(METRIC_IDS).map(([key, value]) => (
-
- ))}
-
-
-
-
-
- {feedEvents.length > 0 && (
-
- {feedEvents.map(event => (
-
-
- {event.type}
-
-
-
- {JSON.stringify(event.attributes, null, 2)}
-
-
-
- ))}
-
- )}
-
- );
-
- return (
-
-
- Klaviyo API Endpoints
-
-
- {error && (
-
- Error
- {error}
-
- )}
-
-
-
- Metrics
- Events
- Aggregate
- Stats
- Products
- Feed
-
-
-
- {renderMetricsEndpoint()}
-
-
-
- {renderEventsEndpoint()}
-
-
-
- {renderAggregateEndpoint()}
-
-
-
- {renderStatsEndpoint()}
-
-
-
- {renderProductsEndpoint()}
-
-
-
- {renderFeedEndpoint()}
-
-
-
-
- );
-};
-
-export default KlaviyoApiTest;
\ No newline at end of file
diff --git a/dashboard/src/components/dashboard/TableActions.jsx b/dashboard/src/components/dashboard/TableActions.jsx
deleted file mode 100644
index 770f5b0..0000000
--- a/dashboard/src/components/dashboard/TableActions.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-// components/TableActions.jsx
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { Download, Search } from "lucide-react";
-
-export const TableActions = ({ onSearch, onExport }) => {
- return (
-
-
-
-
- onSearch(e.target.value)}
- className="pl-8"
- />
-
-
-
-
- );
-};
\ No newline at end of file
diff --git a/dashboard/src/components/dashboard/TimeRangeSelect.jsx b/dashboard/src/components/dashboard/TimeRangeSelect.jsx
deleted file mode 100644
index 548ac57..0000000
--- a/dashboard/src/components/dashboard/TimeRangeSelect.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-
-const timeRangeOptions = [
- { value: "today", label: "Today" },
- { value: "yesterday", label: "Yesterday" },
- { value: "last7days", label: "Last 7 Days" },
- { value: "last30days", label: "Last 30 Days" },
- { value: "last90days", label: "Last 90 Days" },
-];
-
-export const TimeRangeSelect = ({
- value,
- onChange,
- className,
- allowedRanges = [],
-}) => {
- const filteredOptions = allowedRanges.length
- ? timeRangeOptions.filter((option) => allowedRanges.includes(option.value))
- : timeRangeOptions;
-
- console.log("Allowed Ranges prop:", allowedRanges); // Debugging line
- console.log("Filtered Options:", filteredOptions); // Debugging line
-
- return (
-
- );
-};