"use client"; import { Calendar, ChevronLeft, ChevronRight, Globe } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useId, useState } from 'react'; import TimezoneSelect, { type ITimezoneOption } from 'react-timezone-select'; import { getCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGet, getDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGet,getUsageHistoryApiV1OrganizationsUsageRunsGet } from '@/client/sdk.gen'; import type { CurrentUsageResponse, DailyUsageBreakdownResponse,UsageHistoryResponse, WorkflowRunUsageResponse } from '@/client/types.gen'; import { DailyUsageTable } from '@/components/DailyUsageTable'; import { FilterBuilder } from '@/components/filters/FilterBuilder'; import { MediaPreviewButtons, MediaPreviewDialog } from '@/components/MediaPreviewDialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Progress } from '@/components/ui/progress'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { useUserConfig } from '@/context/UserConfigContext'; import { getDispositionBadgeVariant } from '@/lib/dispositionBadgeVariant'; import { usageFilterAttributes } from '@/lib/filterAttributes'; import { decodeFiltersFromURL, encodeFiltersToURL } from '@/lib/filters'; import { ActiveFilter, DateRangeValue } from '@/types/filters'; // Get local timezone const getLocalTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone; export default function UsagePage() { const router = useRouter(); const searchParams = useSearchParams(); const { userConfig, saveUserConfig, loading: userConfigLoading, accessToken, organizationPricing } = useUserConfig(); // Current usage state const [currentUsage, setCurrentUsage] = useState(null); const [isLoadingCurrent, setIsLoadingCurrent] = useState(true); // Usage history state const [usageHistory, setUsageHistory] = useState(null); const [isLoadingHistory, setIsLoadingHistory] = useState(false); const [currentPage, setCurrentPage] = useState(() => { const pageParam = searchParams.get('page'); return pageParam ? parseInt(pageParam, 10) : 1; }); const [isExecutingFilters, setIsExecutingFilters] = useState(false); // Daily usage breakdown state (only for paid orgs) const [dailyUsage, setDailyUsage] = useState(null); const [isLoadingDaily, setIsLoadingDaily] = useState(false); // Initialize filters from URL const [activeFilters, setActiveFilters] = useState(() => { return decodeFiltersFromURL(searchParams, usageFilterAttributes); }); // Media preview dialog const mediaPreview = MediaPreviewDialog({ accessToken }); // Timezone state - initialize with empty string to avoid hydration mismatch const localTimezone = getLocalTimezone(); const [selectedTimezone, setSelectedTimezone] = useState(''); const [savingTimezone, setSavingTimezone] = useState(false); const timezoneSelectId = useId(); // Stable ID for react-select to prevent hydration mismatch // Fetch current usage const fetchCurrentUsage = useCallback(async () => { if (!accessToken) return; try { const response = await getCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGet({ headers: { 'Authorization': `Bearer ${accessToken}`, } }); if (response.data) { setCurrentUsage(response.data); } } catch (error) { console.error('Failed to fetch current usage:', error); } finally { setIsLoadingCurrent(false); } }, [accessToken]); // Fetch usage history const fetchUsageHistory = useCallback(async (page: number, filters?: ActiveFilter[]) => { if (!accessToken) return; setIsLoadingHistory(true); try { let filterParam = undefined; let startDate = ''; let endDate = ''; if (filters && filters.length > 0) { // Extract date range filter if present const dateRangeFilter = filters.find(f => f.attribute.id === 'dateRange'); if (dateRangeFilter && dateRangeFilter.value) { const dateValue = dateRangeFilter.value as DateRangeValue; if (dateValue.from) { // The dates are already in the user's local timezone // Convert to UTC ISO string for the backend startDate = dateValue.from.toISOString(); } if (dateValue.to) { // Convert to UTC ISO string for the backend endDate = dateValue.to.toISOString(); } } // Process other filters (excluding dateRange) const otherFilters = filters.filter(f => f.attribute.id !== 'dateRange'); if (otherFilters.length > 0) { const filterData = otherFilters.map(filter => ({ attribute: filter.attribute.id, type: filter.attribute.type, value: filter.value, })); filterParam = JSON.stringify(filterData); } } const response = await getUsageHistoryApiV1OrganizationsUsageRunsGet({ query: { page, limit: 50, ...(startDate && { start_date: startDate }), ...(endDate && { end_date: endDate }), ...(filterParam && { filters: filterParam }) }, headers: { 'Authorization': `Bearer ${accessToken}`, } }); if (response.data) { setUsageHistory(response.data); } } catch (error) { console.error('Failed to fetch usage history:', error); } finally { setIsLoadingHistory(false); } }, [accessToken]); // Fetch daily usage breakdown const fetchDailyUsage = useCallback(async () => { if (!accessToken || !organizationPricing?.price_per_second_usd) return; setIsLoadingDaily(true); try { const response = await getDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGet({ query: { days: 7 }, headers: { 'Authorization': `Bearer ${accessToken}`, } }); if (response.data) { setDailyUsage(response.data); } } catch (error) { console.error('Failed to fetch daily usage:', error); } finally { setIsLoadingDaily(false); } }, [accessToken, organizationPricing]); // Handle timezone change const handleTimezoneChange = async (timezone: ITimezoneOption | string) => { setSelectedTimezone(timezone); setSavingTimezone(true); try { const tzValue = typeof timezone === 'string' ? timezone : timezone.value; await saveUserConfig({ timezone: tzValue }); } catch (error) { console.error('Failed to save timezone:', error); // Revert to previous timezone on error const prevTz = userConfig?.timezone || localTimezone; setSelectedTimezone(prevTz); } finally { setSavingTimezone(false); } }; // Update timezone when userConfig loads useEffect(() => { if (!userConfigLoading) { // Config has loaded - set the timezone if (userConfig?.timezone) { setSelectedTimezone(userConfig.timezone); } else { // No saved timezone, use local setSelectedTimezone(localTimezone); } } }, [userConfig, userConfigLoading, localTimezone]); // Initial load - fetch when accessToken becomes available useEffect(() => { if (accessToken) { fetchCurrentUsage(); fetchUsageHistory(currentPage, activeFilters); } }, [accessToken, currentPage, activeFilters, fetchUsageHistory, fetchCurrentUsage]); // Fetch daily usage when organizationPricing becomes available useEffect(() => { if (accessToken && organizationPricing?.price_per_second_usd) { fetchDailyUsage(); } }, [accessToken, organizationPricing, fetchDailyUsage]); // Update URL with query parameters const updateUrlParams = useCallback((params: { page?: number; filters?: ActiveFilter[] }) => { const newParams = new URLSearchParams(); if (params.page !== undefined) { newParams.set('page', params.page.toString()); } // Add filters to URL if present if (params.filters && params.filters.length > 0) { const filterString = encodeFiltersToURL(params.filters); if (filterString) { const filterParams = new URLSearchParams(filterString); filterParams.forEach((value, key) => newParams.set(key, value)); } } router.push(`/usage?${newParams.toString()}`); }, [router]); const handleApplyFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); // Reset to first page when applying filters updateUrlParams({ page: 1, filters: activeFilters }); await fetchUsageHistory(1, activeFilters); setIsExecutingFilters(false); }, [activeFilters, fetchUsageHistory, updateUrlParams]); const handleFiltersChange = useCallback((filters: ActiveFilter[]) => { setActiveFilters(filters); }, []); const handleClearFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); updateUrlParams({ page: 1, filters: [] }); // Clear filters from URL await fetchUsageHistory(1, []); // Fetch all runs without filters setIsExecutingFilters(false); }, [fetchUsageHistory, updateUrlParams]); // Handle page change const handlePageChange = (newPage: number) => { setCurrentPage(newPage); updateUrlParams({ page: newPage, filters: activeFilters }); fetchUsageHistory(newPage, activeFilters); }; // Handle row click to navigate to workflow run const handleRowClick = (run: WorkflowRunUsageResponse) => { router.push(`/workflow/${run.workflow_id}/run/${run.id}`); }; // Format date for display with timezone support const formatDate = (dateString: string) => { const date = new Date(dateString); const tzValue = typeof selectedTimezone === 'string' ? selectedTimezone : selectedTimezone.value; // Use local timezone if none selected (during loading) const effectiveTz = tzValue || localTimezone; return date.toLocaleDateString('en-US', { timeZone: effectiveTz, year: 'numeric', month: 'short', day: 'numeric' }); }; // Format datetime for display with timezone support const formatDateTime = (dateString: string) => { const date = new Date(dateString); const tzValue = typeof selectedTimezone === 'string' ? selectedTimezone : selectedTimezone.value; // Use local timezone if none selected (during loading) const effectiveTz = tzValue || localTimezone; return date.toLocaleString('en-US', { timeZone: effectiveTz, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: true }); }; // Format duration for display const formatDuration = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes === 0) return `${remainingSeconds}s`; if (remainingSeconds === 0) return `${minutes}m`; return `${minutes}m ${remainingSeconds}s`; }; return (

