| 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 |
|