@orion-studios/payload-seo-audit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +127 -0
  2. package/bin/init.js +267 -0
  3. package/dist/api/backlinks-import.d.ts +4 -0
  4. package/dist/api/backlinks-import.d.ts.map +1 -0
  5. package/dist/api/backlinks-import.js +182 -0
  6. package/dist/api/cron.d.ts +4 -0
  7. package/dist/api/cron.d.ts.map +1 -0
  8. package/dist/api/cron.js +89 -0
  9. package/dist/api/index.d.ts +10 -0
  10. package/dist/api/index.d.ts.map +1 -0
  11. package/dist/api/index.js +21 -0
  12. package/dist/api/page-result.d.ts +4 -0
  13. package/dist/api/page-result.d.ts.map +1 -0
  14. package/dist/api/page-result.js +93 -0
  15. package/dist/api/page-results.d.ts +4 -0
  16. package/dist/api/page-results.d.ts.map +1 -0
  17. package/dist/api/page-results.js +83 -0
  18. package/dist/api/run-stream.d.ts +4 -0
  19. package/dist/api/run-stream.d.ts.map +1 -0
  20. package/dist/api/run-stream.js +273 -0
  21. package/dist/api/run.d.ts +4 -0
  22. package/dist/api/run.d.ts.map +1 -0
  23. package/dist/api/run.js +102 -0
  24. package/dist/api/snapshot-report.d.ts +4 -0
  25. package/dist/api/snapshot-report.d.ts.map +1 -0
  26. package/dist/api/snapshot-report.js +130 -0
  27. package/dist/api/snapshots.d.ts +4 -0
  28. package/dist/api/snapshots.d.ts.map +1 -0
  29. package/dist/api/snapshots.js +138 -0
  30. package/dist/api/trend.d.ts +4 -0
  31. package/dist/api/trend.d.ts.map +1 -0
  32. package/dist/api/trend.js +71 -0
  33. package/dist/collections/SeoAuthoritySnapshots.d.ts +3 -0
  34. package/dist/collections/SeoAuthoritySnapshots.d.ts.map +1 -0
  35. package/dist/collections/SeoAuthoritySnapshots.js +83 -0
  36. package/dist/collections/SeoKeywordVisibility.d.ts +3 -0
  37. package/dist/collections/SeoKeywordVisibility.d.ts.map +1 -0
  38. package/dist/collections/SeoKeywordVisibility.js +65 -0
  39. package/dist/collections/SeoPageResults.d.ts +3 -0
  40. package/dist/collections/SeoPageResults.d.ts.map +1 -0
  41. package/dist/collections/SeoPageResults.js +170 -0
  42. package/dist/collections/SeoSnapshots.d.ts +3 -0
  43. package/dist/collections/SeoSnapshots.d.ts.map +1 -0
  44. package/dist/collections/SeoSnapshots.js +131 -0
  45. package/dist/components/hooks/useSeoApi.d.ts +7 -0
  46. package/dist/components/hooks/useSeoApi.d.ts.map +1 -0
  47. package/dist/components/hooks/useSeoApi.js +31 -0
  48. package/dist/components/hooks/useSeoPageResults.d.ts +19 -0
  49. package/dist/components/hooks/useSeoPageResults.d.ts.map +1 -0
  50. package/dist/components/hooks/useSeoPageResults.js +62 -0
  51. package/dist/components/hooks/useSeoSnapshot.d.ts +8 -0
  52. package/dist/components/hooks/useSeoSnapshot.d.ts.map +1 -0
  53. package/dist/components/hooks/useSeoSnapshot.js +39 -0
  54. package/dist/components/hooks/useSeoTrend.d.ts +8 -0
  55. package/dist/components/hooks/useSeoTrend.d.ts.map +1 -0
  56. package/dist/components/hooks/useSeoTrend.js +38 -0
  57. package/dist/components/layout/SeoReportHeader.d.ts +10 -0
  58. package/dist/components/layout/SeoReportHeader.d.ts.map +1 -0
  59. package/dist/components/layout/SeoReportHeader.js +18 -0
  60. package/dist/components/layout/SeoReportShell.d.ts +9 -0
  61. package/dist/components/layout/SeoReportShell.d.ts.map +1 -0
  62. package/dist/components/layout/SeoReportShell.js +17 -0
  63. package/dist/components/pdf/PdfDownloadButton.d.ts +9 -0
  64. package/dist/components/pdf/PdfDownloadButton.d.ts.map +1 -0
  65. package/dist/components/pdf/PdfDownloadButton.js +80 -0
  66. package/dist/components/tables/IssueTable.d.ts +11 -0
  67. package/dist/components/tables/IssueTable.d.ts.map +1 -0
  68. package/dist/components/tables/IssueTable.js +121 -0
  69. package/dist/components/tables/PageResultsTable.d.ts +18 -0
  70. package/dist/components/tables/PageResultsTable.d.ts.map +1 -0
  71. package/dist/components/tables/PageResultsTable.js +96 -0
  72. package/dist/components/types.d.ts +107 -0
  73. package/dist/components/types.d.ts.map +1 -0
  74. package/dist/components/types.js +22 -0
  75. package/dist/components/utils/formatters.d.ts +15 -0
  76. package/dist/components/utils/formatters.d.ts.map +1 -0
  77. package/dist/components/utils/formatters.js +98 -0
  78. package/dist/components/utils/scoreHelpers.d.ts +17 -0
  79. package/dist/components/utils/scoreHelpers.d.ts.map +1 -0
  80. package/dist/components/utils/scoreHelpers.js +139 -0
  81. package/dist/components/views/SeoDashboard.d.ts +3 -0
  82. package/dist/components/views/SeoDashboard.d.ts.map +1 -0
  83. package/dist/components/views/SeoDashboard.js +239 -0
  84. package/dist/components/views/SeoPageReport.d.ts +3 -0
  85. package/dist/components/views/SeoPageReport.d.ts.map +1 -0
  86. package/dist/components/views/SeoPageReport.js +234 -0
  87. package/dist/components/views/SeoSnapshotReport.d.ts +3 -0
  88. package/dist/components/views/SeoSnapshotReport.d.ts.map +1 -0
  89. package/dist/components/views/SeoSnapshotReport.js +224 -0
  90. package/dist/components/visualization/CategoryScoreCard.d.ts +11 -0
  91. package/dist/components/visualization/CategoryScoreCard.d.ts.map +1 -0
  92. package/dist/components/visualization/CategoryScoreCard.js +17 -0
  93. package/dist/components/visualization/CategoryScoreGrid.d.ts +9 -0
  94. package/dist/components/visualization/CategoryScoreGrid.d.ts.map +1 -0
  95. package/dist/components/visualization/CategoryScoreGrid.js +32 -0
  96. package/dist/components/visualization/IssueCategoryChart.d.ts +8 -0
  97. package/dist/components/visualization/IssueCategoryChart.d.ts.map +1 -0
  98. package/dist/components/visualization/IssueCategoryChart.js +47 -0
  99. package/dist/components/visualization/MetricCard.d.ts +11 -0
  100. package/dist/components/visualization/MetricCard.d.ts.map +1 -0
  101. package/dist/components/visualization/MetricCard.js +17 -0
  102. package/dist/components/visualization/MetricCardRow.d.ts +7 -0
  103. package/dist/components/visualization/MetricCardRow.d.ts.map +1 -0
  104. package/dist/components/visualization/MetricCardRow.js +12 -0
  105. package/dist/components/visualization/ScoreBar.d.ts +11 -0
  106. package/dist/components/visualization/ScoreBar.d.ts.map +1 -0
  107. package/dist/components/visualization/ScoreBar.js +34 -0
  108. package/dist/components/visualization/ScoreGauge.d.ts +11 -0
  109. package/dist/components/visualization/ScoreGauge.d.ts.map +1 -0
  110. package/dist/components/visualization/ScoreGauge.js +28 -0
  111. package/dist/components/visualization/ScoreTrendChart.d.ts +9 -0
  112. package/dist/components/visualization/ScoreTrendChart.d.ts.map +1 -0
  113. package/dist/components/visualization/ScoreTrendChart.js +43 -0
  114. package/dist/components/visualization/SeverityBadge.d.ts +8 -0
  115. package/dist/components/visualization/SeverityBadge.d.ts.map +1 -0
  116. package/dist/components/visualization/SeverityBadge.js +14 -0
  117. package/dist/config.d.ts +38 -0
  118. package/dist/config.d.ts.map +1 -0
  119. package/dist/config.js +36 -0
  120. package/dist/exports/components.d.ts +4 -0
  121. package/dist/exports/components.d.ts.map +1 -0
  122. package/dist/exports/components.js +9 -0
  123. package/dist/globals/SeoDashboard.d.ts +3 -0
  124. package/dist/globals/SeoDashboard.d.ts.map +1 -0
  125. package/dist/globals/SeoDashboard.js +25 -0
  126. package/dist/index.d.ts +6 -0
  127. package/dist/index.d.ts.map +1 -0
  128. package/dist/index.js +39 -0
  129. package/dist/utilities/access.d.ts +8 -0
  130. package/dist/utilities/access.d.ts.map +1 -0
  131. package/dist/utilities/access.js +11 -0
  132. package/dist/utilities/auth.d.ts +7 -0
  133. package/dist/utilities/auth.d.ts.map +1 -0
  134. package/dist/utilities/auth.js +28 -0
  135. package/dist/utilities/checks.d.ts +3 -0
  136. package/dist/utilities/checks.d.ts.map +1 -0
  137. package/dist/utilities/checks.js +255 -0
  138. package/dist/utilities/crawler.d.ts +14 -0
  139. package/dist/utilities/crawler.d.ts.map +1 -0
  140. package/dist/utilities/crawler.js +152 -0
  141. package/dist/utilities/gsc.d.ts +15 -0
  142. package/dist/utilities/gsc.d.ts.map +1 -0
  143. package/dist/utilities/gsc.js +69 -0
  144. package/dist/utilities/helpers.d.ts +7 -0
  145. package/dist/utilities/helpers.d.ts.map +1 -0
  146. package/dist/utilities/helpers.js +44 -0
  147. package/dist/utilities/pagespeed.d.ts +3 -0
  148. package/dist/utilities/pagespeed.d.ts.map +1 -0
  149. package/dist/utilities/pagespeed.js +49 -0
  150. package/dist/utilities/providers.d.ts +3 -0
  151. package/dist/utilities/providers.d.ts.map +1 -0
  152. package/dist/utilities/providers.js +18 -0
  153. package/dist/utilities/runAudit.d.ts +14 -0
  154. package/dist/utilities/runAudit.d.ts.map +1 -0
  155. package/dist/utilities/runAudit.js +224 -0
  156. package/dist/utilities/scoring.d.ts +3 -0
  157. package/dist/utilities/scoring.d.ts.map +1 -0
  158. package/dist/utilities/scoring.js +45 -0
  159. package/dist/utilities/triggers.d.ts +3 -0
  160. package/dist/utilities/triggers.d.ts.map +1 -0
  161. package/dist/utilities/triggers.js +39 -0
  162. package/dist/utilities/types.d.ts +87 -0
  163. package/dist/utilities/types.d.ts.map +1 -0
  164. package/dist/utilities/types.js +2 -0
  165. package/package.json +63 -0
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.SeoSnapshotReport = void 0;
38
+ const react_1 = __importStar(require("react"));
39
+ const ScoreGauge_1 = require("../visualization/ScoreGauge");
40
+ const MetricCard_1 = require("../visualization/MetricCard");
41
+ const CategoryScoreGrid_1 = require("../visualization/CategoryScoreGrid");
42
+ const IssueCategoryChart_1 = require("../visualization/IssueCategoryChart");
43
+ const SeverityBadge_1 = require("../visualization/SeverityBadge");
44
+ const IssueTable_1 = require("../tables/IssueTable");
45
+ const PageResultsTable_1 = require("../tables/PageResultsTable");
46
+ const SeoReportShell_1 = require("../layout/SeoReportShell");
47
+ const SeoReportHeader_1 = require("../layout/SeoReportHeader");
48
+ const PdfDownloadButton_1 = require("../pdf/PdfDownloadButton");
49
+ const formatters_1 = require("../utils/formatters");
50
+ const scoreHelpers_1 = require("../utils/scoreHelpers");
51
+ const types_1 = require("../types");
52
+ const getIdFromPath = () => {
53
+ if (typeof window === 'undefined')
54
+ return null;
55
+ const segments = window.location.pathname.split('/');
56
+ const idx = segments.indexOf('seo-snapshots');
57
+ if (idx >= 0 && segments[idx + 1]) {
58
+ return segments[idx + 1];
59
+ }
60
+ return null;
61
+ };
62
+ const SeoSnapshotReport = () => {
63
+ const [reportData, setReportData] = (0, react_1.useState)(null);
64
+ const [loading, setLoading] = (0, react_1.useState)(true);
65
+ const [error, setError] = (0, react_1.useState)(null);
66
+ const [pageResultsDocs, setPageResultsDocs] = (0, react_1.useState)([]);
67
+ const [pageResultsPagination, setPageResultsPagination] = (0, react_1.useState)({
68
+ totalDocs: 0,
69
+ totalPages: 0,
70
+ page: 1,
71
+ hasNextPage: false,
72
+ hasPrevPage: false,
73
+ });
74
+ const [pageResultsLoading, setPageResultsLoading] = (0, react_1.useState)(false);
75
+ const [activeTab, setActiveTab] = (0, react_1.useState)('overview');
76
+ const snapshotId = (0, react_1.useMemo)(() => getIdFromPath(), []);
77
+ const fetchReport = (0, react_1.useCallback)(async () => {
78
+ if (!snapshotId) {
79
+ setError('Could not determine snapshot ID.');
80
+ setLoading(false);
81
+ return;
82
+ }
83
+ setLoading(true);
84
+ setError(null);
85
+ try {
86
+ const response = await fetch(`/api/seo/snapshot-report?snapshot=${snapshotId}`, {
87
+ credentials: 'include',
88
+ });
89
+ if (!response.ok) {
90
+ const body = (await response.json().catch(() => ({})));
91
+ throw new Error(body.error || `Request failed with ${response.status}`);
92
+ }
93
+ const data = (await response.json());
94
+ setReportData(data);
95
+ setPageResultsDocs(data.pageResults.slice(0, 50));
96
+ setPageResultsPagination({
97
+ totalDocs: data.pageResults.length,
98
+ totalPages: Math.ceil(data.pageResults.length / 50),
99
+ page: 1,
100
+ hasNextPage: data.pageResults.length > 50,
101
+ hasPrevPage: false,
102
+ });
103
+ }
104
+ catch (err) {
105
+ setError(err instanceof Error ? err.message : 'Failed to load snapshot report.');
106
+ }
107
+ finally {
108
+ setLoading(false);
109
+ }
110
+ }, [snapshotId]);
111
+ (0, react_1.useEffect)(() => {
112
+ void fetchReport();
113
+ }, [fetchReport]);
114
+ const handlePageResultsPageChange = (0, react_1.useCallback)((page) => {
115
+ if (!reportData)
116
+ return;
117
+ const start = (page - 1) * 50;
118
+ const end = start + 50;
119
+ setPageResultsDocs(reportData.pageResults.slice(start, end));
120
+ setPageResultsPagination((prev) => ({
121
+ ...prev,
122
+ page,
123
+ hasNextPage: end < reportData.pageResults.length,
124
+ hasPrevPage: page > 1,
125
+ }));
126
+ }, [reportData]);
127
+ const handleSelectPage = (0, react_1.useCallback)((pageResult) => {
128
+ window.location.href = `/admin/collections/seo-page-results/${pageResult.id}`;
129
+ }, []);
130
+ const allIssues = (0, react_1.useMemo)(() => {
131
+ if (!reportData)
132
+ return [];
133
+ return reportData.pageResults.flatMap((pr) => (pr.issues || []).map((issue) => ({ ...issue, url: pr.url })));
134
+ }, [reportData]);
135
+ const severityTotal = (0, react_1.useMemo)(() => {
136
+ if (!reportData?.aggregated?.severityDistribution)
137
+ return 0;
138
+ return Object.values(reportData.aggregated.severityDistribution).reduce((a, b) => a + b, 0);
139
+ }, [reportData]);
140
+ if (loading) {
141
+ return (react_1.default.createElement(SeoReportShell_1.SeoReportShell, null,
142
+ react_1.default.createElement("div", { className: "flex items-center gap-3 py-12" },
143
+ react_1.default.createElement("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300" }),
144
+ react_1.default.createElement("p", { className: "text-sm text-slate-500 dark:text-slate-400" }, "Loading snapshot report..."))));
145
+ }
146
+ if (error || !reportData) {
147
+ return (react_1.default.createElement(SeoReportShell_1.SeoReportShell, { onBack: () => window.history.back(), backLabel: "Back to Snapshots" },
148
+ react_1.default.createElement("div", { className: "rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-900/30 dark:text-red-300" }, error || 'No data available.')));
149
+ }
150
+ const { snapshot, aggregated } = reportData;
151
+ const scores = snapshot.scores || {};
152
+ const metrics = snapshot.metrics || {};
153
+ const issueCounts = snapshot.issueCounts || {};
154
+ const overallScore = scores.overall ?? 0;
155
+ return (react_1.default.createElement(SeoReportShell_1.SeoReportShell, { onBack: () => window.history.back(), backLabel: "Back to Snapshots" },
156
+ react_1.default.createElement("div", { className: "space-y-6", id: "seo-snapshot-report" },
157
+ react_1.default.createElement(SeoReportHeader_1.SeoReportHeader, { title: snapshot.runLabel || 'SEO Audit Report', subtitle: (0, formatters_1.formatTimestamp)(snapshot.startedAt), actions: react_1.default.createElement(PdfDownloadButton_1.PdfDownloadButton, { targetId: "seo-snapshot-report", filename: `seo-report-${snapshot.runLabel || snapshotId}` }), badges: react_1.default.createElement(react_1.default.Fragment, null,
158
+ react_1.default.createElement("span", { className: "inline-flex items-center rounded-full border border-slate-200 bg-slate-50 px-2.5 py-0.5 text-xs font-medium text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-400" }, snapshot.runType || 'manual'),
159
+ react_1.default.createElement("span", { className: `inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium ${snapshot.status === 'completed'
160
+ ? 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-900/30 dark:text-green-400'
161
+ : snapshot.status === 'failed'
162
+ ? 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-900/30 dark:text-red-300'
163
+ : 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-900/30 dark:text-amber-400'}` }, snapshot.status)) }),
164
+ react_1.default.createElement("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-3" },
165
+ react_1.default.createElement("div", { className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-slate-200 bg-gradient-to-br from-slate-50 to-white py-8 dark:border-slate-700 dark:from-slate-800/50 dark:to-slate-900/50" },
166
+ react_1.default.createElement(ScoreGauge_1.ScoreGauge, { score: overallScore, size: 140, showGrade: true })),
167
+ react_1.default.createElement("div", { className: "col-span-2 grid grid-cols-2 gap-3 sm:grid-cols-3" },
168
+ react_1.default.createElement(MetricCard_1.MetricCard, { label: "Pages Checked", value: (0, formatters_1.formatNumber)(metrics.pagesChecked) }),
169
+ react_1.default.createElement(MetricCard_1.MetricCard, { label: "Critical", value: issueCounts.critical ?? 0, valueClassName: (issueCounts.critical ?? 0) > 0 ? 'text-red-600 dark:text-red-400' : 'text-slate-900 dark:text-slate-100' }),
170
+ react_1.default.createElement(MetricCard_1.MetricCard, { label: "High", value: issueCounts.high ?? 0, valueClassName: (issueCounts.high ?? 0) > 0 ? 'text-orange-600 dark:text-orange-400' : 'text-slate-900 dark:text-slate-100' }),
171
+ react_1.default.createElement(MetricCard_1.MetricCard, { label: "Medium", value: issueCounts.medium ?? 0 }),
172
+ react_1.default.createElement(MetricCard_1.MetricCard, { label: "Lighthouse", value: metrics.avgLighthousePerformance != null
173
+ ? (0, formatters_1.formatScore)(metrics.avgLighthousePerformance)
174
+ : '-', subtext: "Avg Performance" }),
175
+ react_1.default.createElement(MetricCard_1.MetricCard, { label: "Avg LCP", value: (0, formatters_1.formatMs)(metrics.avgLCPMs) }))),
176
+ react_1.default.createElement("div", null,
177
+ react_1.default.createElement("h2", { className: "mb-3 text-sm font-semibold text-slate-900 dark:text-slate-100" }, "Category Scores"),
178
+ react_1.default.createElement(CategoryScoreGrid_1.CategoryScoreGrid, { scores: scores })),
179
+ severityTotal > 0 && (react_1.default.createElement("div", null,
180
+ react_1.default.createElement("h2", { className: "mb-3 text-sm font-semibold text-slate-900 dark:text-slate-100" }, "Issue Severity Distribution"),
181
+ react_1.default.createElement("div", { className: "overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700" },
182
+ react_1.default.createElement("div", { className: "flex h-8" }, types_1.SEVERITY_ORDER.map((severity) => {
183
+ const count = reportData.aggregated.severityDistribution[severity] || 0;
184
+ if (count === 0)
185
+ return null;
186
+ const pct = (count / severityTotal) * 100;
187
+ return (react_1.default.createElement("div", { key: severity, className: `flex items-center justify-center text-xs font-medium text-white ${(0, scoreHelpers_1.getSeverityBarColor)(severity)}`, style: { width: `${pct}%` }, title: `${severity}: ${count}` }, pct >= 10 ? `${severity} (${count})` : count > 0 ? count : ''));
188
+ })),
189
+ react_1.default.createElement("div", { className: "flex flex-wrap gap-3 border-t border-slate-200 bg-slate-50 px-3 py-2 dark:border-slate-700 dark:bg-slate-800" }, types_1.SEVERITY_ORDER.map((severity) => {
190
+ const count = reportData.aggregated.severityDistribution[severity] || 0;
191
+ if (count === 0)
192
+ return null;
193
+ return (react_1.default.createElement("div", { key: severity, className: "flex items-center gap-1.5" },
194
+ react_1.default.createElement(SeverityBadge_1.SeverityBadge, { severity: severity }),
195
+ react_1.default.createElement("span", { className: "text-xs text-slate-600 dark:text-slate-400" }, count)));
196
+ }))))),
197
+ react_1.default.createElement("div", { className: "border-b border-slate-200 dark:border-slate-700" },
198
+ react_1.default.createElement("div", { className: "flex gap-6" }, ['overview', 'pages', 'issues'].map((tab) => (react_1.default.createElement("button", { key: tab, type: "button", onClick: () => setActiveTab(tab), className: `border-b-2 pb-2 text-sm font-medium transition-colors ${activeTab === tab
199
+ ? 'border-slate-900 text-slate-900 dark:border-slate-100 dark:text-slate-100'
200
+ : 'border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200'}` },
201
+ tab === 'overview' && 'Overview',
202
+ tab === 'pages' && `Pages (${reportData.pageResults.length})`,
203
+ tab === 'issues' && `Issues (${allIssues.length})`))))),
204
+ activeTab === 'overview' && (react_1.default.createElement("div", { className: "space-y-6" },
205
+ react_1.default.createElement("div", null,
206
+ react_1.default.createElement("h2", { className: "mb-3 text-sm font-semibold text-slate-900 dark:text-slate-100" }, "Issues by Category"),
207
+ react_1.default.createElement(IssueCategoryChart_1.IssueCategoryChart, { categoryDistribution: aggregated.categoryDistribution })),
208
+ aggregated.worstPages.length > 0 && (react_1.default.createElement("div", null,
209
+ react_1.default.createElement("h2", { className: "mb-3 text-sm font-semibold text-slate-900 dark:text-slate-100" }, "Pages Needing Attention"),
210
+ react_1.default.createElement("div", { className: "overflow-x-auto rounded-lg border border-slate-200 dark:border-slate-700" },
211
+ react_1.default.createElement("table", { className: "w-full text-sm" },
212
+ react_1.default.createElement("thead", null,
213
+ react_1.default.createElement("tr", { className: "border-b border-slate-200 bg-slate-50 dark:border-slate-700 dark:bg-slate-800" },
214
+ react_1.default.createElement("th", { className: "px-3 py-2 text-left font-medium text-slate-600 dark:text-slate-400" }, "URL"),
215
+ react_1.default.createElement("th", { className: "px-3 py-2 text-center font-medium text-slate-600 dark:text-slate-400" }, "Score"),
216
+ react_1.default.createElement("th", { className: "px-3 py-2 text-center font-medium text-slate-600 dark:text-slate-400" }, "Issues"))),
217
+ react_1.default.createElement("tbody", null, aggregated.worstPages.map((page) => (react_1.default.createElement("tr", { key: page.url, className: "border-b border-slate-100 dark:border-slate-700" },
218
+ react_1.default.createElement("td", { className: "px-3 py-2 text-blue-600 dark:text-blue-400" }, page.url),
219
+ react_1.default.createElement("td", { className: "px-3 py-2 text-center font-medium text-slate-900 dark:text-slate-100" }, page.score),
220
+ react_1.default.createElement("td", { className: "px-3 py-2 text-center text-slate-600 dark:text-slate-400" }, page.issueCount))))))))))),
221
+ activeTab === 'pages' && (react_1.default.createElement(PageResultsTable_1.PageResultsTable, { docs: pageResultsDocs, pagination: pageResultsPagination, loading: pageResultsLoading, onPageChange: handlePageResultsPageChange, onSelectPage: handleSelectPage })),
222
+ activeTab === 'issues' && react_1.default.createElement(IssueTable_1.IssueTable, { issues: allIssues, pageSize: 20 }))));
223
+ };
224
+ exports.SeoSnapshotReport = SeoSnapshotReport;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { SEOIssueCategory } from '../types';
3
+ type CategoryScoreCardProps = {
4
+ category: SEOIssueCategory;
5
+ score: number;
6
+ previousScore?: number | null;
7
+ icon?: React.ReactNode;
8
+ };
9
+ export declare const CategoryScoreCard: React.FC<CategoryScoreCardProps>;
10
+ export {};
11
+ //# sourceMappingURL=CategoryScoreCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryScoreCard.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/CategoryScoreCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAGhD,KAAK,sBAAsB,GAAG;IAC5B,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAoB9D,CAAA"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CategoryScoreCard = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const ScoreBar_1 = require("./ScoreBar");
10
+ const types_1 = require("../types");
11
+ const CategoryScoreCard = ({ category, score, previousScore, icon, }) => {
12
+ const label = types_1.CATEGORY_LABELS[category];
13
+ const weight = `${Math.round(types_1.CATEGORY_WEIGHTS[category] * 100)}%`;
14
+ return (react_1.default.createElement("div", { className: "rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-800/50" },
15
+ react_1.default.createElement(ScoreBar_1.ScoreBar, { label: label, score: score, weight: weight, previousScore: previousScore, icon: icon })));
16
+ };
17
+ exports.CategoryScoreCard = CategoryScoreCard;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { SEOScores } from '../types';
3
+ type CategoryScoreGridProps = {
4
+ scores: SEOScores;
5
+ previousScores?: SEOScores | null;
6
+ };
7
+ export declare const CategoryScoreGrid: React.FC<CategoryScoreGridProps>;
8
+ export {};
9
+ //# sourceMappingURL=CategoryScoreGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryScoreGrid.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/CategoryScoreGrid.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAWzB,OAAO,KAAK,EAAoB,SAAS,EAAE,MAAM,UAAU,CAAA;AAE3D,KAAK,sBAAsB,GAAG;IAC5B,MAAM,EAAE,SAAS,CAAA;IACjB,cAAc,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;CAClC,CAAA;AAsBD,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAiB9D,CAAA"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CategoryScoreGrid = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const lucide_react_1 = require("lucide-react");
10
+ const CategoryScoreCard_1 = require("./CategoryScoreCard");
11
+ const CATEGORY_ICONS = {
12
+ metadata: react_1.default.createElement(lucide_react_1.FileText, { size: 16 }),
13
+ indexability: react_1.default.createElement(lucide_react_1.Search, { size: 16 }),
14
+ structure: react_1.default.createElement(lucide_react_1.LayoutGrid, { size: 16 }),
15
+ links: react_1.default.createElement(lucide_react_1.Link2, { size: 16 }),
16
+ media: react_1.default.createElement(lucide_react_1.ImageIcon, { size: 16 }),
17
+ structuredData: react_1.default.createElement(lucide_react_1.Code2, { size: 16 }),
18
+ performance: react_1.default.createElement(lucide_react_1.Gauge, { size: 16 }),
19
+ };
20
+ const CATEGORIES = [
21
+ 'metadata',
22
+ 'indexability',
23
+ 'structure',
24
+ 'links',
25
+ 'media',
26
+ 'structuredData',
27
+ 'performance',
28
+ ];
29
+ const CategoryScoreGrid = ({ scores, previousScores, }) => {
30
+ return (react_1.default.createElement("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" }, CATEGORIES.map((category) => (react_1.default.createElement(CategoryScoreCard_1.CategoryScoreCard, { key: category, category: category, score: scores[category] ?? 0, previousScore: previousScores?.[category], icon: CATEGORY_ICONS[category] })))));
31
+ };
32
+ exports.CategoryScoreGrid = CategoryScoreGrid;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type IssueCategoryChartProps = {
3
+ categoryDistribution: Record<string, number>;
4
+ height?: number;
5
+ };
6
+ export declare const IssueCategoryChart: React.FC<IssueCategoryChartProps>;
7
+ export {};
8
+ //# sourceMappingURL=IssueCategoryChart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IssueCategoryChart.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/IssueCategoryChart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAczB,KAAK,uBAAuB,GAAG;IAC7B,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAYD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA2DhE,CAAA"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.IssueCategoryChart = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const recharts_1 = require("recharts");
10
+ const types_1 = require("../types");
11
+ const CATEGORY_COLORS = {
12
+ metadata: '#6366f1',
13
+ indexability: '#8b5cf6',
14
+ structure: '#06b6d4',
15
+ links: '#0ea5e9',
16
+ media: '#f59e0b',
17
+ structuredData: '#10b981',
18
+ performance: '#ef4444',
19
+ };
20
+ const IssueCategoryChart = ({ categoryDistribution, height = 240, }) => {
21
+ const chartData = Object.entries(categoryDistribution)
22
+ .map(([key, value]) => ({
23
+ category: types_1.CATEGORY_LABELS[key] || key,
24
+ key,
25
+ count: value,
26
+ }))
27
+ .sort((a, b) => b.count - a.count);
28
+ if (chartData.length === 0) {
29
+ return (react_1.default.createElement("div", { className: "flex h-40 items-center justify-center rounded-lg border border-slate-200 bg-slate-50" },
30
+ react_1.default.createElement("p", { className: "text-sm text-slate-500" }, "No issues to display.")));
31
+ }
32
+ return (react_1.default.createElement("div", { style: { width: '100%', height } },
33
+ react_1.default.createElement(recharts_1.ResponsiveContainer, null,
34
+ react_1.default.createElement(recharts_1.BarChart, { data: chartData, margin: { top: 5, right: 20, left: 0, bottom: 5 }, layout: "vertical" },
35
+ react_1.default.createElement(recharts_1.CartesianGrid, { strokeDasharray: "3 3", stroke: "#e2e8f0", horizontal: false }),
36
+ react_1.default.createElement(recharts_1.XAxis, { type: "number", tick: { fontSize: 11, fill: '#64748b' }, tickLine: false, axisLine: { stroke: '#e2e8f0' } }),
37
+ react_1.default.createElement(recharts_1.YAxis, { type: "category", dataKey: "category", width: 110, tick: { fontSize: 11, fill: '#64748b' }, tickLine: false, axisLine: { stroke: '#e2e8f0' } }),
38
+ react_1.default.createElement(recharts_1.Tooltip, { contentStyle: {
39
+ backgroundColor: '#fff',
40
+ border: '1px solid #e2e8f0',
41
+ borderRadius: '8px',
42
+ fontSize: '12px',
43
+ boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1)',
44
+ }, formatter: ((value) => [value ?? 0, 'Issues']) }),
45
+ react_1.default.createElement(recharts_1.Bar, { dataKey: "count", radius: [0, 4, 4, 0], maxBarSize: 28 }, chartData.map((entry) => (react_1.default.createElement(recharts_1.Cell, { key: entry.key, fill: CATEGORY_COLORS[entry.key] || '#94a3b8' }))))))));
46
+ };
47
+ exports.IssueCategoryChart = IssueCategoryChart;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ type MetricCardProps = {
3
+ label: string;
4
+ value: string | number;
5
+ subtext?: string;
6
+ icon?: React.ReactNode;
7
+ valueClassName?: string;
8
+ };
9
+ export declare const MetricCard: React.FC<MetricCardProps>;
10
+ export {};
11
+ //# sourceMappingURL=MetricCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricCard.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/MetricCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAiBhD,CAAA"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MetricCard = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const MetricCard = ({ label, value, subtext, icon, valueClassName = 'text-slate-900 dark:text-slate-100', }) => {
10
+ return (react_1.default.createElement("div", { className: "rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-800/50" },
11
+ react_1.default.createElement("div", { className: "flex items-start justify-between" },
12
+ react_1.default.createElement("p", { className: "text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400" }, label),
13
+ icon && react_1.default.createElement("span", { className: "text-slate-400 dark:text-slate-500" }, icon)),
14
+ react_1.default.createElement("p", { className: `mt-1 text-2xl font-semibold ${valueClassName}` }, value),
15
+ subtext && react_1.default.createElement("p", { className: "mt-0.5 text-xs text-slate-500 dark:text-slate-400" }, subtext)));
16
+ };
17
+ exports.MetricCard = MetricCard;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type MetricCardRowProps = {
3
+ children: React.ReactNode;
4
+ };
5
+ export declare const MetricCardRow: React.FC<MetricCardRowProps>;
6
+ export {};
7
+ //# sourceMappingURL=MetricCardRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricCardRow.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/MetricCardRow.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAItD,CAAA"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MetricCardRow = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const MetricCardRow = ({ children }) => {
10
+ return (react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-6" }, children));
11
+ };
12
+ exports.MetricCardRow = MetricCardRow;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ type ScoreBarProps = {
3
+ label: string;
4
+ score: number;
5
+ weight?: string;
6
+ previousScore?: number | null;
7
+ icon?: React.ReactNode;
8
+ };
9
+ export declare const ScoreBar: React.FC<ScoreBarProps>;
10
+ export {};
11
+ //# sourceMappingURL=ScoreBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScoreBar.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/ScoreBar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAsC5C,CAAA"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ScoreBar = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const scoreHelpers_1 = require("../utils/scoreHelpers");
10
+ const formatters_1 = require("../utils/formatters");
11
+ const ScoreBar = ({ label, score, weight, previousScore, icon, }) => {
12
+ const clampedScore = Math.max(0, Math.min(100, Math.round(score)));
13
+ const barColor = (0, scoreHelpers_1.getScoreBarColorClass)(clampedScore);
14
+ const textColor = (0, scoreHelpers_1.getScoreTextClass)(clampedScore);
15
+ const delta = (0, scoreHelpers_1.getDeltaDisplay)(clampedScore, previousScore);
16
+ return (react_1.default.createElement("div", { className: "space-y-1.5" },
17
+ react_1.default.createElement("div", { className: "flex items-center justify-between" },
18
+ react_1.default.createElement("div", { className: "flex items-center gap-2" },
19
+ icon && react_1.default.createElement("span", { className: "text-slate-400 dark:text-slate-500" }, icon),
20
+ react_1.default.createElement("span", { className: "text-sm font-medium text-slate-700 dark:text-slate-300" }, label),
21
+ weight && react_1.default.createElement("span", { className: "text-xs text-slate-400 dark:text-slate-500" },
22
+ "(",
23
+ weight,
24
+ ")")),
25
+ react_1.default.createElement("div", { className: "flex items-center gap-2" },
26
+ react_1.default.createElement("span", { className: `text-sm font-semibold ${textColor}` }, (0, formatters_1.formatScore)(clampedScore)),
27
+ delta && (react_1.default.createElement("span", { className: `text-xs font-medium ${delta.className}` }, delta.text)))),
28
+ react_1.default.createElement("div", { className: "h-2 w-full overflow-hidden rounded-full bg-slate-100 dark:bg-slate-700" },
29
+ react_1.default.createElement("div", { className: `h-full rounded-full ${barColor}`, style: {
30
+ width: `${clampedScore}%`,
31
+ transition: 'width 0.6s ease-out',
32
+ } }))));
33
+ };
34
+ exports.ScoreBar = ScoreBar;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ type ScoreGaugeProps = {
3
+ score: number;
4
+ size?: number;
5
+ strokeWidth?: number;
6
+ label?: string;
7
+ showGrade?: boolean;
8
+ };
9
+ export declare const ScoreGauge: React.FC<ScoreGaugeProps>;
10
+ export {};
11
+ //# sourceMappingURL=ScoreGauge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScoreGauge.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/ScoreGauge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+DhD,CAAA"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ScoreGauge = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const scoreHelpers_1 = require("../utils/scoreHelpers");
10
+ const ScoreGauge = ({ score, size = 120, strokeWidth = 10, label, showGrade = false, }) => {
11
+ const radius = (size - strokeWidth) / 2;
12
+ const circumference = 2 * Math.PI * radius;
13
+ const clampedScore = Math.max(0, Math.min(100, Math.round(score)));
14
+ const offset = circumference - (clampedScore / 100) * circumference;
15
+ const color = (0, scoreHelpers_1.getScoreColor)(clampedScore);
16
+ const grade = (0, scoreHelpers_1.getScoreGrade)(clampedScore);
17
+ const center = size / 2;
18
+ return (react_1.default.createElement("div", { className: "inline-flex flex-col items-center gap-1" },
19
+ react_1.default.createElement("div", { className: "relative", style: { width: size, height: size } },
20
+ react_1.default.createElement("svg", { width: size, height: size, className: "transform -rotate-90" },
21
+ react_1.default.createElement("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: "currentColor", strokeWidth: strokeWidth, className: "text-slate-200 dark:text-slate-700" }),
22
+ react_1.default.createElement("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: color, strokeWidth: strokeWidth, strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", style: { transition: 'stroke-dashoffset 0.6s ease-out, stroke 0.3s ease' } })),
23
+ react_1.default.createElement("div", { className: "absolute inset-0 flex flex-col items-center justify-center" },
24
+ react_1.default.createElement("span", { className: "font-bold text-slate-900 dark:text-slate-100", style: { fontSize: size * 0.22 } }, clampedScore),
25
+ showGrade && (react_1.default.createElement("span", { className: `font-semibold ${(0, scoreHelpers_1.getGradeColorClass)(grade)}`, style: { fontSize: size * 0.14 } }, grade)))),
26
+ label && (react_1.default.createElement("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400" }, label))));
27
+ };
28
+ exports.ScoreGauge = ScoreGauge;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { SEOTrendPoint } from '../types';
3
+ type ScoreTrendChartProps = {
4
+ points: SEOTrendPoint[];
5
+ height?: number;
6
+ };
7
+ export declare const ScoreTrendChart: React.FC<ScoreTrendChartProps>;
8
+ export {};
9
+ //# sourceMappingURL=ScoreTrendChart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScoreTrendChart.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/ScoreTrendChart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAI7C,KAAK,oBAAoB,GAAG;IAC1B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA+D1D,CAAA"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ScoreTrendChart = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const recharts_1 = require("recharts");
10
+ const formatters_1 = require("../utils/formatters");
11
+ const scoreHelpers_1 = require("../utils/scoreHelpers");
12
+ const ScoreTrendChart = ({ points, height = 280 }) => {
13
+ const chartData = [...points].reverse().map((point) => ({
14
+ date: (0, formatters_1.formatDate)(point.startedAt),
15
+ overall: point.scores.overall ?? 0,
16
+ metadata: point.scores.metadata ?? 0,
17
+ indexability: point.scores.indexability ?? 0,
18
+ structure: point.scores.structure ?? 0,
19
+ performance: point.scores.performance ?? 0,
20
+ }));
21
+ if (chartData.length === 0) {
22
+ return (react_1.default.createElement("div", { className: "flex h-40 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 dark:border-slate-700 dark:bg-slate-800" },
23
+ react_1.default.createElement("p", { className: "text-sm text-slate-500 dark:text-slate-400" }, "No trend data available yet.")));
24
+ }
25
+ return (react_1.default.createElement("div", { style: { width: '100%', height } },
26
+ react_1.default.createElement(recharts_1.ResponsiveContainer, null,
27
+ react_1.default.createElement(recharts_1.LineChart, { data: chartData, margin: { top: 5, right: 20, left: 0, bottom: 5 } },
28
+ react_1.default.createElement(recharts_1.CartesianGrid, { strokeDasharray: "3 3", stroke: "#e2e8f0" }),
29
+ react_1.default.createElement(recharts_1.XAxis, { dataKey: "date", tick: { fontSize: 11, fill: '#64748b' }, tickLine: false, axisLine: { stroke: '#e2e8f0' } }),
30
+ react_1.default.createElement(recharts_1.YAxis, { domain: [0, 100], tick: { fontSize: 11, fill: '#64748b' }, tickLine: false, axisLine: { stroke: '#e2e8f0' }, width: 35 }),
31
+ react_1.default.createElement(recharts_1.Tooltip, { contentStyle: {
32
+ backgroundColor: '#fff',
33
+ border: '1px solid #e2e8f0',
34
+ borderRadius: '8px',
35
+ fontSize: '12px',
36
+ boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1)',
37
+ }, formatter: ((value, name) => [
38
+ value ?? 0,
39
+ String(name ?? '').charAt(0).toUpperCase() + String(name ?? '').slice(1),
40
+ ]) }),
41
+ react_1.default.createElement(recharts_1.Line, { type: "monotone", dataKey: "overall", stroke: (0, scoreHelpers_1.getScoreColor)(chartData[chartData.length - 1]?.overall ?? 50), strokeWidth: 2.5, dot: { fill: '#fff', stroke: (0, scoreHelpers_1.getScoreColor)(chartData[chartData.length - 1]?.overall ?? 50), strokeWidth: 2, r: 4 }, activeDot: { r: 6 } })))));
42
+ };
43
+ exports.ScoreTrendChart = ScoreTrendChart;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type SeverityBadgeProps = {
3
+ severity: string;
4
+ className?: string;
5
+ };
6
+ export declare const SeverityBadge: React.FC<SeverityBadgeProps>;
7
+ export {};
8
+ //# sourceMappingURL=SeverityBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SeverityBadge.d.ts","sourceRoot":"","sources":["../../../src/components/visualization/SeverityBadge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAUtD,CAAA"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SeverityBadge = void 0;
8
+ const react_1 = __importDefault(require("react"));
9
+ const scoreHelpers_1 = require("../utils/scoreHelpers");
10
+ const SeverityBadge = ({ severity, className = '' }) => {
11
+ const { badge } = (0, scoreHelpers_1.getSeverityColorClasses)(severity);
12
+ return (react_1.default.createElement("span", { className: `inline-flex items-center rounded border px-2 py-0.5 text-xs font-medium ${badge} ${className}` }, severity.toUpperCase()));
13
+ };
14
+ exports.SeverityBadge = SeverityBadge;