PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / src / admin / components / pages / UsageBreakdown.js

UsageBreakdown.js in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.0, at src/admin/components/pages/UsageBreakdown.js

270 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Usage Breakdown Component
3 *
4 * Detailed AI usage breakdown with tabular view
5 *
6 * @package ThinkRank
7 * @since 1.0.0
8 */
9
10 import { __ } from '@wordpress/i18n';
11 import { Spinner, Notice, SelectControl, Button } from '@wordpress/components';
12 import { useState, useEffect } from '@wordpress/element';
13 import apiFetch from '@wordpress/api-fetch';
14 import ModelBadge from '../shared/ModelBadge';
15
16 /**
17 * Usage Breakdown Table Component
18 */
19 const UsageBreakdownTable = ({ records, isLoading }) => {
20 if (isLoading) {
21 return (
22 <div className="thinkrank-text-center thinkrank-py-8">
23 <Spinner />
24 <p className="thinkrank-mt-4 thinkrank-text-secondary">
25 {__('Loading usage data...', 'thinkrank')}
26 </p>
27 </div>
28 );
29 }
30
31 if (!records || records.length === 0) {
32 return (
33 <div className="thinkrank-text-center thinkrank-py-8">
34 <p className="thinkrank-text-secondary">
35 {__('No usage data found for the selected period.', 'thinkrank')}
36 </p>
37 </div>
38 );
39 }
40
41 return (
42 <div className="thinkrank-overflow-x-auto">
43 <table className="thinkrank-table thinkrank-w-full">
44 <thead>
45 <tr className="thinkrank-border-b thinkrank-border-gray-200">
46 <th className="thinkrank-text-left thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
47 {__('Date & Time', 'thinkrank')}
48 </th>
49 <th className="thinkrank-text-left thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
50 {__('Action', 'thinkrank')}
51 </th>
52 <th className="thinkrank-text-left thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
53 {__('AI Model', 'thinkrank')}
54 </th>
55 <th className="thinkrank-text-right thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
56 {__('Tokens', 'thinkrank')}
57 </th>
58 <th className="thinkrank-text-right thinkrank-py-3 thinkrank-px-4 thinkrank-font-semibold thinkrank-text-primary">
59 {__('Est. Cost', 'thinkrank')}
60 </th>
61 </tr>
62 </thead>
63 <tbody>
64 {records.map((record) => (
65 <tr key={record.id} className="thinkrank-border-b thinkrank-border-gray-100 hover:thinkrank-bg-gray-50">
66 <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm">
67 <div className="thinkrank-font-medium thinkrank-text-primary">
68 {record.formatted_date}
69 </div>
70 </td>
71 <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm">
72 <div className="thinkrank-font-medium thinkrank-text-primary">
73 {record.action.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()).replace(/\bSeo\b/g, 'SEO')}
74 </div>
75 </td>
76 <td className="thinkrank-py-3 thinkrank-px-4">
77 <ModelBadge
78 provider={record.provider}
79 model={record.model}
80 />
81 </td>
82 <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm thinkrank-text-right thinkrank-font-mono">
83 {record.tokens_used.toLocaleString()}
84 </td>
85 <td className="thinkrank-py-3 thinkrank-px-4 thinkrank-text-sm thinkrank-text-right thinkrank-font-mono">
86 ${record.estimated_cost.toFixed(4)}
87 </td>
88 </tr>
89 ))}
90 </tbody>
91 </table>
92 </div>
93 );
94 };
95
96 /**
97 * Pagination Component
98 */
99 const Pagination = ({ currentPage, totalPages, onPageChange, isLoading }) => {
100 if (totalPages <= 1) return null;
101
102 const pages = [];
103 const maxVisiblePages = 5;
104
105 let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
106 let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
107
108 if (endPage - startPage + 1 < maxVisiblePages) {
109 startPage = Math.max(1, endPage - maxVisiblePages + 1);
110 }
111
112 for (let i = startPage; i <= endPage; i++) {
113 pages.push(i);
114 }
115
116 return (
117 <div className="thinkrank-flex thinkrank-items-center thinkrank-justify-between thinkrank-mt-6 thinkrank-pt-4 thinkrank-border-t thinkrank-border-gray-200">
118 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-2">
119 <Button
120 variant="secondary"
121 disabled={currentPage === 1 || isLoading}
122 onClick={() => onPageChange(currentPage - 1)}
123 size="small"
124 >
125 {__('Previous', 'thinkrank')}
126 </Button>
127
128 {pages.map(page => (
129 <Button
130 key={page}
131 variant={page === currentPage ? 'primary' : 'secondary'}
132 disabled={isLoading}
133 onClick={() => onPageChange(page)}
134 size="small"
135 >
136 {page}
137 </Button>
138 ))}
139
140 <Button
141 variant="secondary"
142 disabled={currentPage === totalPages || isLoading}
143 onClick={() => onPageChange(currentPage + 1)}
144 size="small"
145 >
146 {__('Next', 'thinkrank')}
147 </Button>
148 </div>
149
150 <div className="thinkrank-text-sm thinkrank-text-secondary">
151 {__('Page', 'thinkrank')} {currentPage} {__('of', 'thinkrank')} {totalPages}
152 </div>
153 </div>
154 );
155 };
156
157 /**
158 * Main Usage Breakdown Component
159 */
160 const UsageBreakdown = () => {
161 const [period, setPeriod] = useState('30d');
162 const [currentPage, setCurrentPage] = useState(1);
163 const [perPage] = useState(20);
164 const [data, setData] = useState(null);
165 const [isLoading, setIsLoading] = useState(false);
166 const [error, setError] = useState(null);
167
168 // Period options
169 const periodOptions = [
170 { label: __('Last 7 days', 'thinkrank'), value: '7d' },
171 { label: __('Last 30 days', 'thinkrank'), value: '30d' },
172 { label: __('Last 90 days', 'thinkrank'), value: '90d' },
173 { label: __('All time', 'thinkrank'), value: 'all' }
174 ];
175
176 // Fetch usage breakdown data
177 const fetchUsageBreakdown = async (selectedPeriod = period, page = currentPage) => {
178 setIsLoading(true);
179 setError(null);
180
181 try {
182 const response = await apiFetch({
183 path: `/thinkrank/v1/analytics/usage?period=${selectedPeriod}&page=${page}&per_page=${perPage}`,
184 method: 'GET'
185 });
186
187 if (response.success) {
188 setData(response.data);
189 } else {
190 setError(__('Failed to load usage breakdown data.', 'thinkrank'));
191 }
192 } catch (err) {
193 console.error('Usage breakdown fetch error:', err);
194 setError(__('Failed to load usage breakdown data.', 'thinkrank'));
195 } finally {
196 setIsLoading(false);
197 }
198 };
199
200 // Handle period change
201 const handlePeriodChange = (newPeriod) => {
202 setPeriod(newPeriod);
203 setCurrentPage(1);
204 fetchUsageBreakdown(newPeriod, 1);
205 };
206
207 // Handle page change
208 const handlePageChange = (newPage) => {
209 setCurrentPage(newPage);
210 fetchUsageBreakdown(period, newPage);
211 };
212
213 // Initial data fetch
214 useEffect(() => {
215 fetchUsageBreakdown();
216 }, []);
217
218 return (
219 <div className="thinkrank-ui">
220 <div className="thinkrank-card thinkrank-card--elevated thinkrank-mb-6">
221 <div className="thinkrank-card__header">
222 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-flex-wrap thinkrank-gap-4">
223 <h2 className="thinkrank-text-2xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-0">
224 {__('AI Usage Breakdown', 'thinkrank')}
225 </h2>
226 <SelectControl
227 label={__('Time Period', 'thinkrank')}
228 value={period}
229 options={periodOptions}
230 onChange={handlePeriodChange}
231 className="thinkrank-min-w-40"
232 __next40pxDefaultSize={true}
233 __nextHasNoMarginBottom={true}
234 />
235 </div>
236 </div>
237 <div className="thinkrank-card__body">
238 {error && (
239 <Notice status="error" isDismissible={false}>
240 {error}
241 </Notice>
242 )}
243
244 <UsageBreakdownTable
245 records={data?.usage_records || []}
246 isLoading={isLoading}
247 />
248
249 {data?.pagination && (
250 <Pagination
251 currentPage={data.pagination.page}
252 totalPages={data.pagination.total_pages}
253 onPageChange={handlePageChange}
254 isLoading={isLoading}
255 />
256 )}
257
258 {data?.pagination && (
259 <div className="thinkrank-mt-4 thinkrank-text-sm thinkrank-text-secondary thinkrank-text-center">
260 {__('Showing', 'thinkrank')} {data.pagination.total_records} {__('total records', 'thinkrank')}
261 </div>
262 )}
263 </div>
264 </div>
265 </div>
266 );
267 };
268
269 export default UsageBreakdown;
270