From 7ed6cac8f77b494c7c93eec60d5b90c8506e2617 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 28 Dec 2024 15:19:30 -0500 Subject: [PATCH] Merge external custom components into aircalldashboard directly --- .gitignore | 1 + dashboard/src/App.jsx | 1 - .../components/dashboard/AgentStatsCard.jsx | 43 - .../components/dashboard/AircallDashboard.jsx | 67 +- .../components/dashboard/KlaviyoApiTest.jsx | 862 ------------------ .../src/components/dashboard/TableActions.jsx | 25 - .../components/dashboard/TimeRangeSelect.jsx | 44 - 7 files changed, 66 insertions(+), 977 deletions(-) delete mode 100644 dashboard/src/components/dashboard/AgentStatsCard.jsx delete mode 100644 dashboard/src/components/dashboard/KlaviyoApiTest.jsx delete mode 100644 dashboard/src/components/dashboard/TableActions.jsx delete mode 100644 dashboard/src/components/dashboard/TimeRangeSelect.jsx 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' && ( -
-
- - setCustomDateRange(prev => ({ - ...prev, - startDate: e.target.value - }))} - /> -
-
- - setCustomDateRange(prev => ({ - ...prev, - endDate: e.target.value - }))} - /> -
-
- )} -
- ); - - 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 && ( -
- {product.name} 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 ( - - ); -};