# thinkrank/1.0.0/src/admin/components/pages/UsageBreakdown.js

ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console &amp; Local SEO, version 1.0.0. 270 lines.

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/pages/UsageBreakdown.js
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.0/raw/src/admin/components/pages/UsageBreakdown.js
- Modified: 2025-08-10T07:57:44+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/pages/UsageBreakdown.js#L10-L20`.

```javascript
/**
 * Usage Breakdown Component
 *
 * Detailed AI usage breakdown with tabular view
 *
 * @package ThinkRank
 * @since 1.0.0
 */

import { __ } from '@wordpress/i18n';
import { Spinner, Notice, SelectControl, Button } from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import ModelBadge from '../shared/ModelBadge';

/**
 * Usage Breakdown Table Component
 */
const UsageBreakdownTable = ({ records, isLoading }) => {
    if (isLoading) {
        return (
            <div className="thinkrank-text-center thinkrank-py-8">
                <Spinner />
                <p className="thinkrank-mt-4 thinkrank-text-secondary">
                    {__('Loading usage data...', 'thinkrank')}
                </p>
            </div>
        );
    }

    if (!records || records.length === 0) {
        return (
            <div className="thinkrank-text-center thinkrank-py-8">
                <p className="thinkrank-text-secondary">
                    {__('No usage data found for the selected period.', 'thinkrank')}
                </p>
            </div>
        );
    }

    return (
        <div className="thinkrank-overflow-x-auto">
            <table className="thinkrank-table thinkrank-w-full">
                <thead>
                    <tr className="thinkrank-border-b thinkrank-border-gray-200">
                        <th className="thinkrank-text-left thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
                            {__('Date & Time', 'thinkrank')}
                        </th>
                        <th className="thinkrank-text-left thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
                            {__('Action', 'thinkrank')}
                        </th>
                        <th className="thinkrank-text-left thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
                            {__('AI Model', 'thinkrank')}
                        </th>
                        <th className="thinkrank-text-right thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
                            {__('Tokens', 'thinkrank')}
                        </th>
                        <th className="thinkrank-text-right thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
                            {__('Est. Cost', 'thinkrank')}
                        </th>
                    </tr>
                </thead>
                <tbody>
                    {records.map((record) => (
                        <tr key={record.id} className="thinkrank-border-b thinkrank-border-gray-100 hover:thinkrank-bg-gray-50">
                            <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm">
                                <div className="thinkrank-font-medium thinkrank-text-primary">
                                    {record.formatted_date}
                                </div>
                            </td>
                            <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm">
                                <div className="thinkrank-font-medium thinkrank-text-primary">
                                    {record.action.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()).replace(/\bSeo\b/g, 'SEO')}
                                </div>
                            </td>
                            <td className="thinkrank-py-3 thinkrank-px-4">
                                <ModelBadge
                                    provider={record.provider}
                                    model={record.model}
                                />
                            </td>
                            <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm thinkrank-text-right thinkrank-font-mono">
                                {record.tokens_used.toLocaleString()}
                            </td>
                            <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm thinkrank-text-right thinkrank-font-mono">
                                ${record.estimated_cost.toFixed(4)}
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
        </div>
    );
};

/**
 * Pagination Component
 */
const Pagination = ({ currentPage, totalPages, onPageChange, isLoading }) => {
    if (totalPages <= 1) return null;

    const pages = [];
    const maxVisiblePages = 5;
    
    let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
    let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
    
    if (endPage - startPage + 1 < maxVisiblePages) {
        startPage = Math.max(1, endPage - maxVisiblePages + 1);
    }

    for (let i = startPage; i <= endPage; i++) {
        pages.push(i);
    }

    return (
        <div className="thinkrank-flex thinkrank-items-center thinkrank-justify-between thinkrank-mt-6 thinkrank-pt-4 thinkrank-border-t thinkrank-border-gray-200">
            <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-2">
                <Button
                    variant="secondary"
                    disabled={currentPage === 1 || isLoading}
                    onClick={() => onPageChange(currentPage - 1)}
                    size="small"
                >
                    {__('Previous', 'thinkrank')}
                </Button>
                
                {pages.map(page => (
                    <Button
                        key={page}
                        variant={page === currentPage ? 'primary' : 'secondary'}
                        disabled={isLoading}
                        onClick={() => onPageChange(page)}
                        size="small"
                    >
                        {page}
                    </Button>
                ))}
                
                <Button
                    variant="secondary"
                    disabled={currentPage === totalPages || isLoading}
                    onClick={() => onPageChange(currentPage + 1)}
                    size="small"
                >
                    {__('Next', 'thinkrank')}
                </Button>
            </div>
            
            <div className="thinkrank-text-sm thinkrank-text-secondary">
                {__('Page', 'thinkrank')} {currentPage} {__('of', 'thinkrank')} {totalPages}
            </div>
        </div>
    );
};

/**
 * Main Usage Breakdown Component
 */
const UsageBreakdown = () => {
    const [period, setPeriod] = useState('30d');
    const [currentPage, setCurrentPage] = useState(1);
    const [perPage] = useState(20);
    const [data, setData] = useState(null);
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);

    // Period options
    const periodOptions = [
        { label: __('Last 7 days', 'thinkrank'), value: '7d' },
        { label: __('Last 30 days', 'thinkrank'), value: '30d' },
        { label: __('Last 90 days', 'thinkrank'), value: '90d' },
        { label: __('All time', 'thinkrank'), value: 'all' }
    ];

    // Fetch usage breakdown data
    const fetchUsageBreakdown = async (selectedPeriod = period, page = currentPage) => {
        setIsLoading(true);
        setError(null);

        try {
            const response = await apiFetch({
                path: `/thinkrank/v1/analytics/usage?period=${selectedPeriod}&page=${page}&per_page=${perPage}`,
                method: 'GET'
            });

            if (response.success) {
                setData(response.data);
            } else {
                setError(__('Failed to load usage breakdown data.', 'thinkrank'));
            }
        } catch (err) {
            console.error('Usage breakdown fetch error:', err);
            setError(__('Failed to load usage breakdown data.', 'thinkrank'));
        } finally {
            setIsLoading(false);
        }
    };

    // Handle period change
    const handlePeriodChange = (newPeriod) => {
        setPeriod(newPeriod);
        setCurrentPage(1);
        fetchUsageBreakdown(newPeriod, 1);
    };

    // Handle page change
    const handlePageChange = (newPage) => {
        setCurrentPage(newPage);
        fetchUsageBreakdown(period, newPage);
    };

    // Initial data fetch
    useEffect(() => {
        fetchUsageBreakdown();
    }, []);

    return (
        <div className="thinkrank-ui">
            <div className="thinkrank-card thinkrank-card--elevated thinkrank-mb-6">
                <div className="thinkrank-card__header">
                    <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-flex-wrap thinkrank-gap-4">
                        <h2 className="thinkrank-text-2xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-0">
                            {__('AI Usage Breakdown', 'thinkrank')}
                        </h2>
                        <SelectControl
                            label={__('Time Period', 'thinkrank')}
                            value={period}
                            options={periodOptions}
                            onChange={handlePeriodChange}
                            className="thinkrank-min-w-40"
                            __next40pxDefaultSize={true}
                            __nextHasNoMarginBottom={true}
                        />
                    </div>
                </div>
                <div className="thinkrank-card__body">
                    {error && (
                        <Notice status="error" isDismissible={false}>
                            {error}
                        </Notice>
                    )}

                    <UsageBreakdownTable 
                        records={data?.usage_records || []} 
                        isLoading={isLoading} 
                    />

                    {data?.pagination && (
                        <Pagination
                            currentPage={data.pagination.page}
                            totalPages={data.pagination.total_pages}
                            onPageChange={handlePageChange}
                            isLoading={isLoading}
                        />
                    )}

                    {data?.pagination && (
                        <div className="thinkrank-mt-4 thinkrank-text-sm thinkrank-text-secondary thinkrank-text-center">
                            {__('Showing', 'thinkrank')} {data.pagination.total_records} {__('total records', 'thinkrank')}
                        </div>
                    )}
                </div>
            </div>
        </div>
    );
};

export default UsageBreakdown;

```
