| 1 |
/** |
| 2 |
* Analysis Results Component |
| 3 |
* |
| 4 |
* Displays SEO analysis results including score breakdown and suggestions |
| 5 |
* |
| 6 |
* @package ThinkRank |
| 7 |
* @since 1.0.0 |
| 8 |
*/ |
| 9 |
|
| 10 |
import { __ } from '@wordpress/i18n'; |
| 11 |
|
| 12 |
/** |
| 13 |
* Analysis Results Component |
| 14 |
*/ |
| 15 |
const AnalysisResults = ({ analysisData, showScoreBreakdown }) => { |
| 16 |
if (!analysisData) { |
| 17 |
return null; |
| 18 |
} |
| 19 |
|
| 20 |
/** |
| 21 |
* Get score class based on score value |
| 22 |
*/ |
| 23 |
const getScoreClass = (score) => { |
| 24 |
if (score >= 80) return 'score-high'; |
| 25 |
if (score >= 60) return 'score-medium'; |
| 26 |
return 'score-low'; |
| 27 |
}; |
| 28 |
|
| 29 |
/** |
| 30 |
* Get grade from score |
| 31 |
*/ |
| 32 |
const getGrade = (score) => { |
| 33 |
if (score >= 90) return 'A'; |
| 34 |
if (score >= 80) return 'B'; |
| 35 |
if (score >= 70) return 'C'; |
| 36 |
if (score >= 60) return 'D'; |
| 37 |
return 'F'; |
| 38 |
}; |
| 39 |
|
| 40 |
/** |
| 41 |
* Render score factor item |
| 42 |
*/ |
| 43 |
const renderScoreFactor = (factorKey, factorData) => { |
| 44 |
const percentage = factorData.percentage || 0; |
| 45 |
const score = factorData.score || 0; |
| 46 |
const maxScore = factorData.max_score || 0; |
| 47 |
const fillWidth = maxScore > 0 ? (score / maxScore) * 100 : 0; |
| 48 |
|
| 49 |
const factorLabels = { |
| 50 |
'satisfying_content': __('Satisfying Content', 'thinkrank'), |
| 51 |
'title_optimization': __('Title Optimization', 'thinkrank'), |
| 52 |
'niche_expertise': __('Niche Expertise', 'thinkrank'), |
| 53 |
'searcher_engagement': __('Searcher Engagement', 'thinkrank'), |
| 54 |
'backlink_authority': __('Backlink Authority', 'thinkrank'), |
| 55 |
'content_freshness': __('Content Freshness', 'thinkrank'), |
| 56 |
'mobile_experience': __('Mobile Experience', 'thinkrank'), |
| 57 |
'trustworthiness': __('Trustworthiness', 'thinkrank'), |
| 58 |
'link_diversity': __('Link Diversity', 'thinkrank'), |
| 59 |
'core_web_vitals': __('Core Web Vitals', 'thinkrank'), |
| 60 |
'site_security': __('Site Security', 'thinkrank'), |
| 61 |
'internal_linking': __('Internal Linking', 'thinkrank'), |
| 62 |
'technical_factors': __('Technical Factors', 'thinkrank') |
| 63 |
}; |
| 64 |
|
| 65 |
const factorIcons = { |
| 66 |
'satisfying_content': '🎯', |
| 67 |
'title_optimization': '📝', |
| 68 |
'niche_expertise': '🧠', |
| 69 |
'searcher_engagement': '👥', |
| 70 |
'backlink_authority': '🔗', |
| 71 |
'content_freshness': '🔄', |
| 72 |
'mobile_experience': '📱', |
| 73 |
'trustworthiness': '🛡️', |
| 74 |
'link_diversity': '🌐', |
| 75 |
'core_web_vitals': '⚡', |
| 76 |
'site_security': '🔒', |
| 77 |
'internal_linking': '🔗', |
| 78 |
'technical_factors': '⚙️' |
| 79 |
}; |
| 80 |
|
| 81 |
return ( |
| 82 |
<div key={factorKey} id={`score-${factorKey.replace('_', '-')}`} className="score-factor-item"> |
| 83 |
<div className="factor-header"> |
| 84 |
<span className="factor-icon">{factorIcons[factorKey] || '📊'}</span> |
| 85 |
<span className="factor-label">{factorLabels[factorKey] || factorKey}</span> |
| 86 |
<div className="factor-score"> |
| 87 |
<span className="score-value">{score}/{maxScore}</span> |
| 88 |
<div className="score-bar"> |
| 89 |
<div className="score-bar-fill" style={{ width: `${fillWidth}%` }}></div> |
| 90 |
</div> |
| 91 |
</div> |
| 92 |
</div> |
| 93 |
</div> |
| 94 |
); |
| 95 |
}; |
| 96 |
|
| 97 |
return ( |
| 98 |
<div className="thinkrank-analysis-results" id="thinkrank-analysis-results"> |
| 99 |
{/* SEO Score Section */} |
| 100 |
<div className="analysis-section score-section"> |
| 101 |
<div className={`score-circle ${getScoreClass(analysisData.overall_score || 0)}`}> |
| 102 |
<span className="score-number" id="analysis-score">{analysisData.overall_score || 0}</span> |
| 103 |
<span className="score-grade" id="seo-grade">{getGrade(analysisData.overall_score || 0)}</span> |
| 104 |
<span className="score-label">{__('SEO Score', 'thinkrank')}</span> |
| 105 |
</div> |
| 106 |
<div className="score-breakdown"> |
| 107 |
<div className="score-item"> |
| 108 |
<span className="score-label">{__('Content Quality', 'thinkrank')}</span> |
| 109 |
<span className="score-value" id="content-quality"> |
| 110 |
{analysisData.content_quality || '-'} |
| 111 |
</span> |
| 112 |
</div> |
| 113 |
<div className="score-item"> |
| 114 |
<span className="score-label">{__('Keyword Optimization', 'thinkrank')}</span> |
| 115 |
<span className="score-value" id="keyword-optimization"> |
| 116 |
{analysisData.keyword_optimization || '-'} |
| 117 |
</span> |
| 118 |
</div> |
| 119 |
<div className="score-item"> |
| 120 |
<span className="score-label">{__('Readability', 'thinkrank')}</span> |
| 121 |
<span className="score-value" id="readability-score"> |
| 122 |
{analysisData.readability_score || '-'} |
| 123 |
</span> |
| 124 |
</div> |
| 125 |
</div> |
| 126 |
</div> |
| 127 |
|
| 128 |
{/* Comprehensive Score Breakdown Section */} |
| 129 |
{showScoreBreakdown && analysisData.score_breakdown && ( |
| 130 |
<div id="seo-score-breakdown" className="analysis-section comprehensive-score-section"> |
| 131 |
<h5>{__('2025 SEO Analysis (Google Q1 Algorithm)', 'thinkrank')}</h5> |
| 132 |
{Object.entries(analysisData.score_breakdown).map(([factorKey, factorData]) => |
| 133 |
renderScoreFactor(factorKey, factorData) |
| 134 |
)} |
| 135 |
</div> |
| 136 |
)} |
| 137 |
|
| 138 |
{/* Content Analysis Section */} |
| 139 |
<div className="analysis-section content-analysis"> |
| 140 |
<h4>{__('Content Analysis', 'thinkrank')}</h4> |
| 141 |
<div className="analysis-grid"> |
| 142 |
<div className="analysis-item"> |
| 143 |
<span className="analysis-label">{__('Word Count', 'thinkrank')}</span> |
| 144 |
<span className="analysis-value" id="word-count"> |
| 145 |
{analysisData.word_count || '-'} |
| 146 |
</span> |
| 147 |
</div> |
| 148 |
<div className="analysis-item"> |
| 149 |
<span className="analysis-label">{__('Structure', 'thinkrank')}</span> |
| 150 |
<span className="analysis-value" id="content-structure"> |
| 151 |
{analysisData.content_structure || '-'} |
| 152 |
</span> |
| 153 |
</div> |
| 154 |
<div className="analysis-item"> |
| 155 |
<span className="analysis-label">{__('Keyword Density', 'thinkrank')}</span> |
| 156 |
<span className="analysis-value" id="keyword-density"> |
| 157 |
{analysisData.keyword_density || '-'} |
| 158 |
</span> |
| 159 |
</div> |
| 160 |
</div> |
| 161 |
</div> |
| 162 |
|
| 163 |
{/* Suggestions Section */} |
| 164 |
{analysisData.suggestions && analysisData.suggestions.length > 0 && ( |
| 165 |
<div className="analysis-section suggestions-section"> |
| 166 |
<h4>{__('Improvement Suggestions', 'thinkrank')}</h4> |
| 167 |
<ul className="suggestions-list" id="improvement-suggestions"> |
| 168 |
{analysisData.suggestions.map((suggestion, index) => ( |
| 169 |
<li key={index}> |
| 170 |
{typeof suggestion === 'string' ? suggestion : suggestion.text || suggestion.message || JSON.stringify(suggestion)} |
| 171 |
</li> |
| 172 |
))} |
| 173 |
</ul> |
| 174 |
</div> |
| 175 |
)} |
| 176 |
|
| 177 |
{/* Strengths & Weaknesses */} |
| 178 |
{(analysisData.strengths || analysisData.weaknesses) && ( |
| 179 |
<div className="analysis-section strengths-weaknesses"> |
| 180 |
{analysisData.strengths && analysisData.strengths.length > 0 && ( |
| 181 |
<div className="strengths-column"> |
| 182 |
<h5>{__('Strengths', 'thinkrank')}</h5> |
| 183 |
<ul className="strengths-list" id="content-strengths"> |
| 184 |
{analysisData.strengths.map((strength, index) => ( |
| 185 |
<li key={index}> |
| 186 |
{typeof strength === 'string' ? strength : strength.text || strength.message || JSON.stringify(strength)} |
| 187 |
</li> |
| 188 |
))} |
| 189 |
</ul> |
| 190 |
</div> |
| 191 |
)} |
| 192 |
{analysisData.weaknesses && analysisData.weaknesses.length > 0 && ( |
| 193 |
<div className="weaknesses-column"> |
| 194 |
<h5>{__('Areas for Improvement', 'thinkrank')}</h5> |
| 195 |
<ul className="weaknesses-list" id="content-weaknesses"> |
| 196 |
{analysisData.weaknesses.map((weakness, index) => ( |
| 197 |
<li key={index}> |
| 198 |
{typeof weakness === 'string' ? weakness : weakness.text || weakness.message || JSON.stringify(weakness)} |
| 199 |
</li> |
| 200 |
))} |
| 201 |
</ul> |
| 202 |
</div> |
| 203 |
)} |
| 204 |
</div> |
| 205 |
)} |
| 206 |
</div> |
| 207 |
); |
| 208 |
}; |
| 209 |
|
| 210 |
export default AnalysisResults; |
| 211 |
|