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 / essential-seo / AnalyticsTab.js

AnalyticsTab.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/essential-seo/AnalyticsTab.js

1,490 lines 84.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Analytics Tab Component
3 *
4 * SEO Analytics & Intelligence tab with Google API integration,
5 * dashboard data visualization, and AI-powered insights.
6 * Follows ThinkRank patterns from Site Identity and Performance tabs.
7 *
8 * @package ThinkRank
9 * @since 1.0.0
10 */
11
12 import { useState, useEffect } from '@wordpress/element';
13 import { __ } from '@wordpress/i18n';
14 import {
15 Card,
16 CardBody,
17 CardHeader,
18 Spinner,
19 Notice,
20 Button,
21 Flex,
22 FlexItem,
23 SelectControl,
24 __experimentalText as Text,
25 __experimentalHeading as Heading,
26 __experimentalDivider as Divider
27 } from '@wordpress/components';
28 import apiFetch from '@wordpress/api-fetch';
29
30 // Import shared components following ThinkRank patterns
31 import SettingsCard from '../common/SettingsCard';
32 import OptimizationButton from '../common/OptimizationButton';
33
34 /**
35 * Default analytics settings
36 * Following ThinkRank settings patterns
37 */
38 const getDefaultSettings = () => ({
39 seo_analytics_enabled: false,
40 seo_analytics_setup_completed: false,
41 seo_analytics_google_analytics_property_id: '',
42 search_console_property: '',
43 seo_analytics_enable_ai_insights: true,
44 seo_analytics_enable_automated_alerts: false,
45 seo_analytics_monitoring_frequency: 3600,
46 seo_analytics_cache_analytics_data: true
47 });
48
49 /**
50 * Analytics Tab Component
51 */
52 const AnalyticsTab = ({ activeSubSection = 'setup', onNavigate }) => {
53 // State management following Site Identity patterns
54 const [isLoading, setIsLoading] = useState(true);
55 const [isSaving, setIsSaving] = useState(false);
56
57 const [isRefreshing, setIsRefreshing] = useState(false);
58 const [settings, setSettings] = useState({});
59 const [hasChanges, setHasChanges] = useState(false);
60 const [notice, setNotice] = useState(null);
61
62 const [dashboardData, setDashboardData] = useState(null);
63 const [seoOpportunities, setSeoOpportunities] = useState(null);
64 const [dateRange, setDateRange] = useState('30d');
65
66 // Intelligence enhancement state
67 const [intelligentDashboardData, setIntelligentDashboardData] = useState(null);
68 const [intelligentOpportunities, setIntelligentOpportunities] = useState(null);
69 const [seoInsights, setSeoInsights] = useState(null);
70 const [isLoadingIntelligence, setIsLoadingIntelligence] = useState(false);
71
72 /**
73 * Check if dashboard data contains valid/meaningful data
74 * @param {Object} data Dashboard data object
75 * @return {boolean} True if data contains meaningful information
76 */
77 const hasValidDashboardData = (data) => {
78 if (!data) return false;
79
80 // Check if traffic data has meaningful values
81 const hasTrafficData = data.traffic &&
82 Object.keys(data.traffic).length > 0 &&
83 (data.traffic.sessions > 0 || data.traffic.pageviews > 0);
84
85 // Check if search performance has data
86 const hasSearchData = data.search_performance &&
87 data.search_performance.rows &&
88 data.search_performance.rows.length > 0;
89
90 // Check if core web vitals has data
91 const hasWebVitalsData = data.core_web_vitals &&
92 Object.keys(data.core_web_vitals).length > 0 &&
93 !data.core_web_vitals.error;
94
95 return hasTrafficData || hasSearchData || hasWebVitalsData;
96 };
97
98 useEffect(() => {
99 loadSettings();
100 if (activeSubSection === 'dashboard' || activeSubSection === 'opportunities' || activeSubSection === 'insights') {
101 loadDashboardData();
102 }
103 }, [activeSubSection]);
104
105 useEffect(() => {
106 if (activeSubSection === 'dashboard' || activeSubSection === 'opportunities' || activeSubSection === 'insights') {
107 loadDashboardData();
108 }
109 }, [dateRange]);
110
111 // Clear notice when component unmounts or tab changes
112 useEffect(() => {
113 return () => {
114 setNotice(null);
115 };
116 }, []);
117
118 /**
119 * Load settings from API
120 * Following Site Identity loadSettings pattern
121 */
122 const loadSettings = async () => {
123 try {
124 setIsLoading(true);
125 const response = await apiFetch({
126 path: '/thinkrank/v1/settings-management/category/seo_analytics',
127 method: 'GET'
128 });
129
130 if (response.success && response.data && response.data.settings) {
131 setSettings({ ...getDefaultSettings(), ...response.data.settings });
132 } else {
133 setSettings(getDefaultSettings());
134 }
135 } catch (error) {
136 console.error('Analytics settings load error:', error);
137 setSettings(getDefaultSettings());
138 setNotice({
139 status: 'error',
140 message: __('Failed to load analytics settings. Please try again.', 'thinkrank')
141 });
142 } finally {
143 setIsLoading(false);
144 }
145 };
146
147 /**
148 * Save settings to API
149 * Following Site Identity saveSettings pattern
150 */
151 const saveSettings = async () => {
152 try {
153 setIsSaving(true);
154 const response = await apiFetch({
155 path: '/thinkrank/v1/settings-management/category/seo_analytics',
156 method: 'POST',
157 data: { settings }
158 });
159
160 if (response.success) {
161 setHasChanges(false);
162 setNotice({
163 status: 'success',
164 message: __('Analytics settings saved successfully.', 'thinkrank')
165 });
166 } else {
167 setNotice({
168 status: 'error',
169 message: response.message || __('Failed to save settings.', 'thinkrank')
170 });
171 }
172 } catch (error) {
173 console.error('Analytics settings save error:', error);
174 setNotice({
175 status: 'error',
176 message: __('Failed to save analytics settings. Please try again.', 'thinkrank')
177 });
178 } finally {
179 setIsSaving(false);
180 }
181 };
182
183 /**
184 * Handle setting changes
185 * Following Site Identity handleSettingChange pattern
186 */
187 const handleSettingChange = (key, value) => {
188 setSettings(prev => ({
189 ...prev,
190 [key]: value
191 }));
192 setHasChanges(true);
193 };
194
195
196
197 /**
198 * Load dashboard data with intelligence enhancements
199 */
200 const loadDashboardData = async () => {
201 try {
202 setIsRefreshing(true);
203 setIsLoadingIntelligence(true);
204
205 // Load basic dashboard data (maintain backward compatibility)
206 const dashboardResponse = await apiFetch({
207 path: `/thinkrank/v1/seo-analytics/dashboard?date_range=${dateRange}`,
208 method: 'GET'
209 });
210
211 if (dashboardResponse.success) {
212 setDashboardData(dashboardResponse.data);
213 }
214
215 // Load basic SEO opportunities (maintain backward compatibility)
216 const opportunitiesResponse = await apiFetch({
217 path: `/thinkrank/v1/seo-analytics/opportunities?date_range=${dateRange}`,
218 method: 'GET'
219 });
220
221 if (opportunitiesResponse.success) {
222 setSeoOpportunities(opportunitiesResponse.data);
223 }
224
225 // Load intelligent dashboard data (new intelligence features)
226 if (settings.seo_analytics_enable_ai_insights) {
227 try {
228 const intelligentDashboardResponse = await apiFetch({
229 path: `/thinkrank/v1/seo-analytics/intelligent-dashboard?date_range=${dateRange}`,
230 method: 'GET'
231 });
232
233 if (intelligentDashboardResponse.success) {
234 setIntelligentDashboardData(intelligentDashboardResponse.data);
235 } else {
236 // Use the specific message from the backend
237 const message = intelligentDashboardResponse.message ||
238 __('AI dashboard insights require data collection for meaningful analysis.', 'thinkrank');
239
240 if (!notice || notice.status !== 'error') {
241 setNotice({
242 status: 'info',
243 message: message
244 });
245 }
246 }
247 } catch (intelligenceError) {
248 // Only show error notice if it's a real network/server error
249 if (intelligenceError.message && !intelligenceError.message.includes('retrieved')) {
250 setNotice({
251 status: 'info',
252 message: __('AI insights require data collection. Please ensure your Google Analytics and Search Console are connected and have collected data for meaningful analysis.', 'thinkrank')
253 });
254 }
255 }
256
257 // Load intelligent opportunities (new intelligence features)
258 try {
259 const intelligentOpportunitiesResponse = await apiFetch({
260 path: `/thinkrank/v1/seo-analytics/intelligent-opportunities?date_range=${dateRange}`,
261 method: 'GET'
262 });
263
264 if (intelligentOpportunitiesResponse.success) {
265 setIntelligentOpportunities(intelligentOpportunitiesResponse.data);
266 }
267 // Don't show notice for opportunities - they're handled in the UI cards
268 } catch (intelligenceError) {
269 // Silently handle - opportunities are optional and shown in UI cards
270 }
271
272 // Load SEO insights (new intelligence features)
273 try {
274 const insightsResponse = await apiFetch({
275 path: `/thinkrank/v1/seo-analytics/insights?date_range=${dateRange}`,
276 method: 'GET'
277 });
278
279 if (insightsResponse.success) {
280 setSeoInsights(insightsResponse.data);
281 } else {
282 // Use the specific message from the backend for insights
283 const message = insightsResponse.message ||
284 __('SEO insights require data collection for meaningful analysis.', 'thinkrank');
285
286 if (!notice || notice.status !== 'error') {
287 setNotice({
288 status: 'info',
289 message: message
290 });
291 }
292 }
293 } catch (intelligenceError) {
294 // Silently handle - insights are optional and shown in UI cards
295 }
296 }
297
298 } catch (error) {
299 console.error('Dashboard data load error:', error);
300 setNotice({
301 status: 'error',
302 message: __('Failed to load analytics data. Please check your Google API connections in the Integrations tab.', 'thinkrank')
303 });
304 } finally {
305 setIsRefreshing(false);
306 setIsLoadingIntelligence(false);
307 }
308 };
309
310 /**
311 * Refresh analytics data
312 */
313 const refreshData = async () => {
314 try {
315 setIsRefreshing(true);
316 const response = await apiFetch({
317 path: '/thinkrank/v1/seo-analytics/refresh',
318 method: 'POST'
319 });
320
321 if (response.success) {
322 setNotice({
323 status: 'success',
324 message: response.data.message
325 });
326 // Reload dashboard data
327 await loadDashboardData();
328 } else {
329 setNotice({
330 status: 'error',
331 message: response.message || __('Failed to refresh data.', 'thinkrank')
332 });
333 }
334 } catch (error) {
335 console.error('Data refresh error:', error);
336 setNotice({
337 status: 'error',
338 message: __('Failed to refresh analytics data. Please try again.', 'thinkrank')
339 });
340 } finally {
341 setIsRefreshing(false);
342 }
343 };
344
345 /**
346 * Get optimization button configuration based on active section
347 * Following Site Identity getOptimizationButtonConfig pattern
348 */
349 const getOptimizationButtonConfig = () => {
350 switch (activeSubSection) {
351 case 'setup':
352 return null; // No button for setup section
353 case 'dashboard':
354 case 'opportunities':
355 case 'insights':
356 return {
357 type: 'refresh',
358 label: activeSubSection === 'insights' ? __('Regenerate Insights', 'thinkrank') : __('Refresh Data', 'thinkrank'),
359 loadingLabel: activeSubSection === 'insights' ? __('Regenerating...', 'thinkrank') : __('Refreshing...', 'thinkrank'),
360 onClick: refreshData,
361 isBusy: isRefreshing || isLoadingIntelligence,
362 disabled: !settings.seo_analytics_enabled || isRefreshing || isLoadingIntelligence
363 };
364 default:
365 return null;
366 }
367 };
368
369 /**
370 * Render loading state
371 * Following Site Identity loading pattern
372 */
373 if (isLoading) {
374 return (
375 <div className="thinkrank-flex thinkrank-justify-center thinkrank-items-center thinkrank-p-8">
376 <Spinner />
377 </div>
378 );
379 }
380
381 /**
382 * Date range options for dashboard
383 */
384 const dateRangeOptions = [
385 { label: __('Last 7 days', 'thinkrank'), value: '7d' },
386 { label: __('Last 30 days', 'thinkrank'), value: '30d' },
387 { label: __('Last 90 days', 'thinkrank'), value: '90d' }
388 ];
389
390 /**
391 * Render SEO Health Score component
392 * Following ThinkRank metric card patterns
393 */
394 const renderSEOHealthScore = () => {
395 if (!intelligentDashboardData?.intelligence?.seo_health_score) {
396 return null;
397 }
398
399 const healthScore = intelligentDashboardData.intelligence.seo_health_score;
400 const scoreColor = healthScore.overall_score >= 80 ? 'success' :
401 healthScore.overall_score >= 60 ? 'warning' : 'error';
402
403 return (
404 <Card size="small" className="thinkrank-seo-health-card thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
405 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
406 <Flex justify="space-between" align="center">
407 <FlexItem>
408 <h3>{__('SEO Health Score', 'thinkrank')}</h3>
409 </FlexItem>
410 <FlexItem>
411 <div className={`thinkrank-health-score thinkrank-score-${scoreColor} thinkrank-flex thinkrank-items-center thinkrank-gap-2`}>
412 <div className={`thinkrank-flex thinkrank-items-center thinkrank-justify-center thinkrank-rounded-full thinkrank-text-white thinkrank-relative thinkrank-font-bold thinkrank-text-xl thinkrank-shadow-md ${scoreColor === 'success' ? 'thinkrank-bg-green' : scoreColor === 'warning' ? 'thinkrank-bg-orange' : 'thinkrank-bg-red'}`} style={{
413 width: '60px',
414 height: '60px'
415 }}>
416 {Math.round(healthScore.overall_score)}
417 <span className={`thinkrank-score-grade thinkrank-absolute thinkrank-flex thinkrank-items-center thinkrank-justify-center thinkrank-rounded-full thinkrank-text-white thinkrank-border-2 thinkrank-border-white thinkrank-font-bold thinkrank-text-sm thinkrank-shadow-sm ${scoreColor === 'success' ? 'thinkrank-bg-green' : scoreColor === 'warning' ? 'thinkrank-bg-orange' : 'thinkrank-bg-red'}`} style={{
418 top: '-8px',
419 right: '-8px',
420 padding: '2px 6px',
421 minWidth: '20px',
422 height: '20px'
423 }}>
424 {healthScore.grade}
425 </span>
426 </div>
427 </div>
428 </FlexItem>
429 </Flex>
430 </CardHeader>
431 <CardBody className="thinkrank-p-lg">
432 <div className="thinkrank-space-y-3">
433 <Text variant="muted">{healthScore.interpretation}</Text>
434
435 {/* Component Scores */}
436 <div className="thinkrank-grid thinkrank-grid-cols-2 thinkrank-gap-3">
437 {Object.entries(healthScore.component_scores).map(([component, score]) => (
438 <div key={component} className="thinkrank-component-score thinkrank-p-3 thinkrank-bg-gray-100 thinkrank-border thinkrank-border-light thinkrank-rounded">
439 <div className="thinkrank-component-name thinkrank-text-xs thinkrank-text-muted thinkrank-mb-1">
440 {component.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}
441 </div>
442 <div className="thinkrank-component-value thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary">
443 {Math.round(score)}/25
444 </div>
445 </div>
446 ))}
447 </div>
448
449 {/* Recommendations */}
450 {healthScore.recommendations && healthScore.recommendations.length > 0 && (
451 <div className="thinkrank-recommendations thinkrank-mt-lg">
452 <Text weight="600" className="thinkrank-mb-3 thinkrank-block thinkrank-text-secondary">
453 {__('Recommendations:', 'thinkrank')}
454 </Text>
455 <div className="thinkrank-recommendation-list thinkrank-flex thinkrank-flex-col thinkrank-gap-2">
456 {healthScore.recommendations.slice(0, 3).map((rec, index) => (
457 <div key={index} className="thinkrank-flex thinkrank-items-start thinkrank-gap-2 thinkrank-p-3 thinkrank-bg-gray-100 thinkrank-border thinkrank-border-light thinkrank-rounded thinkrank-border-l-4 thinkrank-border-blue">
458 <div className="thinkrank-w-2 thinkrank-h-2 thinkrank-rounded-full thinkrank-bg-blue thinkrank-mt-2 thinkrank-flex-shrink-0"></div>
459 <Text size="small" className="thinkrank-text-tertiary thinkrank-leading-relaxed">
460 {typeof rec === 'object' ? rec.recommendation : rec}
461 </Text>
462 </div>
463 ))}
464 </div>
465 </div>
466 )}
467 </div>
468 </CardBody>
469 </Card>
470 );
471 };
472
473 /**
474 * Render Traffic Trends component with intelligence
475 * Following ThinkRank trend display patterns
476 */
477 const renderTrafficTrends = () => {
478 if (!intelligentDashboardData?.intelligence?.trends?.traffic_trends) {
479 return null;
480 }
481
482 const trends = intelligentDashboardData.intelligence.trends.traffic_trends;
483
484 return (
485 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
486 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
487 <h3>{__('Traffic Trends & Intelligence', 'thinkrank')}</h3>
488 </CardHeader>
489 <CardBody className="thinkrank-p-lg">
490 <div className="thinkrank-space-y-3">
491 {/* Sessions Trend */}
492 {trends.sessions && (
493 <div className="thinkrank-trend-item">
494 <Flex justify="space-between" align="center">
495 <FlexItem>
496 <Text weight="600">{trends.sessions.metric_name}</Text>
497 <Text variant="muted" size="small">{trends.sessions.interpretation}</Text>
498 </FlexItem>
499 <FlexItem>
500 <div className={`thinkrank-trend-indicator thinkrank-trend-${trends.sessions.trend_direction}`}>
501 <span className="thinkrank-trend-value">
502 {trends.sessions.percentage_change > 0 ? '+' : ''}{trends.sessions.percentage_change.toFixed(1)}%
503 </span>
504 <span className={`thinkrank-trend-significance thinkrank-significance-${trends.sessions.significance}`}>
505 {trends.sessions.significance}
506 </span>
507 </div>
508 </FlexItem>
509 </Flex>
510 </div>
511 )}
512
513 {/* Organic Traffic Trend */}
514 {trends.organic_traffic && (
515 <div className="thinkrank-trend-item">
516 <Flex justify="space-between" align="center">
517 <FlexItem>
518 <Text weight="600">{__('Organic Traffic', 'thinkrank')}</Text>
519 <Text variant="muted" size="small">{trends.organic_traffic.interpretation}</Text>
520 </FlexItem>
521 <FlexItem>
522 <div className={`thinkrank-trend-indicator thinkrank-trend-${trends.organic_traffic.trend_direction}`}>
523 <span className="thinkrank-trend-value">
524 {trends.organic_traffic.percentage_change > 0 ? '+' : ''}{trends.organic_traffic.percentage_change.toFixed(1)}%
525 </span>
526 </div>
527 </FlexItem>
528 </Flex>
529 </div>
530 )}
531
532 {/* Summary */}
533 {trends.summary && (
534 <div className="thinkrank-trend-summary">
535 <Text variant="muted" size="small">{trends.summary}</Text>
536 </div>
537 )}
538 </div>
539 </CardBody>
540 </Card>
541 );
542 };
543
544 /**
545 * Render SEO Insights component
546 * Following ThinkRank insight display patterns
547 */
548 const renderSEOInsights = () => {
549 if (!seoInsights?.insights || seoInsights.insights.length === 0) {
550 return null;
551 }
552
553 const insights = seoInsights.insights.slice(0, 5); // Show top 5 insights
554
555 return (
556 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
557 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
558 <Flex justify="space-between" align="center">
559 <FlexItem>
560 <h3>{__('SEO Insights', 'thinkrank')}</h3>
561 </FlexItem>
562 <FlexItem>
563 <Text variant="muted" size="small">
564 {seoInsights.summary.high_impact_count} {__('high impact', 'thinkrank')}
565 {seoInsights.summary.action_required_count} {__('need action', 'thinkrank')}
566 </Text>
567 </FlexItem>
568 </Flex>
569 </CardHeader>
570 <CardBody className="thinkrank-p-lg">
571 <div className="thinkrank-space-y-3">
572 {insights.map((insight, index) => (
573 <div key={insight.id || index} className={`thinkrank-insight-item thinkrank-impact-${insight.impact} thinkrank-p-6 thinkrank-bg-white thinkrank-border-2 thinkrank-border-light thinkrank-rounded-lg thinkrank-mb-lg thinkrank-border-l-4 thinkrank-shadow-sm ${insight.impact === 'high' ? 'thinkrank-border-red' : insight.impact === 'medium' ? 'thinkrank-border-orange' : 'thinkrank-border-muted'}`}>
574 <div className="thinkrank-insight-header thinkrank-mb-3">
575 <Flex justify="space-between" align="center">
576 <FlexItem>
577 <Text weight="600" className="thinkrank-text-primary thinkrank-text-lg">{insight.title}</Text>
578 </FlexItem>
579 <FlexItem>
580 <div className="thinkrank-insight-meta thinkrank-flex thinkrank-gap-2">
581 <span className={`thinkrank-impact-badge thinkrank-impact-${insight.impact} thinkrank-px-3 thinkrank-py-1 thinkrank-rounded-full thinkrank-text-xs thinkrank-font-semibold thinkrank-border ${insight.impact === 'high' ? 'thinkrank-bg-red thinkrank-text-white thinkrank-border-red' : insight.impact === 'medium' ? 'thinkrank-bg-orange thinkrank-text-white thinkrank-border-orange' : 'thinkrank-bg-gray-100 thinkrank-text-muted thinkrank-border-light'}`}>
582 {insight.impact.toUpperCase()}
583 </span>
584 {insight.action_required && (
585 <span className="thinkrank-action-required thinkrank-px-3 thinkrank-py-1 thinkrank-rounded-full thinkrank-text-xs thinkrank-font-semibold thinkrank-bg-red thinkrank-text-white thinkrank-border thinkrank-border-red">
586 {__('Action Required', 'thinkrank')}
587 </span>
588 )}
589 </div>
590 </FlexItem>
591 </Flex>
592 </div>
593 <div className="thinkrank-insight-content">
594 <Text variant="muted" size="small" className="thinkrank-mb-3 thinkrank-text-tertiary thinkrank-leading-relaxed">{insight.description}</Text>
595 {insight.recommended_action && (
596 <div className="thinkrank-insight-action thinkrank-p-3 thinkrank-bg-gray-100 thinkrank-rounded thinkrank-border thinkrank-border-light thinkrank-border-l-4 thinkrank-border-blue">
597 <Text size="small" className="thinkrank-text-secondary thinkrank-leading-relaxed">
598 <strong className="thinkrank-text-primary">{__('Recommended:', 'thinkrank')}</strong> {insight.recommended_action}
599 </Text>
600 </div>
601 )}
602 </div>
603 </div>
604 ))}
605 </div>
606 </CardBody>
607 </Card>
608 );
609 };
610
611 /**
612 * Render setup section content
613 */
614 const renderSetupSection = () => (
615 <div className="thinkrank-space-y-6">
616 <Text variant="muted">
617 {__('Configure Google API connections for SEO analytics data collection.', 'thinkrank')}
618 </Text>
619
620 {/* Setup Instructions */}
621 <Card size="small" className="thinkrank-mt-md thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
622 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
623 <h3>{__('Setup Instructions', 'thinkrank')}</h3>
624 </CardHeader>
625 <CardBody className="thinkrank-p-lg">
626 <div className="thinkrank-space-y-md">
627 <div>
628 <h4 className="thinkrank-mb-1 thinkrank-text-primary">{__('1. Configure API Keys', 'thinkrank')}</h4>
629 <Text variant="muted">
630 {__('Add your Google Analytics, Search Console, and PageSpeed API keys in the ', 'thinkrank')}
631 <Button
632 variant="link"
633 onClick={() => {
634 // Navigate to Integrations section
635 if (onNavigate) {
636 onNavigate('integrations', 'google-services');
637 }
638 }}
639 className="thinkrank-p-0 thinkrank-underline thinkrank-h-auto">
640 >
641 {__('Integrations tab', 'thinkrank')}
642 </Button>
643 {__('.', 'thinkrank')}
644 </Text>
645 </div>
646 <div>
647 <h4 className="thinkrank-mb-1 thinkrank-text-primary">{__('2. Test Connections', 'thinkrank')}</h4>
648 <Text variant="muted">
649 {__('Use the "Test Connections" button to verify your API configuration.', 'thinkrank')}
650 </Text>
651 </div>
652 <div>
653 <h4 className="thinkrank-mb-1 thinkrank-text-primary">{__('3. Enable Analytics', 'thinkrank')}</h4>
654 <Text variant="muted">
655 {__('Toggle the "Enable SEO Analytics" switch above to start collecting data.', 'thinkrank')}
656 </Text>
657 </div>
658 </div>
659 </CardBody>
660 </Card>
661 </div>
662 );
663
664 /**
665 * Render dashboard section content with intelligence enhancements
666 */
667 const renderDashboardSection = () => (
668 <div className="thinkrank-space-y-6">
669 <Flex justify="space-between" align="center">
670 <FlexItem>
671 <Text variant="muted">
672 {settings.seo_analytics_enable_ai_insights
673 ? __('SEO analytics dashboard with AI-powered insights, trends, and performance intelligence.', 'thinkrank')
674 : __('SEO analytics dashboard with traffic and search performance data.', 'thinkrank')
675 }
676 </Text>
677 </FlexItem>
678 <FlexItem>
679 <SelectControl
680 label={__('Date Range', 'thinkrank')}
681 value={dateRange}
682 options={dateRangeOptions}
683 onChange={setDateRange}
684 __next40pxDefaultSize={true}
685 __nextHasNoMarginBottom={true}
686 />
687 </FlexItem>
688 </Flex>
689
690 {/* Intelligence Loading State */}
691 {isLoadingIntelligence && settings.seo_analytics_enable_ai_insights && (
692 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
693 <CardBody className="thinkrank-p-lg">
694 <div className="thinkrank-flex thinkrank-justify-center thinkrank-items-center thinkrank-p-md">
695 <Spinner />
696 <Text variant="muted" className="thinkrank-ml-sm">
697 {__('Loading AI insights...', 'thinkrank')}
698 </Text>
699 </div>
700 </CardBody>
701 </Card>
702 )}
703
704 {/* SEO Health Score - Intelligence Feature */}
705 {settings.seo_analytics_enable_ai_insights && renderSEOHealthScore()}
706
707 {/* Dashboard Data */}
708 {dashboardData && hasValidDashboardData(dashboardData) && (
709 <div className="thinkrank-space-y-6 thinkrank-mt-6">
710 {/* Traffic Trends with Intelligence */}
711 {settings.seo_analytics_enable_ai_insights && renderTrafficTrends()}
712
713 {/* Traditional Traffic Overview (Enhanced) */}
714 {dashboardData.traffic && Object.keys(dashboardData.traffic).length > 0 && (
715 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
716 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
717 <h3>{__('Traffic Overview', 'thinkrank')}</h3>
718 </CardHeader>
719 <CardBody className="thinkrank-p-lg">
720 <div className="thinkrank-grid thinkrank-grid-cols-2 thinkrank-gap-3">
721 <div className="thinkrank-metric-card thinkrank-p-lg thinkrank-bg-gray-100 thinkrank-border thinkrank-border-light thinkrank-rounded thinkrank-text-center">
722 <div className="thinkrank-metric-value thinkrank-text-2xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">{dashboardData.traffic.sessions?.toLocaleString() || 0}</div>
723 <div className="thinkrank-metric-label thinkrank-text-xs thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide">{__('Sessions', 'thinkrank')}</div>
724 {/* Add trend indicator if intelligence data available */}
725 {intelligentDashboardData?.intelligence?.trends?.traffic_trends?.sessions && (
726 <div className={`thinkrank-metric-trend thinkrank-trend-${intelligentDashboardData.intelligence.trends.traffic_trends.sessions.trend_direction}`}>
727 {intelligentDashboardData.intelligence.trends.traffic_trends.sessions.percentage_change > 0 ? '+' : ''}
728 {intelligentDashboardData.intelligence.trends.traffic_trends.sessions.percentage_change.toFixed(1)}%
729 </div>
730 )}
731 </div>
732 <div className="thinkrank-metric-card thinkrank-p-lg thinkrank-bg-gray-100 thinkrank-border thinkrank-border-light thinkrank-rounded thinkrank-text-center">
733 <div className="thinkrank-metric-value thinkrank-text-2xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">{dashboardData.traffic.pageviews?.toLocaleString() || 0}</div>
734 <div className="thinkrank-metric-label thinkrank-text-xs thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide">{__('Pageviews', 'thinkrank')}</div>
735 {/* Add trend indicator if intelligence data available */}
736 {intelligentDashboardData?.intelligence?.trends?.traffic_trends?.pageviews && (
737 <div className={`thinkrank-metric-trend thinkrank-trend-${intelligentDashboardData.intelligence.trends.traffic_trends.pageviews.trend_direction}`}>
738 {intelligentDashboardData.intelligence.trends.traffic_trends.pageviews.percentage_change > 0 ? '+' : ''}
739 {intelligentDashboardData.intelligence.trends.traffic_trends.pageviews.percentage_change.toFixed(1)}%
740 </div>
741 )}
742 </div>
743 <div className="thinkrank-metric-card">
744 <div className="thinkrank-metric-value">{dashboardData.traffic.active_users?.toLocaleString() || 0}</div>
745 <div className="thinkrank-metric-label">{__('Active Users', 'thinkrank')}</div>
746 </div>
747 <div className="thinkrank-metric-card">
748 <div className="thinkrank-metric-value">{(dashboardData.traffic.bounce_rate * 100)?.toFixed(1) || 0}%</div>
749 <div className="thinkrank-metric-label">{__('Bounce Rate', 'thinkrank')}</div>
750 {/* Add trend indicator if intelligence data available */}
751 {intelligentDashboardData?.intelligence?.trends?.traffic_trends?.bounce_rate && (
752 <div className={`thinkrank-metric-trend thinkrank-trend-${intelligentDashboardData.intelligence.trends.traffic_trends.bounce_rate.trend_direction}`}>
753 {intelligentDashboardData.intelligence.trends.traffic_trends.bounce_rate.percentage_change > 0 ? '+' : ''}
754 {intelligentDashboardData.intelligence.trends.traffic_trends.bounce_rate.percentage_change.toFixed(1)}%
755 </div>
756 )}
757 </div>
758 </div>
759 </CardBody>
760 </Card>
761 )}
762
763 {/* SEO Insights - Intelligence Feature */}
764 {settings.seo_analytics_enable_ai_insights && renderSEOInsights()}
765
766 {/* Search Performance (Enhanced with Intelligence) */}
767 {dashboardData.search_performance && dashboardData.search_performance.rows && dashboardData.search_performance.rows.length > 0 && (
768 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
769 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
770 <Flex justify="space-between" align="center">
771 <FlexItem>
772 <h3>{__('Search Performance', 'thinkrank')}</h3>
773 </FlexItem>
774 {/* Add keyword trends summary if intelligence available */}
775 {intelligentDashboardData?.intelligence?.trends?.keyword_trends && (
776 <FlexItem>
777 <Text variant="muted" size="small">
778 {intelligentDashboardData.intelligence.trends.keyword_trends.summary}
779 </Text>
780 </FlexItem>
781 )}
782 </Flex>
783 </CardHeader>
784 <CardBody className="thinkrank-p-lg">
785 <div className="thinkrank-space-y-3">
786 {dashboardData.search_performance.rows.slice(0, 5).map((row, index) => {
787 // Check if this keyword is in gaining/losing keywords from intelligence
788 const keyword = row.keys?.[0] || '';
789 const isGaining = intelligentDashboardData?.intelligence?.trends?.keyword_trends?.top_gaining_keywords?.some(
790 k => k.keyword === keyword
791 );
792 const isLosing = intelligentDashboardData?.intelligence?.trends?.keyword_trends?.top_losing_keywords?.some(
793 k => k.keyword === keyword
794 );
795
796 return (
797 <Flex key={index} justify="space-between" align="center">
798 <FlexItem>
799 <div className="thinkrank-keyword-item">
800 <Text>{keyword || __('Unknown Query', 'thinkrank')}</Text>
801 {/* Add intelligence indicators */}
802 {settings.seo_analytics_enable_ai_insights && (
803 <div className="thinkrank-keyword-indicators">
804 {isGaining && (
805 <span className="thinkrank-keyword-badge thinkrank-badge-success">
806 {__('Gaining', 'thinkrank')}
807 </span>
808 )}
809 {isLosing && (
810 <span className="thinkrank-keyword-badge thinkrank-badge-warning">
811 {__('Needs Attention', 'thinkrank')}
812 </span>
813 )}
814 </div>
815 )}
816 </div>
817 </FlexItem>
818 <FlexItem>
819 <div className="thinkrank-flex thinkrank-gap-3">
820 <span className="thinkrank-metric-small">{row.clicks || 0} {__('clicks', 'thinkrank')}</span>
821 <span className="thinkrank-metric-small">{row.impressions || 0} {__('impressions', 'thinkrank')}</span>
822 <span className="thinkrank-metric-small">{((row.ctr || 0) * 100).toFixed(1)}% {__('CTR', 'thinkrank')}</span>
823 <span className="thinkrank-metric-small">{(row.position || 0).toFixed(1)} {__('pos', 'thinkrank')}</span>
824 </div>
825 </FlexItem>
826 </Flex>
827 );
828 })}
829 </div>
830 </CardBody>
831 </Card>
832 )}
833
834 </div>
835 )}
836
837 {(!dashboardData || !hasValidDashboardData(dashboardData)) && !isRefreshing && (
838 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
839 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
840 <Flex align="center" gap={2}>
841 <div className="thinkrank-text-xl"></div>
842 <Heading level={4}>{__('Data Collection in Progress', 'thinkrank')}</Heading>
843 </Flex>
844 </CardHeader>
845 <CardBody className="thinkrank-p-lg">
846 <div className="thinkrank-mb-lg">
847 <Text className="thinkrank-mb-3 thinkrank-block">
848 {__('Your Google APIs are configured. Data collection timelines:', 'thinkrank')}
849 </Text>
850
851 <div className="thinkrank-pl-lg thinkrank-mb-lg">
852 <div className="thinkrank-mb-2">
853 <Text>📈 <strong>{__('Analytics Data:', 'thinkrank')}</strong> {__('12-24 hours', 'thinkrank')}</Text>
854 </div>
855 <div className="thinkrank-mb-2">
856 <Text>🔍 <strong>{__('Search Console:', 'thinkrank')}</strong> {__('2-3 days', 'thinkrank')}</Text>
857 </div>
858 <div>
859 <Text>🤖 <strong>{__('AI Insights:', 'thinkrank')}</strong> {__('1-2 weeks', 'thinkrank')}</Text>
860 </div>
861 </div>
862
863 <Text variant="muted" className="thinkrank-text-sm">
864 {__('Use the refresh button above to check for new data.', 'thinkrank')}
865 </Text>
866 </div>
867 </CardBody>
868 </Card>
869 )}
870
871 {isRefreshing && (
872 <div className="thinkrank-flex thinkrank-justify-center thinkrank-p-lg">
873 <Spinner />
874 </div>
875 )}
876 </div>
877 );
878
879 /**
880 * Render Quick Wins component
881 * Following ThinkRank opportunity display patterns
882 */
883 const renderQuickWins = () => {
884 // Check if we have quick wins data
885 const hasQuickWins = intelligentOpportunities?.intelligent_opportunities?.quick_wins?.opportunities?.length > 0;
886 const quickWins = hasQuickWins ? intelligentOpportunities.intelligent_opportunities.quick_wins.opportunities.slice(0, 5) : [];
887
888 return (
889 <Card size="small" className="thinkrank-mb-6 thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
890 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100 thinkrank-p-6">
891 <Flex justify="space-between" align="center">
892 <FlexItem>
893 <h3 className="thinkrank-m-0 thinkrank-mb-2 thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary">
894 {__('Quick Wins', 'thinkrank')}
895 </h3>
896 <Text variant="muted" size="small" className="thinkrank-text-muted thinkrank-leading-relaxed">
897 {__('High-impact, low-effort opportunities for immediate results', 'thinkrank')}
898 </Text>
899 </FlexItem>
900 {hasQuickWins && (
901 <FlexItem>
902 <div className="thinkrank-quick-wins-summary">
903 <Text size="small" className="thinkrank-text-green thinkrank-font-medium">
904 +{intelligentOpportunities.intelligent_opportunities.quick_wins.potential_additional_clicks || 0} {__('potential clicks', 'thinkrank')}
905 </Text>
906 </div>
907 </FlexItem>
908 )}
909 </Flex>
910 </CardHeader>
911 <CardBody className={hasQuickWins ? 'thinkrank-p-6' : 'thinkrank-py-10 thinkrank-px-6'}>
912 {hasQuickWins ? (
913 <div className="thinkrank-space-y-3">
914 {quickWins.map((opportunity, index) => (
915 <div key={index} className="thinkrank-quick-win-item thinkrank-p-lg thinkrank-bg-gray-100 thinkrank-border thinkrank-border-light thinkrank-rounded-lg thinkrank-border-l-4 thinkrank-border-green thinkrank-mb-3">
916 <Flex justify="space-between" align="center">
917 <FlexItem>
918 <div className="thinkrank-opportunity-content">
919 <Text weight="600" className="thinkrank-text-primary thinkrank-mb-1 thinkrank-block">
920 {opportunity.keyword}
921 </Text>
922 <Text variant="muted" size="small" className="thinkrank-text-muted">
923 {opportunity.recommended_action}
924 </Text>
925 </div>
926 </FlexItem>
927 <FlexItem>
928 <div className="thinkrank-opportunity-metrics thinkrank-text-right">
929 <div className="thinkrank-metric-row thinkrank-mb-1 thinkrank-flex thinkrank-items-center thinkrank-gap-1 thinkrank-justify-end">
930 <span className="thinkrank-metric-label thinkrank-text-xs thinkrank-text-muted">{__('Position:', 'thinkrank')}</span>
931 <span className="thinkrank-metric-value thinkrank-text-xs thinkrank-font-semibold thinkrank-text-red">{opportunity.current_position}</span>
932 <span className="thinkrank-metric-arrow thinkrank-text-muted"></span>
933 <span className="thinkrank-metric-target thinkrank-text-xs thinkrank-font-semibold thinkrank-text-green">{opportunity.potential_position}</span>
934 </div>
935 <div className="thinkrank-metric-row thinkrank-flex thinkrank-items-center thinkrank-gap-1 thinkrank-justify-end">
936 <span className="thinkrank-metric-label thinkrank-text-xs thinkrank-text-muted">{__('Potential:', 'thinkrank')}</span>
937 <span className="thinkrank-metric-value thinkrank-text-xs thinkrank-font-semibold thinkrank-text-green">+{opportunity.potential_clicks} {__('clicks', 'thinkrank')}</span>
938 </div>
939 <div className={`thinkrank-impact-score thinkrank-score-${opportunity.impact_score >= 70 ? 'high' : 'medium'}`}>
940 {Math.round(opportunity.impact_score)}
941 </div>
942 </div>
943 </FlexItem>
944 </Flex>
945 </div>
946 ))}
947 </div>
948 ) : (
949 <div className="thinkrank-text-center thinkrank-p-8">
950 <div className="thinkrank-text-5xl thinkrank-mb-lg thinkrank-opacity-60">📊</div>
951 <Text weight="600" className="thinkrank-text-secondary thinkrank-mb-2 thinkrank-block">
952 {__('No Quick Wins Available', 'thinkrank')}
953 </Text>
954 <Text size="small" className="thinkrank-text-muted thinkrank-leading-relaxed">
955 {intelligentOpportunities?.intelligent_opportunities?.quick_wins?.summary || __('No opportunities detected yet. Check back after more search performance data is collected.', 'thinkrank')}
956 </Text>
957 </div>
958 )}
959 </CardBody>
960 </Card>
961 );
962 };
963
964 /**
965 * Get display name for matrix quadrant
966 */
967 const getQuadrantDisplayName = (quadrantKey) => {
968 const quadrantNames = {
969 'high_impact_low_effort': __('Quick Wins', 'thinkrank'),
970 'high_impact_high_effort': __('Major Projects', 'thinkrank'),
971 'low_impact_low_effort': __('Fill-ins', 'thinkrank'),
972 'low_impact_high_effort': __('Avoid', 'thinkrank')
973 };
974 return quadrantNames[quadrantKey] || quadrantKey?.replace('_', ' ');
975 };
976
977 /**
978 * Render Impact/Effort Matrix component
979 * Following ThinkRank matrix display patterns
980 */
981 const renderImpactEffortMatrix = () => {
982 if (!intelligentOpportunities?.impact_effort_matrix?.matrix) {
983 return (
984 <Card size="small" className="thinkrank-mb-6 thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
985 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100 thinkrank-p-6">
986 <h3 className="thinkrank-m-0 thinkrank-mb-2 thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary">
987 {__('Opportunity Priority Matrix', 'thinkrank')}
988 </h3>
989 <Text variant="muted" size="small" className="thinkrank-text-muted thinkrank-leading-relaxed">
990 {__('Automatically prioritize SEO opportunities by impact and effort required', 'thinkrank')}
991 </Text>
992 </CardHeader>
993 <CardBody className="thinkrank-py-10 thinkrank-px-6 thinkrank-text-center">
994 <div className="thinkrank-max-w-sm thinkrank-mx-auto">
995 <div className="thinkrank-text-6xl thinkrank-mb-6 thinkrank-opacity-60">📊</div>
996 <Text weight="600" className="thinkrank-mb-3 thinkrank-block thinkrank-text-secondary thinkrank-text-lg">
997 {__('No Opportunities Available', 'thinkrank')}
998 </Text>
999 <Text variant="muted" className="thinkrank-text-muted thinkrank-leading-relaxed thinkrank-block">
1000 {__('No opportunities detected yet. Check back after more search performance data is collected.', 'thinkrank')}
1001 </Text>
1002 </div>
1003 </CardBody>
1004 </Card>
1005 );
1006 }
1007
1008 const matrix = intelligentOpportunities.impact_effort_matrix.matrix;
1009 const recommendations = intelligentOpportunities.impact_effort_matrix.recommendations;
1010 const summary = intelligentOpportunities.impact_effort_matrix.summary;
1011
1012 // Check if we have any meaningful data to show
1013 const hasQuickWins = Array.isArray(matrix.high_impact_low_effort) && matrix.high_impact_low_effort.length > 0;
1014 const hasMajorProjects = Array.isArray(matrix.high_impact_high_effort) && matrix.high_impact_high_effort.length > 0;
1015 const hasFillIns = Array.isArray(matrix.low_impact_low_effort) && matrix.low_impact_low_effort.length > 0;
1016 const hasAvoid = Array.isArray(matrix.low_impact_high_effort) && matrix.low_impact_high_effort.length > 0;
1017
1018 // If no meaningful data, show a helpful message for connected but empty state
1019 if (!hasQuickWins && !hasMajorProjects && !hasFillIns && !hasAvoid) {
1020 return (
1021 <Card size="small" className="thinkrank-mb-6 thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1022 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100 thinkrank-p-6">
1023 <h3 className="thinkrank-m-0 thinkrank-mb-2 thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary">
1024 {__('Opportunity Priority Matrix', 'thinkrank')}
1025 </h3>
1026 <Text variant="muted" size="small" className="thinkrank-text-muted thinkrank-leading-relaxed">
1027 {summary || __('SEO opportunities organized by impact and effort required', 'thinkrank')}
1028 </Text>
1029 </CardHeader>
1030 <CardBody className="thinkrank-py-10 thinkrank-px-6 thinkrank-text-center">
1031 <div className="thinkrank-max-w-sm thinkrank-mx-auto">
1032 <div className="thinkrank-text-6xl thinkrank-mb-6 thinkrank-opacity-60">📊</div>
1033 <Text weight="600" className="thinkrank-mb-3 thinkrank-block thinkrank-text-secondary thinkrank-text-lg">
1034 {__('No Opportunities Available', 'thinkrank')}
1035 </Text>
1036 <Text variant="muted" className="thinkrank-text-muted thinkrank-leading-relaxed thinkrank-block">
1037 {summary || __('No opportunities detected yet. Check back after more search performance data is collected.', 'thinkrank')}
1038 </Text>
1039 </div>
1040 </CardBody>
1041 </Card>
1042 );
1043 }
1044
1045 return (
1046 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1047 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
1048 <h3>{__('Opportunity Priority Matrix', 'thinkrank')}</h3>
1049 <Text variant="muted" size="small">
1050 {summary || __('SEO opportunities organized by impact and effort required', 'thinkrank')}
1051 </Text>
1052 </CardHeader>
1053 <CardBody className="thinkrank-p-lg">
1054 {/* Show only quadrants with actual content */}
1055 <div className="thinkrank-matrix-grid thinkrank-grid thinkrank-gap-lg thinkrank-mb-lg" style={{
1056 gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))'
1057 }}>
1058 {/* Quick Wins - Highest Priority */}
1059 {hasQuickWins && (
1060 <div className="thinkrank-matrix-quadrant thinkrank-quadrant-quick-wins thinkrank-p-lg thinkrank-bg-green thinkrank-bg-opacity-10 thinkrank-border-2 thinkrank-border-green thinkrank-rounded-lg">
1061 <div className="thinkrank-quadrant-header thinkrank-mb-3 thinkrank-text-center">
1062 <Text weight="600" className="thinkrank-quadrant-title thinkrank-text-green thinkrank-block thinkrank-mb-1">
1063 {__('🎯 Quick Wins', 'thinkrank')}
1064 </Text>
1065 <Text size="small" className="thinkrank-quadrant-subtitle thinkrank-text-green thinkrank-mb-2 thinkrank-block">
1066 {__('High Impact, Low Effort', 'thinkrank')}
1067 </Text>
1068 <div className="thinkrank-quadrant-count thinkrank-bg-green thinkrank-text-white thinkrank-rounded-full thinkrank-w-6 thinkrank-h-6 thinkrank-flex thinkrank-items-center thinkrank-justify-center thinkrank-text-xs thinkrank-font-bold thinkrank-mx-auto">
1069 {matrix.high_impact_low_effort.length}
1070 </div>
1071 </div>
1072 {matrix.high_impact_low_effort.slice(0, 5).map((item, index) => (
1073 <div key={index} className="thinkrank-matrix-item thinkrank-py-2 thinkrank-px-3 thinkrank-bg-white thinkrank-border thinkrank-border-green thinkrank-border-opacity-20 thinkrank-rounded thinkrank-mb-2 thinkrank-text-sm">
1074 <Text size="small" className="thinkrank-text-secondary thinkrank-font-medium">
1075 {item.keyword || item.query || item.page_path || item.action || item.title || 'SEO Opportunity'}
1076 </Text>
1077 {item.current_position && (
1078 <Text size="small" className="thinkrank-text-green thinkrank-block thinkrank-mt-1">
1079 Position: {item.current_position} | CTR: {item.current_ctr || item.ctr}%
1080 </Text>
1081 )}
1082 {item.potential_impact && (
1083 <Text size="small" className="thinkrank-text-green thinkrank-block thinkrank-mt-1">
1084 +{item.potential_impact} potential clicks
1085 </Text>
1086 )}
1087 </div>
1088 ))}
1089 </div>
1090 )}
1091
1092 {/* Major Projects - Secondary Priority */}
1093 {hasMajorProjects && (
1094 <div className="thinkrank-matrix-quadrant thinkrank-quadrant-major-projects thinkrank-p-lg thinkrank-bg-orange thinkrank-bg-opacity-10 thinkrank-border-2 thinkrank-border-orange thinkrank-rounded-lg">
1095 <div className="thinkrank-quadrant-header thinkrank-mb-3 thinkrank-text-center">
1096 <Text weight="600" className="thinkrank-quadrant-title thinkrank-text-orange thinkrank-block thinkrank-mb-1">
1097 {__('📋 Major Projects', 'thinkrank')}
1098 </Text>
1099 <Text size="small" className="thinkrank-quadrant-subtitle thinkrank-text-orange thinkrank-mb-2 thinkrank-block">
1100 {__('High Impact, High Effort', 'thinkrank')}
1101 </Text>
1102 <div className="thinkrank-quadrant-count thinkrank-bg-orange thinkrank-text-white thinkrank-rounded-full thinkrank-w-6 thinkrank-h-6 thinkrank-flex thinkrank-items-center thinkrank-justify-center thinkrank-text-xs thinkrank-font-bold thinkrank-mx-auto">
1103 {matrix.high_impact_high_effort.length}
1104 </div>
1105 </div>
1106 {matrix.high_impact_high_effort.slice(0, 5).map((item, index) => (
1107 <div key={index} className="thinkrank-matrix-item thinkrank-py-2 thinkrank-px-3 thinkrank-bg-white thinkrank-border thinkrank-border-orange thinkrank-border-opacity-20 thinkrank-rounded thinkrank-mb-2 thinkrank-text-sm">
1108 <Text size="small" className="thinkrank-text-secondary thinkrank-font-medium">
1109 {item.keyword || item.query || item.page_path || item.action || item.title || 'SEO Opportunity'}
1110 </Text>
1111 {item.current_position && (
1112 <Text size="small" className="thinkrank-text-orange thinkrank-block thinkrank-mt-1">
1113 Position: {item.current_position} | CTR: {item.current_ctr || item.ctr}%
1114 </Text>
1115 )}
1116 {item.potential_impact && (
1117 <Text size="small" className="thinkrank-text-orange thinkrank-block thinkrank-mt-1">
1118 +{item.potential_impact} potential clicks
1119 </Text>
1120 )}
1121 </div>
1122 ))}
1123 </div>
1124 )}
1125
1126 </div>
1127
1128 {/* Show recommendations if available */}
1129 {recommendations && (
1130 <div className="thinkrank-matrix-recommendations thinkrank-mt-md thinkrank-mb-lg thinkrank-p-6 thinkrank-bg-gray-100 thinkrank-border thinkrank-border-light thinkrank-rounded-lg thinkrank-border-l-4 thinkrank-border-blue">
1131 <Text weight="600" className="thinkrank-mb-3 thinkrank-block thinkrank-text-blue thinkrank-text-base">
1132 {__('💡 Recommendations:', 'thinkrank')}
1133 </Text>
1134 <Text size="small" className="thinkrank-text-tertiary thinkrank-leading-relaxed thinkrank-text-sm">
1135 {__('Focus on', 'thinkrank')} <strong className="thinkrank-text-primary">{getQuadrantDisplayName(recommendations.focus_on)}</strong> {__('first, then plan for', 'thinkrank')} <strong className="thinkrank-text-primary">{getQuadrantDisplayName(recommendations.plan_for)}</strong>.
1136 </Text>
1137 </div>
1138 )}
1139
1140 {/* Show helpful message when no priority items */}
1141 {!hasQuickWins && !hasMajorProjects && (
1142 <div className="thinkrank-p-6 thinkrank-text-center thinkrank-bg-gray-100 thinkrank-rounded-lg thinkrank-border thinkrank-border-light">
1143 <div className="thinkrank-text-3xl thinkrank-mb-3">🔍</div>
1144 <Text weight="600" className="thinkrank-mb-2 thinkrank-block thinkrank-text-secondary">
1145 {__('No High-Priority Opportunities Found', 'thinkrank')}
1146 </Text>
1147 <Text variant="muted" size="small" className="thinkrank-text-muted thinkrank-leading-relaxed">
1148 {__('This could mean your site is well-optimized, or more data is needed. Try connecting Google Search Console or check back after more traffic data is collected.', 'thinkrank')}
1149 </Text>
1150 </div>
1151 )}
1152
1153
1154
1155
1156 </CardBody>
1157 </Card>
1158 );
1159 };
1160
1161 /**
1162 * Render opportunities section content with intelligence enhancements
1163 */
1164 const renderOpportunitiesSection = () => (
1165 <div className="thinkrank-space-y-6">
1166 <Flex justify="space-between" align="center">
1167 <FlexItem>
1168 <Text variant="muted">
1169 {settings.seo_analytics_enable_ai_insights
1170 ? __('AI-powered SEO opportunities with intelligent prioritization and impact analysis based on your search performance data.', 'thinkrank')
1171 : __('SEO opportunities and actionable insights based on your search performance data.', 'thinkrank')
1172 }
1173 </Text>
1174 </FlexItem>
1175 <FlexItem>
1176 <SelectControl
1177 label={__('Date Range', 'thinkrank')}
1178 value={dateRange}
1179 options={dateRangeOptions}
1180 onChange={setDateRange}
1181 __next40pxDefaultSize={true}
1182 __nextHasNoMarginBottom={true}
1183 />
1184 </FlexItem>
1185 </Flex>
1186
1187 {/* Intelligence Loading State */}
1188 {isLoadingIntelligence && settings.seo_analytics_enable_ai_insights && (
1189 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1190 <CardBody className="thinkrank-p-lg">
1191 <div className="thinkrank-flex thinkrank-justify-center thinkrank-items-center thinkrank-p-md">
1192 <Spinner />
1193 <Text variant="muted" className="thinkrank-ml-sm">
1194 {__('Analyzing opportunities...', 'thinkrank')}
1195 </Text>
1196 </div>
1197 </CardBody>
1198 </Card>
1199 )}
1200
1201 {/* Quick Wins - Intelligence Feature */}
1202 {settings.seo_analytics_enable_ai_insights && renderQuickWins()}
1203
1204 {/* Impact/Effort Matrix - Intelligence Feature */}
1205 {settings.seo_analytics_enable_ai_insights && renderImpactEffortMatrix()}
1206
1207 {/* Traditional SEO Opportunities Data (Enhanced) */}
1208 {seoOpportunities && (
1209 <div className="thinkrank-space-y-6">
1210 {/* Keyword Opportunities */}
1211 {seoOpportunities.keyword_opportunities && seoOpportunities.keyword_opportunities.opportunities && (
1212 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1213 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
1214 <h3>{__('Keyword Opportunities', 'thinkrank')}</h3>
1215 <Text variant="muted">
1216 {__('Keywords with potential for ranking improvements', 'thinkrank')}
1217 </Text>
1218 </CardHeader>
1219 <CardBody className="thinkrank-p-lg">
1220 <div className="thinkrank-space-y-3">
1221 {seoOpportunities.keyword_opportunities.opportunities.slice(0, 10).map((opportunity, index) => (
1222 <div key={index} className="thinkrank-opportunity-card">
1223 <Flex justify="space-between" align="center">
1224 <FlexItem>
1225 <div>
1226 <Text weight="600">{opportunity.query}</Text>
1227 <div className="thinkrank-opportunity-reasons">
1228 {opportunity.reasons.map((reason, reasonIndex) => (
1229 <span key={reasonIndex} className="thinkrank-reason-tag">
1230 {reason}
1231 </span>
1232 ))}
1233 </div>
1234 </div>
1235 </FlexItem>
1236 <FlexItem>
1237 <div className="thinkrank-opportunity-metrics">
1238 <div className="thinkrank-metric-small">
1239 <span className="thinkrank-metric-label">{__('Position:', 'thinkrank')}</span>
1240 <span className="thinkrank-metric-value">{opportunity.position}</span>
1241 </div>
1242 <div className="thinkrank-metric-small">
1243 <span className="thinkrank-metric-label">{__('Impressions:', 'thinkrank')}</span>
1244 <span className="thinkrank-metric-value">{opportunity.impressions}</span>
1245 </div>
1246 <div className="thinkrank-metric-small">
1247 <span className="thinkrank-metric-label">{__('CTR:', 'thinkrank')}</span>
1248 <span className="thinkrank-metric-value">{opportunity.ctr}%</span>
1249 </div>
1250 <div className={`thinkrank-opportunity-score thinkrank-score-${opportunity.opportunity_score >= 70 ? 'high' : opportunity.opportunity_score >= 40 ? 'medium' : 'low'}`}>
1251 {__('Score:', 'thinkrank')} {opportunity.opportunity_score}
1252 </div>
1253 </div>
1254 </FlexItem>
1255 </Flex>
1256 </div>
1257 ))}
1258 </div>
1259 </CardBody>
1260 </Card>
1261 )}
1262
1263 {/* Device Performance Insights */}
1264 {seoOpportunities.device_insights && seoOpportunities.device_insights.devices && (
1265 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1266 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
1267 <h3>{__('Device Performance', 'thinkrank')}</h3>
1268 <Text variant="muted">
1269 {__('Performance breakdown by device type', 'thinkrank')}
1270 </Text>
1271 </CardHeader>
1272 <CardBody className="thinkrank-p-lg">
1273 <div className="thinkrank-grid thinkrank-grid-cols-3 thinkrank-gap-3">
1274 {Object.entries(seoOpportunities.device_insights.devices).map(([device, data]) => (
1275 <div key={device} className="thinkrank-device-card">
1276 <div className="thinkrank-device-name">{device.charAt(0).toUpperCase() + device.slice(1)}</div>
1277 <div className="thinkrank-device-metrics">
1278 <div className="thinkrank-metric-row">
1279 <span>{__('Clicks:', 'thinkrank')}</span>
1280 <span>{data.clicks}</span>
1281 </div>
1282 <div className="thinkrank-metric-row">
1283 <span>{__('CTR:', 'thinkrank')}</span>
1284 <span>{data.ctr}%</span>
1285 </div>
1286 <div className="thinkrank-metric-row">
1287 <span>{__('Position:', 'thinkrank')}</span>
1288 <span>{data.position}</span>
1289 </div>
1290 </div>
1291 </div>
1292 ))}
1293 </div>
1294 </CardBody>
1295 </Card>
1296 )}
1297
1298 {/* Search Appearance */}
1299 {seoOpportunities.search_appearance && seoOpportunities.search_appearance.appearances && (
1300 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1301 <CardHeader className="thinkrank-border-b thinkrank-border-light thinkrank-bg-gray-100">
1302 <h3>{__('Search Appearance', 'thinkrank')}</h3>
1303 <Text variant="muted">
1304 {__('Rich results and search appearance features', 'thinkrank')}
1305 </Text>
1306 </CardHeader>
1307 <CardBody className="thinkrank-p-lg">
1308 <div className="thinkrank-space-y-3">
1309 {Object.entries(seoOpportunities.search_appearance.appearances).map(([appearance, data]) => (
1310 <Flex key={appearance} justify="space-between" align="center">
1311 <FlexItem>
1312 <Text weight="600">{appearance.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}</Text>
1313 </FlexItem>
1314 <FlexItem>
1315 <div className="thinkrank-flex thinkrank-gap-3">
1316 <span className="thinkrank-metric-small">{data.clicks} {__('clicks', 'thinkrank')}</span>
1317 <span className="thinkrank-metric-small">{data.impressions} {__('impressions', 'thinkrank')}</span>
1318 <span className="thinkrank-metric-small">{data.ctr}% {__('CTR', 'thinkrank')}</span>
1319 </div>
1320 </FlexItem>
1321 </Flex>
1322 ))}
1323 </div>
1324 </CardBody>
1325 </Card>
1326 )}
1327 </div>
1328 )}
1329
1330 {!seoOpportunities && !isRefreshing && (
1331 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1332 <CardBody className="thinkrank-p-lg">
1333 <div className="thinkrank-text-center thinkrank-p-lg">
1334 <Text variant="muted">
1335 {__('No SEO opportunities data available yet. Search Console data collection may take 24-48 hours after API configuration.', 'thinkrank')}
1336 </Text>
1337 </div>
1338 </CardBody>
1339 </Card>
1340 )}
1341
1342 {isRefreshing && (
1343 <div className="thinkrank-flex thinkrank-justify-center thinkrank-p-lg">
1344 <Spinner />
1345 </div>
1346 )}
1347 </div>
1348 );
1349
1350 /**
1351 * Render insights section content
1352 * Following ThinkRank insights display patterns
1353 */
1354 const renderInsightsSection = () => (
1355 <div className="thinkrank-space-y-6">
1356 <Flex justify="space-between" align="center">
1357 <FlexItem>
1358 <Text variant="muted">
1359 {__('AI-powered SEO insights with actionable recommendations based on comprehensive data analysis.', 'thinkrank')}
1360 </Text>
1361 </FlexItem>
1362 <FlexItem>
1363 <SelectControl
1364 label={__('Date Range', 'thinkrank')}
1365 value={dateRange}
1366 options={dateRangeOptions}
1367 onChange={setDateRange}
1368 __next40pxDefaultSize={true}
1369 __nextHasNoMarginBottom={true}
1370 />
1371 </FlexItem>
1372 </Flex>
1373
1374 {/* Intelligence Loading State */}
1375 {isLoadingIntelligence && (
1376 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1377 <CardBody className="thinkrank-p-lg">
1378 <div className="thinkrank-flex thinkrank-justify-center thinkrank-items-center thinkrank-p-md">
1379 <Spinner />
1380 <Text variant="muted" className="thinkrank-ml-sm">
1381 {__('Generating insights...', 'thinkrank')}
1382 </Text>
1383 </div>
1384 </CardBody>
1385 </Card>
1386 )}
1387
1388 {/* SEO Health Score */}
1389 {renderSEOHealthScore()}
1390
1391 {/* SEO Insights */}
1392 {renderSEOInsights()}
1393
1394 {/* No insights available */}
1395 {!seoInsights && !isLoadingIntelligence && (
1396 <Card size="small" className="thinkrank-mb-lg thinkrank-border thinkrank-border-light thinkrank-rounded-lg">
1397 <CardBody className="thinkrank-p-lg">
1398 <div className="thinkrank-text-center thinkrank-p-lg">
1399 <Text variant="muted">
1400 {settings.seo_analytics_enable_ai_insights
1401 ? __('No insights available yet. AI insights require 1-2 weeks of data for meaningful analysis.', 'thinkrank')
1402 : __('AI insights are disabled. Enable AI insights in settings to see intelligent recommendations.', 'thinkrank')
1403 }
1404 </Text>
1405 </div>
1406 </CardBody>
1407 </Card>
1408 )}
1409 </div>
1410 );
1411
1412 /**
1413 * Render content based on active sub-section
1414 * Following Performance tab renderContent pattern
1415 */
1416 const renderSubSectionContent = () => {
1417 if (!settings.seo_analytics_enabled) {
1418 return null;
1419 }
1420
1421 switch (activeSubSection) {
1422 case 'setup':
1423 return renderSetupSection();
1424 case 'dashboard':
1425 return renderDashboardSection();
1426 case 'opportunities':
1427 return renderOpportunitiesSection();
1428 case 'insights':
1429 return renderInsightsSection();
1430 default:
1431 return renderSetupSection();
1432 }
1433 };
1434
1435 return (
1436 <div className="thinkrank-analytics-tab">
1437 {/* Main Settings Card */}
1438 <SettingsCard
1439 title={__('SEO Analytics & Intelligence', 'thinkrank')}
1440 enabled={settings.seo_analytics_enabled}
1441 onToggle={(value) => handleSettingChange('seo_analytics_enabled', value)}
1442 toggleLabel={__('Enable SEO Analytics', 'thinkrank')}
1443 optimizationButtons={[
1444 (() => {
1445 const buttonConfig = getOptimizationButtonConfig();
1446 return buttonConfig ? (
1447 <OptimizationButton
1448 key="action"
1449 type={buttonConfig.type}
1450 label={buttonConfig.label}
1451 loadingLabel={buttonConfig.loadingLabel}
1452 onClick={buttonConfig.onClick}
1453 isBusy={buttonConfig.isBusy}
1454 disabled={buttonConfig.disabled}
1455 />
1456 ) : null;
1457 })()
1458 ].filter(Boolean)}
1459 notice={notice}
1460 onNoticeRemove={() => setNotice(null)}
1461 showDisabledWarning={true}
1462 disabledWarningMessage={__('SEO Analytics features are disabled. Enable to access Google API integration and AI insights.', 'thinkrank')}
1463 >
1464 {/* Render content based on active sub-section */}
1465 <div className="thinkrank-py-lg">
1466 {renderSubSectionContent()}
1467 </div>
1468 </SettingsCard>
1469
1470 {/* Save Button */}
1471 {hasChanges && (
1472 <Flex justify="flex-end" className="thinkrank-mt-lg">
1473 <FlexItem>
1474 <Button
1475 variant="primary"
1476 onClick={saveSettings}
1477 isBusy={isSaving}
1478 disabled={isSaving}
1479 >
1480 {isSaving ? __('Saving...', 'thinkrank') : __('Save Settings', 'thinkrank')}
1481 </Button>
1482 </FlexItem>
1483 </Flex>
1484 )}
1485 </div>
1486 );
1487 };
1488
1489 export default AnalyticsTab;
1490