Usage Dashboard

Monitor your Dograh Token usage and quota

({ ...base, minHeight: '36px', fontSize: '14px', }), menu: (base) => ({ ...base, zIndex: 9999, }), }} />
{/* Current Period Card */} Current Billing Period {currentUsage && `${formatDate(currentUsage.period_start)} - ${formatDate(currentUsage.period_end)}`} {isLoadingCurrent ? (
) : currentUsage ? (
{organizationPricing?.price_per_second_usd ? ( <>

${(currentUsage.used_amount_usd || 0).toFixed(2)}

Total Cost (USD)

Rate: ${(organizationPricing.price_per_second_usd * 60).toFixed(4)}/minute

) : ( <>

{currentUsage.used_dograh_tokens.toLocaleString()} / {currentUsage.quota_dograh_tokens.toLocaleString()}

Dograh Tokens

)}
{!organizationPricing?.price_per_second_usd && (

{currentUsage.percentage_used}%

Used

)}
{!organizationPricing?.price_per_second_usd && ( )}
Next refresh: {formatDate(currentUsage.next_refresh_date)}
Total Duration: {formatDuration(currentUsage.total_duration_seconds)}
) : (

Unable to load usage data

)}
{/* Daily Usage Table - Only for paid organizations */} {organizationPricing?.price_per_second_usd && (
)} {/* Filter Builder */}
{/* Usage History */}
Usage History View detailed usage by workflow run
{isLoadingHistory ? (
{[...Array(5)].map((_, i) => (
))}
) : usageHistory && usageHistory.runs.length > 0 ? ( <>
Run ID Agent Name Phone Number Disposition Date Duration {organizationPricing?.price_per_second_usd ? 'Cost (USD)' : 'Dograh Tokens'} Actions {usageHistory.runs.map((run) => ( handleRowClick(run)} > #{run.id} {run.workflow_name || 'Unknown'} {run.phone_number || '-'} {run.disposition ? ( {run.disposition} ) : ( - )} {formatDateTime(run.created_at)} {formatDuration(run.call_duration_seconds)} {organizationPricing?.price_per_second_usd && run.charge_usd !== undefined && run.charge_usd !== null ? `$${run.charge_usd.toFixed(2)}` : run.dograh_token_usage.toLocaleString() } ))}
{/* Summary */} {activeFilters.length > 0 && (

Total for filtered period: {usageHistory.total_dograh_tokens.toLocaleString()} Dograh Tokens {' • '} {formatDuration(usageHistory.total_duration_seconds)}

)} {/* Pagination */} {usageHistory.total_pages > 1 && (

Page {usageHistory.page} of {usageHistory.total_pages} ({usageHistory.total_count} total runs)

)} ) : (

No usage history found

)}
{/* Media Preview Dialog */} {mediaPreview.dialog}
); }