sanity-plugin-seofields 1.5.1 → 1.5.3

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 (32) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +97 -1085
  3. package/dist/SeoHealthDashboard-KPBNXSL4.cjs +10 -0
  4. package/dist/{SeoHealthDashboard-L4D4F3LO.cjs.map → SeoHealthDashboard-KPBNXSL4.cjs.map} +1 -1
  5. package/dist/SeoHealthDashboard-QKVB5HK3.js +4 -0
  6. package/dist/{SeoHealthDashboard-6RBRIYBX.js.map → SeoHealthDashboard-QKVB5HK3.js.map} +1 -1
  7. package/dist/{SeoHealthTool-FXNQPNPG.js → SeoHealthTool-EPPOEDTW.js} +3 -3
  8. package/dist/{SeoHealthTool-FXNQPNPG.js.map → SeoHealthTool-EPPOEDTW.js.map} +1 -1
  9. package/dist/{SeoHealthTool-FM2HXDPU.cjs → SeoHealthTool-ON3SRXCF.cjs} +4 -4
  10. package/dist/{SeoHealthTool-FM2HXDPU.cjs.map → SeoHealthTool-ON3SRXCF.cjs.map} +1 -1
  11. package/dist/{chunk-Z63UP35O.cjs → chunk-G2SVI2SP.cjs} +595 -306
  12. package/dist/chunk-G2SVI2SP.cjs.map +1 -0
  13. package/dist/{chunk-CNBJAXVH.js → chunk-UCVSMPEJ.js} +596 -307
  14. package/dist/chunk-UCVSMPEJ.js.map +1 -0
  15. package/dist/cli.js +1 -1
  16. package/dist/index.cjs +19 -3
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +30 -0
  19. package/dist/index.d.ts +30 -0
  20. package/dist/index.js +19 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/next.cjs +53 -33
  23. package/dist/next.cjs.map +1 -1
  24. package/dist/next.d.cts +39 -3
  25. package/dist/next.d.ts +39 -3
  26. package/dist/next.js +53 -33
  27. package/dist/next.js.map +1 -1
  28. package/package.json +18 -1
  29. package/dist/SeoHealthDashboard-6RBRIYBX.js +0 -4
  30. package/dist/SeoHealthDashboard-L4D4F3LO.cjs +0 -10
  31. package/dist/chunk-CNBJAXVH.js.map +0 -1
  32. package/dist/chunk-Z63UP35O.cjs.map +0 -1
@@ -853,6 +853,91 @@ var DeprecationBannerLink = styled__default.default.a`
853
853
  color: var(--seo-deprecation-text);
854
854
  }
855
855
  `;
856
+ var ExportButton = styled__default.default.button`
857
+ display: inline-flex;
858
+ align-items: center;
859
+ gap: 5px;
860
+ background: var(--seo-btn-bg);
861
+ color: var(--seo-btn-text);
862
+ font-size: 12px;
863
+ font-weight: 500;
864
+ padding: 6px 11px;
865
+ border-radius: 7px;
866
+ border: 1px solid var(--seo-btn-border);
867
+ cursor: pointer;
868
+ flex-shrink: 0;
869
+ transition:
870
+ background 0.15s,
871
+ color 0.15s,
872
+ border-color 0.15s;
873
+
874
+ &:hover {
875
+ background: var(--seo-btn-hover-bg);
876
+ border-color: var(--seo-btn-hover-border);
877
+ }
878
+ `;
879
+ var PaginationBar = styled__default.default.div`
880
+ display: flex;
881
+ align-items: center;
882
+ justify-content: space-between;
883
+ flex-wrap: wrap;
884
+ gap: 10px;
885
+ background: var(--seo-card-bg);
886
+ border-top: 1px solid var(--seo-border);
887
+ padding: 10px 20px;
888
+ font-size: 12px;
889
+ color: var(--seo-text-secondary);
890
+ `;
891
+ var PaginationCenter = styled__default.default.div`
892
+ display: flex;
893
+ align-items: center;
894
+ gap: 8px;
895
+ `;
896
+ var PaginationButton = styled__default.default.button`
897
+ display: inline-flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ width: 30px;
901
+ height: 30px;
902
+ border-radius: 6px;
903
+ border: 1px solid var(--seo-border);
904
+ background: var(--seo-btn-bg);
905
+ color: var(--seo-btn-text);
906
+ font-size: 13px;
907
+ cursor: pointer;
908
+ transition:
909
+ background 0.15s,
910
+ border-color 0.15s;
911
+
912
+ &:disabled {
913
+ opacity: 0.4;
914
+ cursor: not-allowed;
915
+ }
916
+
917
+ &:not(:disabled):hover {
918
+ background: var(--seo-btn-hover-bg);
919
+ border-color: var(--seo-btn-hover-border);
920
+ }
921
+ `;
922
+ var StatsRow = styled__default.default.div`
923
+ display: flex;
924
+ align-items: center;
925
+ flex-wrap: wrap;
926
+ gap: 6px;
927
+ margin-bottom: 20px;
928
+ `;
929
+ var StatPill = styled__default.default.span`
930
+ display: inline-flex;
931
+ align-items: center;
932
+ gap: 4px;
933
+ padding: 8px 12px;
934
+ border-radius: 10px;
935
+ font-size: 12px;
936
+ font-weight: 600;
937
+ background: var(--seo-card-bg);
938
+ border: 1px solid var(--seo-border);
939
+ color: var(--seo-text-secondary);
940
+ `;
856
941
  var TYPE_COLOR_PALETTE = [
857
942
  { bg: "#dbeafe", text: "#0c4a6e" },
858
943
  // Blue
@@ -903,6 +988,56 @@ var getStatusCategory = (score) => {
903
988
  if (score > 0) return "poor";
904
989
  return "missing";
905
990
  };
991
+ var RenderLicenseLoading = ({ text }) => /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { style: { padding: "80px 24px" }, children: [
992
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
993
+ text != null ? text : "Verifying license\u2026"
994
+ ] });
995
+ var RenderLicenseInvalid = ({ licenseKey, validateLicense }) => /* @__PURE__ */ jsxRuntime.jsx(UpgradeContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
996
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u274C" }),
997
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeTitle, { children: "Invalid License Key" }),
998
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeText, { children: "The license key you provided is invalid or has been revoked. Please check your key and update it in the plugin config." }),
999
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeCode, { children: `seofields({
1000
+ healthDashboard: {
1001
+ licenseKey: 'YOUR_LICENSE_KEY', // \u2190 replace with a valid key
1002
+ },
1003
+ })` }),
1004
+ /* @__PURE__ */ jsxRuntime.jsx(
1005
+ UpgradeButton,
1006
+ {
1007
+ href: "https://sanity-plugin-seofields.thehardik.in",
1008
+ target: "_blank",
1009
+ rel: "noopener noreferrer",
1010
+ children: "Get a New License Key \u2192"
1011
+ }
1012
+ ),
1013
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
1014
+ /* @__PURE__ */ jsxRuntime.jsx(ReloadButton, { onClick: () => validateLicense(true), children: "Click here If You Just Updated Your Key" })
1015
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1016
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u{1F512}" }),
1017
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeTitle, { children: "SEO Health Dashboard" }),
1018
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeText, { children: "This feature requires a license key. Add your key to the plugin config to unlock the full dashboard." }),
1019
+ /* @__PURE__ */ jsxRuntime.jsx(UpgradeCode, { children: `// sanity.config.ts
1020
+ import { seofields } from 'sanity-plugin-seofields'
1021
+
1022
+ export default defineConfig({
1023
+ plugins: [
1024
+ seofields({
1025
+ healthDashboard: {
1026
+ licenseKey: 'SEOF-XXXX-XXXX-XXXX',
1027
+ },
1028
+ }),
1029
+ ],
1030
+ })` }),
1031
+ /* @__PURE__ */ jsxRuntime.jsx(
1032
+ UpgradeButton,
1033
+ {
1034
+ href: "https://sanity-plugin-seofields.thehardik.in",
1035
+ target: "_blank",
1036
+ rel: "noopener noreferrer",
1037
+ children: "Get a License Key \u2192"
1038
+ }
1039
+ )
1040
+ ] }) }) });
906
1041
  var scoreMetaTitle = (title) => {
907
1042
  const issues = [];
908
1043
  let score = 0;
@@ -1145,6 +1280,8 @@ var generateDummyData = () => {
1145
1280
  health: calculateHealthScore(doc)
1146
1281
  }));
1147
1282
  };
1283
+ var VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
1284
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
1148
1285
  var SeoHealthDashboard = ({
1149
1286
  icon = "\u{1F4CA}",
1150
1287
  title = "SEO Health Dashboard",
@@ -1166,7 +1303,10 @@ var SeoHealthDashboard = ({
1166
1303
  previewMode = false,
1167
1304
  openInPane = false,
1168
1305
  structureTool,
1169
- _deprecationWarnings
1306
+ _deprecationWarnings,
1307
+ exportEnabled = true,
1308
+ exportFormats = ["csv", "json"],
1309
+ compactStats = false
1170
1310
  }) => {
1171
1311
  const resolvedTypeLabels = typeDisplayLabels;
1172
1312
  const resolvedDocBadge = getDocumentBadge;
@@ -1187,9 +1327,33 @@ var SeoHealthDashboard = ({
1187
1327
  const [loading, setLoading] = react.useState(true);
1188
1328
  const [isRefreshing, setIsRefreshing] = react.useState(false);
1189
1329
  const [searchQuery, setSearchQuery] = react.useState("");
1190
- const [filterStatus, setFilterStatus] = react.useState("all");
1191
- const [filterType, setFilterType] = react.useState("all");
1330
+ const [filterStatus, setFilterStatus] = react.useState(() => {
1331
+ var _a;
1332
+ try {
1333
+ return (_a = localStorage.getItem("seo-dashboard-filter-status")) != null ? _a : "all";
1334
+ } catch (e) {
1335
+ return "all";
1336
+ }
1337
+ });
1338
+ const [filterType, setFilterType] = react.useState(() => {
1339
+ var _a;
1340
+ try {
1341
+ return (_a = localStorage.getItem("seo-dashboard-filter-type")) != null ? _a : "all";
1342
+ } catch (e) {
1343
+ return "all";
1344
+ }
1345
+ });
1192
1346
  const [sortBy, setSortBy] = react.useState("score");
1347
+ const [currentPage, setCurrentPage] = react.useState(1);
1348
+ const [pageSize, setPageSize] = react.useState(() => {
1349
+ try {
1350
+ const stored = localStorage.getItem("seo-dashboard-page-size");
1351
+ return stored ? Number(stored) : 25;
1352
+ } catch (e) {
1353
+ return 25;
1354
+ }
1355
+ });
1356
+ const searchInputRef = react.useRef(null);
1193
1357
  const [activePopover, setActivePopover] = react.useState(null);
1194
1358
  const [themeMode, setThemeMode] = react.useState(() => {
1195
1359
  try {
@@ -1231,8 +1395,22 @@ var SeoHealthDashboard = ({
1231
1395
  () => handleThemeChange("system"),
1232
1396
  [handleThemeChange]
1233
1397
  );
1234
- const VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
1235
- const CACHE_TTL_MS = 60 * 60 * 1e3;
1398
+ const handleFilterStatusChange = react.useCallback((value) => {
1399
+ setFilterStatus(value);
1400
+ try {
1401
+ localStorage.setItem("seo-dashboard-filter-status", value);
1402
+ } catch (e) {
1403
+ }
1404
+ setCurrentPage(1);
1405
+ }, []);
1406
+ const handleFilterTypeChange = react.useCallback((value) => {
1407
+ setFilterType(value);
1408
+ try {
1409
+ localStorage.setItem("seo-dashboard-filter-type", value);
1410
+ } catch (e) {
1411
+ }
1412
+ setCurrentPage(1);
1413
+ }, []);
1236
1414
  const validateLicense = react.useCallback(
1237
1415
  async (forceRefresh = false) => {
1238
1416
  var _a;
@@ -1402,6 +1580,53 @@ var SeoHealthDashboard = ({
1402
1580
  });
1403
1581
  return sorted;
1404
1582
  }, [documents, searchQuery, filterStatus, filterType, sortBy]);
1583
+ const totalPages = Math.max(1, Math.ceil(filteredAndSortedDocs.length / pageSize));
1584
+ const paginatedDocs = react.useMemo(() => {
1585
+ const start = (currentPage - 1) * pageSize;
1586
+ return filteredAndSortedDocs.slice(start, start + pageSize);
1587
+ }, [filteredAndSortedDocs, currentPage, pageSize]);
1588
+ const handleExportCSV = react.useCallback(() => {
1589
+ const exportDocs = filteredAndSortedDocs;
1590
+ const rows = exportDocs.map((doc) => ({
1591
+ id: doc._id,
1592
+ type: doc._type,
1593
+ title: typeof doc.title === "string" ? doc.title : "",
1594
+ score: doc.health.score,
1595
+ status: doc.health.status,
1596
+ issues: doc.health.issues.join(" | ")
1597
+ }));
1598
+ const header = "id,type,title,score,status,issues";
1599
+ const csvRows = rows.map(
1600
+ (r) => [
1601
+ r.id,
1602
+ r.type,
1603
+ `"${r.title.replace(/"/g, '""')}"`,
1604
+ r.score,
1605
+ r.status,
1606
+ `"${r.issues.replace(/"/g, '""')}"`
1607
+ ].join(",")
1608
+ );
1609
+ const csv = [header, ...csvRows].join("\n");
1610
+ const blob = new Blob([csv], { type: "text/csv" });
1611
+ const url = URL.createObjectURL(blob);
1612
+ const a = document.createElement("a");
1613
+ a.href = url;
1614
+ a.download = "seo-health-export.csv";
1615
+ a.click();
1616
+ URL.revokeObjectURL(url);
1617
+ }, [filteredAndSortedDocs]);
1618
+ const handleExportJSON = react.useCallback(() => {
1619
+ const exportDocs = filteredAndSortedDocs;
1620
+ const blob = new Blob([JSON.stringify(exportDocs, null, 2)], {
1621
+ type: "application/json"
1622
+ });
1623
+ const url = URL.createObjectURL(blob);
1624
+ const a = document.createElement("a");
1625
+ a.href = url;
1626
+ a.download = "seo-health-export.json";
1627
+ a.click();
1628
+ URL.revokeObjectURL(url);
1629
+ }, [filteredAndSortedDocs]);
1405
1630
  const stats = react.useMemo(() => {
1406
1631
  const total = documents.length;
1407
1632
  const excellent = documents.filter((d) => d.health.score >= 80).length;
@@ -1415,340 +1640,404 @@ var SeoHealthDashboard = ({
1415
1640
  const handleMouseLeave = react.useCallback(() => {
1416
1641
  setActivePopover(null);
1417
1642
  }, []);
1418
- return /* @__PURE__ */ jsxRuntime.jsxs(DashboardContainer, { style: currentVars, children: [
1419
- licenseStatus === "loading" && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { style: { padding: "80px 24px" }, children: [
1420
- /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
1421
- loadingLicense != null ? loadingLicense : "Verifying license\u2026"
1422
- ] }),
1423
- licenseStatus === "invalid" && /* @__PURE__ */ jsxRuntime.jsx(UpgradeContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1424
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u274C" }),
1425
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeTitle, { children: "Invalid License Key" }),
1426
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeText, { children: "The license key you provided is invalid or has been revoked. Please check your key and update it in the plugin config." }),
1427
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeCode, { children: `seofields({
1428
- healthDashboard: {
1429
- licenseKey: 'YOUR_LICENSE_KEY', // \u2190 replace with a valid key
1430
- },
1431
- })` }),
1432
- /* @__PURE__ */ jsxRuntime.jsx(
1433
- UpgradeButton,
1434
- {
1435
- href: "https://sanity-plugin-seofields.thehardik.in",
1436
- target: "_blank",
1437
- rel: "noopener noreferrer",
1438
- children: "Get a New License Key \u2192"
1439
- }
1440
- ),
1441
- /* @__PURE__ */ jsxRuntime.jsx("br", {}),
1442
- /* @__PURE__ */ jsxRuntime.jsx(ReloadButton, { onClick: () => validateLicense(true), children: "Click here If You Just Updated Your Key" })
1443
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1444
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u{1F512}" }),
1445
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeTitle, { children: "SEO Health Dashboard" }),
1446
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeText, { children: "This feature requires a license key. Add your key to the plugin config to unlock the full dashboard." }),
1447
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeCode, { children: `// sanity.config.ts
1448
- import { seofields } from 'sanity-plugin-seofields'
1449
-
1450
- export default defineConfig({
1451
- plugins: [
1452
- seofields({
1453
- healthDashboard: {
1454
- licenseKey: 'SEOF-XXXX-XXXX-XXXX',
1455
- },
1456
- }),
1457
- ],
1458
- })` }),
1459
- /* @__PURE__ */ jsxRuntime.jsx(
1460
- UpgradeButton,
1461
- {
1462
- href: "https://sanity-plugin-seofields.thehardik.in",
1463
- target: "_blank",
1464
- rel: "noopener noreferrer",
1465
- children: "Get a License Key \u2192"
1466
- }
1467
- )
1468
- ] }) }) }),
1469
- licenseStatus === "valid" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1470
- /* @__PURE__ */ jsxRuntime.jsxs(PageHeader, { children: [
1471
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1472
- /* @__PURE__ */ jsxRuntime.jsxs(PageTitle, { children: [
1473
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1474
- icon,
1475
- " ",
1476
- title
1477
- ] }),
1478
- previewMode && /* @__PURE__ */ jsxRuntime.jsx(PreviewBadge, { children: "Preview Mode" })
1643
+ react.useEffect(() => {
1644
+ setCurrentPage(1);
1645
+ }, [searchQuery, filterStatus, filterType, sortBy]);
1646
+ react.useEffect(() => {
1647
+ const onKeyDown = (e) => {
1648
+ var _a;
1649
+ const target = e.target;
1650
+ const isEditable = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
1651
+ if (e.key === "/" && !isEditable) {
1652
+ e.preventDefault();
1653
+ (_a = searchInputRef.current) == null ? void 0 : _a.focus();
1654
+ }
1655
+ };
1656
+ window.addEventListener("keydown", onKeyDown);
1657
+ return () => window.removeEventListener("keydown", onKeyDown);
1658
+ }, []);
1659
+ const renderDashboardContent = () => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsxs(PageHeader, { children: [
1661
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1662
+ /* @__PURE__ */ jsxRuntime.jsxs(PageTitle, { children: [
1663
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1664
+ icon,
1665
+ " ",
1666
+ title
1479
1667
  ] }),
1480
- /* @__PURE__ */ jsxRuntime.jsx(PageSubtitle, { children: description })
1668
+ previewMode && /* @__PURE__ */ jsxRuntime.jsx(PreviewBadge, { children: "Preview Mode" })
1481
1669
  ] }),
1482
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px", flexShrink: 0 }, children: [
1483
- /* @__PURE__ */ jsxRuntime.jsxs(ThemeSwitcher, { children: [
1484
- /* @__PURE__ */ jsxRuntime.jsx(
1485
- ThemeButton,
1486
- {
1487
- $theme: "light",
1488
- $active: themeMode === "light",
1489
- onClick: handleThemeChangeLight,
1490
- title: "Light theme",
1491
- children: /* @__PURE__ */ jsxRuntime.jsx(SunIcon, {})
1492
- }
1493
- ),
1494
- /* @__PURE__ */ jsxRuntime.jsx(
1495
- ThemeButton,
1496
- {
1497
- $theme: "dark",
1498
- $active: themeMode === "dark",
1499
- onClick: handleThemeChangeDark,
1500
- title: "Dark theme",
1501
- children: /* @__PURE__ */ jsxRuntime.jsx(MoonIcon, {})
1502
- }
1503
- ),
1504
- /* @__PURE__ */ jsxRuntime.jsx(
1505
- ThemeButton,
1506
- {
1507
- $theme: "system",
1508
- $active: themeMode === "system",
1509
- onClick: handleThemeChangeSystem,
1510
- title: "System default",
1511
- children: /* @__PURE__ */ jsxRuntime.jsx(MonitorIcon, {})
1512
- }
1513
- )
1514
- ] }),
1515
- /* @__PURE__ */ jsxRuntime.jsxs(
1516
- DashboardRefreshButton,
1517
- {
1518
- onClick: handleRefresh,
1519
- disabled: loading || isRefreshing,
1520
- $spinning: isRefreshing,
1521
- title: "Refresh documents",
1522
- children: [
1523
- /* @__PURE__ */ jsxRuntime.jsxs(
1524
- "svg",
1525
- {
1526
- width: "14",
1527
- height: "14",
1528
- viewBox: "0 0 24 24",
1529
- fill: "none",
1530
- stroke: "currentColor",
1531
- strokeWidth: "2.2",
1532
- strokeLinecap: "round",
1533
- strokeLinejoin: "round",
1534
- children: [
1535
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "23 4 23 10 17 10" }),
1536
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 20 1 14 7 14" }),
1537
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
1538
- ]
1539
- }
1540
- ),
1541
- "Refresh"
1542
- ]
1543
- }
1544
- )
1545
- ] })
1670
+ /* @__PURE__ */ jsxRuntime.jsx(PageSubtitle, { children: description })
1546
1671
  ] }),
1547
- deprecationGroups.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(DeprecationBanner, { children: [
1548
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "\u26A0\uFE0F Deprecated config keys detected:" }),
1549
- " ",
1550
- deprecationGroups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1551
- group.keys.map((w, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1552
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "#fef9c3", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[0].trim() }),
1553
- " \u2192 ",
1554
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "#dcfce7", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[1].trim() }),
1555
- i < group.keys.length - 1 ? " \xB7 " : ""
1556
- ] }, w)),
1557
- " ",
1558
- "(",
1559
- /* @__PURE__ */ jsxRuntime.jsxs(
1560
- DeprecationBannerLink,
1672
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px", flexShrink: 0 }, children: [
1673
+ /* @__PURE__ */ jsxRuntime.jsxs(ThemeSwitcher, { children: [
1674
+ /* @__PURE__ */ jsxRuntime.jsx(
1675
+ ThemeButton,
1561
1676
  {
1562
- href: group.changelogUrl,
1563
- target: "_blank",
1564
- rel: "noopener noreferrer",
1565
- children: [
1566
- group.version,
1567
- " changelog"
1568
- ]
1677
+ $theme: "light",
1678
+ $active: themeMode === "light",
1679
+ onClick: handleThemeChangeLight,
1680
+ title: "Light theme",
1681
+ children: /* @__PURE__ */ jsxRuntime.jsx(SunIcon, {})
1569
1682
  }
1570
1683
  ),
1571
- ")",
1572
- gi < deprecationGroups.length - 1 ? " \xB7 " : ""
1573
- ] }, group.version)),
1574
- " ",
1575
- "\u2014 Please update your config."
1576
- ] }),
1577
- !loading && /* @__PURE__ */ jsxRuntime.jsxs(StatsGrid, { children: [
1578
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { children: [
1579
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Total Docs" }),
1580
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.total })
1581
- ] }),
1582
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { children: [
1583
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Avg Score" }),
1584
- /* @__PURE__ */ jsxRuntime.jsxs(StatValue, { children: [
1585
- stats.avgScore,
1586
- "%"
1587
- ] })
1588
- ] }),
1589
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#10b981", children: [
1590
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Excellent (80+)" }),
1591
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.excellent })
1592
- ] }),
1593
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#f59e0b", children: [
1594
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Good (60\u201379)" }),
1595
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.good })
1596
- ] }),
1597
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#f97316", children: [
1598
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Fair (40\u201359)" }),
1599
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.fair })
1600
- ] }),
1601
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#ef4444", children: [
1602
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Poor / Missing" }),
1603
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.poor + stats.missing })
1604
- ] })
1605
- ] }),
1606
- /* @__PURE__ */ jsxRuntime.jsxs(ControlsBar, { children: [
1607
- /* @__PURE__ */ jsxRuntime.jsxs(SearchWrapper, { children: [
1608
- /* @__PURE__ */ jsxRuntime.jsx(SearchIconSvg, { children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx(
1609
- "path",
1684
+ /* @__PURE__ */ jsxRuntime.jsx(
1685
+ ThemeButton,
1610
1686
  {
1611
- fillRule: "evenodd",
1612
- d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
1613
- clipRule: "evenodd"
1687
+ $theme: "dark",
1688
+ $active: themeMode === "dark",
1689
+ onClick: handleThemeChangeDark,
1690
+ title: "Dark theme",
1691
+ children: /* @__PURE__ */ jsxRuntime.jsx(MoonIcon, {})
1614
1692
  }
1615
- ) }) }),
1693
+ ),
1616
1694
  /* @__PURE__ */ jsxRuntime.jsx(
1617
- SearchInput,
1695
+ ThemeButton,
1618
1696
  {
1619
- placeholder: "Search documents...",
1620
- value: searchQuery,
1621
- onChange: (e) => setSearchQuery(e.currentTarget.value)
1697
+ $theme: "system",
1698
+ $active: themeMode === "system",
1699
+ onClick: handleThemeChangeSystem,
1700
+ title: "System default",
1701
+ children: /* @__PURE__ */ jsxRuntime.jsx(MonitorIcon, {})
1622
1702
  }
1623
1703
  )
1624
1704
  ] }),
1625
1705
  /* @__PURE__ */ jsxRuntime.jsxs(
1626
- StyledSelect,
1706
+ DashboardRefreshButton,
1627
1707
  {
1628
- value: filterStatus,
1629
- onChange: (e) => setFilterStatus(e.currentTarget.value),
1708
+ onClick: handleRefresh,
1709
+ disabled: loading || isRefreshing,
1710
+ $spinning: isRefreshing,
1711
+ title: "Refresh documents",
1630
1712
  children: [
1631
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Status" }),
1632
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "excellent", children: "Excellent" }),
1633
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "good", children: "Good" }),
1634
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "fair", children: "Fair" }),
1635
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "poor", children: "Poor" }),
1636
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "missing", children: "Missing" })
1713
+ /* @__PURE__ */ jsxRuntime.jsxs(
1714
+ "svg",
1715
+ {
1716
+ width: "14",
1717
+ height: "14",
1718
+ viewBox: "0 0 24 24",
1719
+ fill: "none",
1720
+ stroke: "currentColor",
1721
+ strokeWidth: "2.2",
1722
+ strokeLinecap: "round",
1723
+ strokeLinejoin: "round",
1724
+ children: [
1725
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "23 4 23 10 17 10" }),
1726
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 20 1 14 7 14" }),
1727
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
1728
+ ]
1729
+ }
1730
+ ),
1731
+ "Refresh"
1637
1732
  ]
1638
1733
  }
1639
- ),
1640
- uniqueDocumentTypes.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(
1641
- StyledSelect,
1734
+ )
1735
+ ] })
1736
+ ] }),
1737
+ deprecationGroups.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(DeprecationBanner, { children: [
1738
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "\u26A0\uFE0F Deprecated config keys detected:" }),
1739
+ " ",
1740
+ deprecationGroups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1741
+ group.keys.map((w, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1742
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "#fef9c3", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[0].trim() }),
1743
+ " \u2192 ",
1744
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "#dcfce7", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[1].trim() }),
1745
+ i < group.keys.length - 1 ? " \xB7 " : ""
1746
+ ] }, w)),
1747
+ " ",
1748
+ "(",
1749
+ /* @__PURE__ */ jsxRuntime.jsxs(
1750
+ DeprecationBannerLink,
1642
1751
  {
1643
- value: filterType,
1644
- onChange: (e) => setFilterType(e.currentTarget.value),
1752
+ href: group.changelogUrl,
1753
+ target: "_blank",
1754
+ rel: "noopener noreferrer",
1645
1755
  children: [
1646
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Types" }),
1647
- uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: resolveTypeLabel(type, resolvedTypeLabels) }, type))
1756
+ group.version,
1757
+ " changelog"
1648
1758
  ]
1649
1759
  }
1650
1760
  ),
1651
- /* @__PURE__ */ jsxRuntime.jsxs(
1652
- StyledSelect,
1761
+ ")",
1762
+ gi < deprecationGroups.length - 1 ? " \xB7 " : ""
1763
+ ] }, group.version)),
1764
+ " ",
1765
+ "\u2014 Please update your config."
1766
+ ] }),
1767
+ !loading && compactStats && /* @__PURE__ */ jsxRuntime.jsxs(StatsRow, { children: [
1768
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1769
+ "\u{1F4CB} ",
1770
+ stats.total
1771
+ ] }),
1772
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1773
+ "\u{1F7E2} Excellent: ",
1774
+ stats.excellent
1775
+ ] }),
1776
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1777
+ "\u{1F7E1} Good: ",
1778
+ stats.good
1779
+ ] }),
1780
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1781
+ "\u{1F7E0} Fair: ",
1782
+ stats.fair
1783
+ ] }),
1784
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1785
+ "\u{1F534} Poor/Missing: ",
1786
+ stats.poor + stats.missing
1787
+ ] }),
1788
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1789
+ "\u{1F4CA} Avg: ",
1790
+ stats.avgScore,
1791
+ "%"
1792
+ ] }),
1793
+ /* @__PURE__ */ jsxRuntime.jsxs(StatPill, { children: [
1794
+ "\u{1F5C2}\uFE0F Types: ",
1795
+ uniqueDocumentTypes.length
1796
+ ] })
1797
+ ] }),
1798
+ !loading && !compactStats && /* @__PURE__ */ jsxRuntime.jsxs(StatsGrid, { children: [
1799
+ /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { children: [
1800
+ /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Total Docs" }),
1801
+ /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.total })
1802
+ ] }),
1803
+ /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { children: [
1804
+ /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Avg Score" }),
1805
+ /* @__PURE__ */ jsxRuntime.jsxs(StatValue, { children: [
1806
+ stats.avgScore,
1807
+ "%"
1808
+ ] })
1809
+ ] }),
1810
+ /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#10b981", children: [
1811
+ /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Excellent (80+)" }),
1812
+ /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.excellent })
1813
+ ] }),
1814
+ /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#f59e0b", children: [
1815
+ /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Good (60\u201379)" }),
1816
+ /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.good })
1817
+ ] }),
1818
+ /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#f97316", children: [
1819
+ /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Fair (40\u201359)" }),
1820
+ /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.fair })
1821
+ ] }),
1822
+ /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#ef4444", children: [
1823
+ /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Poor / Missing" }),
1824
+ /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.poor + stats.missing })
1825
+ ] })
1826
+ ] }),
1827
+ /* @__PURE__ */ jsxRuntime.jsxs(ControlsBar, { children: [
1828
+ /* @__PURE__ */ jsxRuntime.jsxs(SearchWrapper, { children: [
1829
+ /* @__PURE__ */ jsxRuntime.jsx(SearchIconSvg, { children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx(
1830
+ "path",
1653
1831
  {
1654
- value: sortBy,
1655
- onChange: (e) => setSortBy(e.currentTarget.value),
1656
- children: [
1657
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "score", children: "Sort by Score" }),
1658
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "title", children: "Sort by Title" })
1659
- ]
1832
+ fillRule: "evenodd",
1833
+ d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
1834
+ clipRule: "evenodd"
1835
+ }
1836
+ ) }) }),
1837
+ /* @__PURE__ */ jsxRuntime.jsx(
1838
+ SearchInput,
1839
+ {
1840
+ ref: searchInputRef,
1841
+ placeholder: "Search documents... (press / to focus)",
1842
+ value: searchQuery,
1843
+ onChange: (e) => setSearchQuery(e.currentTarget.value)
1660
1844
  }
1661
1845
  )
1662
1846
  ] }),
1663
- /* @__PURE__ */ jsxRuntime.jsxs(TableCard, { children: [
1664
- loading && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { children: [
1665
- /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
1666
- loadingDocuments != null ? loadingDocuments : "Loading documents\u2026"
1667
- ] }),
1668
- !loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: noDocuments != null ? noDocuments : "No documents found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1669
- /* @__PURE__ */ jsxRuntime.jsxs(TableHeader, { children: [
1670
- /* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: "Title" }),
1671
- showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: "Type" }),
1672
- /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: "Score" }),
1673
- /* @__PURE__ */ jsxRuntime.jsx(ColIssues, { children: "Top Issues" })
1674
- ] }),
1675
- filteredAndSortedDocs.map((doc) => {
1676
- return /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
1677
- /* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: /* @__PURE__ */ jsxRuntime.jsx(TitleWrapper, { children: /* @__PURE__ */ jsxRuntime.jsxs(TitleCell, { children: [
1678
- doc.title !== null && typeof doc.title !== "string" ? /* @__PURE__ */ jsxRuntime.jsx(NonStringTitleWarning, { title: "title is not a string \u2014 use pt::text(title) in your query.groq projection to convert Portable Text to a plain string", children: "\u26A0 title is not a string \u2014 use pt::text(title) in query.groq" }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: openInPane ? /* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled" }) : /* @__PURE__ */ jsxRuntime.jsx(
1679
- DocTitleAnchor,
1680
- {
1681
- id: doc._id,
1682
- type: doc._type,
1683
- structureTool,
1684
- children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled"
1685
- }
1686
- ) }),
1687
- showDocumentId && /* @__PURE__ */ jsxRuntime.jsx(DocId, { children: doc._id }),
1688
- resolvedDocBadge && /* @__PURE__ */ jsxRuntime.jsx(
1689
- DocBadgeRenderer,
1690
- {
1691
- doc,
1692
- docBadge: resolvedDocBadge
1693
- }
1694
- )
1695
- ] }) }) }),
1696
- showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsxRuntime.jsx(TypeText, { children: resolveTypeLabel(doc._type, resolvedTypeLabels) }) : (() => {
1697
- const typeColor = getTypeColor(doc._type);
1698
- return /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, resolvedTypeLabels) });
1699
- })() }),
1700
- /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: /* @__PURE__ */ jsxRuntime.jsxs(ScoreBadge, { $score: doc.health.score, children: [
1701
- doc.health.score,
1702
- "%"
1703
- ] }) }),
1704
- /* @__PURE__ */ jsxRuntime.jsxs(ColIssues, { children: [
1705
- doc.health.issues.slice(0, 2).map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(IssueTag, { children: [
1706
- "\u2022 ",
1707
- issue
1708
- ] }, `issue-${doc._id}-${issue}`)),
1709
- doc.health.issues.length > 2 && /* @__PURE__ */ jsxRuntime.jsx(
1710
- MoreIssuesWrapper,
1711
- {
1712
- onMouseEnter: function(e) {
1713
- handleMouseEnterIssues(
1714
- e.currentTarget,
1715
- doc.health.issues.slice(2)
1716
- );
1717
- },
1718
- onMouseLeave: handleMouseLeave,
1719
- children: /* @__PURE__ */ jsxRuntime.jsxs(MoreIssues, { children: [
1720
- "+",
1721
- doc.health.issues.length - 2,
1722
- " more issues"
1723
- ] })
1724
- }
1725
- )
1726
- ] })
1727
- ] }, doc._id);
1728
- })
1729
- ] }))
1730
- ] }),
1731
- activePopover && /* @__PURE__ */ jsxRuntime.jsx(
1732
- IssuesPopover,
1847
+ /* @__PURE__ */ jsxRuntime.jsxs(
1848
+ StyledSelect,
1849
+ {
1850
+ value: filterStatus,
1851
+ onChange: (e) => handleFilterStatusChange(e.currentTarget.value),
1852
+ children: [
1853
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Status" }),
1854
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "excellent", children: "Excellent" }),
1855
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "good", children: "Good" }),
1856
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "fair", children: "Fair" }),
1857
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "poor", children: "Poor" }),
1858
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "missing", children: "Missing" })
1859
+ ]
1860
+ }
1861
+ ),
1862
+ uniqueDocumentTypes.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(
1863
+ StyledSelect,
1864
+ {
1865
+ value: filterType,
1866
+ onChange: (e) => handleFilterTypeChange(e.currentTarget.value),
1867
+ children: [
1868
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Types" }),
1869
+ uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: resolveTypeLabel(type, resolvedTypeLabels) }, type))
1870
+ ]
1871
+ }
1872
+ ),
1873
+ /* @__PURE__ */ jsxRuntime.jsxs(
1874
+ StyledSelect,
1733
1875
  {
1734
- style: {
1735
- top: activePopover.top,
1736
- left: activePopover.left,
1737
- transform: "translateY(calc(-100% - 10px))"
1738
- },
1739
- children: activePopover.issues.map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(PopoverIssueItem, { children: [
1740
- "\u26A0\uFE0F ",
1741
- issue
1742
- ] }, issue))
1876
+ value: sortBy,
1877
+ onChange: (e) => setSortBy(e.currentTarget.value),
1878
+ children: [
1879
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "score", children: "Sort by Score" }),
1880
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "title", children: "Sort by Title" })
1881
+ ]
1743
1882
  }
1744
1883
  ),
1745
- " "
1884
+ exportEnabled && exportFormats.includes("csv") && /* @__PURE__ */ jsxRuntime.jsx(ExportButton, { onClick: handleExportCSV, title: "Export all filtered as CSV", children: "\u2B07 CSV" }),
1885
+ exportEnabled && exportFormats.includes("json") && /* @__PURE__ */ jsxRuntime.jsx(ExportButton, { onClick: handleExportJSON, title: "Export all filtered as JSON", children: "\u2B07 JSON" })
1746
1886
  ] }),
1747
- " "
1887
+ /* @__PURE__ */ jsxRuntime.jsxs(TableCard, { children: [
1888
+ loading && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { children: [
1889
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
1890
+ loadingDocuments != null ? loadingDocuments : "Loading documents\u2026"
1891
+ ] }),
1892
+ !loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: noDocuments != null ? noDocuments : "No documents found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1893
+ /* @__PURE__ */ jsxRuntime.jsxs(TableHeader, { children: [
1894
+ /* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: "Title" }),
1895
+ showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: "Type" }),
1896
+ /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: "Score" }),
1897
+ /* @__PURE__ */ jsxRuntime.jsx(ColIssues, { children: "Top Issues" })
1898
+ ] }),
1899
+ paginatedDocs.map((doc) => {
1900
+ return /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
1901
+ /* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: /* @__PURE__ */ jsxRuntime.jsx(TitleWrapper, { children: /* @__PURE__ */ jsxRuntime.jsxs(TitleCell, { children: [
1902
+ doc.title !== null && typeof doc.title !== "string" ? /* @__PURE__ */ jsxRuntime.jsx(NonStringTitleWarning, { title: "title is not a string \u2014 use pt::text(title) in your query.groq projection to convert Portable Text to a plain string", children: "\u26A0 title is not a string \u2014 use pt::text(title) in query.groq" }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: openInPane ? /* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled" }) : /* @__PURE__ */ jsxRuntime.jsx(
1903
+ DocTitleAnchor,
1904
+ {
1905
+ id: doc._id,
1906
+ type: doc._type,
1907
+ structureTool,
1908
+ children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled"
1909
+ }
1910
+ ) }),
1911
+ showDocumentId && /* @__PURE__ */ jsxRuntime.jsx(DocId, { children: doc._id }),
1912
+ resolvedDocBadge && /* @__PURE__ */ jsxRuntime.jsx(
1913
+ DocBadgeRenderer,
1914
+ {
1915
+ doc,
1916
+ docBadge: resolvedDocBadge
1917
+ }
1918
+ )
1919
+ ] }) }) }),
1920
+ showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsxRuntime.jsx(TypeText, { children: resolveTypeLabel(doc._type, resolvedTypeLabels) }) : (() => {
1921
+ const typeColor = getTypeColor(doc._type);
1922
+ return /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, resolvedTypeLabels) });
1923
+ })() }),
1924
+ /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: /* @__PURE__ */ jsxRuntime.jsxs(ScoreBadge, { $score: doc.health.score, children: [
1925
+ doc.health.score,
1926
+ "%"
1927
+ ] }) }),
1928
+ /* @__PURE__ */ jsxRuntime.jsxs(ColIssues, { children: [
1929
+ doc.health.issues.slice(0, 2).map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(IssueTag, { children: [
1930
+ "\u2022 ",
1931
+ issue
1932
+ ] }, `issue-${doc._id}-${issue}`)),
1933
+ doc.health.issues.length > 2 && /* @__PURE__ */ jsxRuntime.jsx(
1934
+ MoreIssuesWrapper,
1935
+ {
1936
+ onMouseEnter: function(e) {
1937
+ handleMouseEnterIssues(
1938
+ e.currentTarget,
1939
+ doc.health.issues.slice(2)
1940
+ );
1941
+ },
1942
+ onMouseLeave: handleMouseLeave,
1943
+ children: /* @__PURE__ */ jsxRuntime.jsxs(MoreIssues, { children: [
1944
+ "+",
1945
+ doc.health.issues.length - 2,
1946
+ " more issues"
1947
+ ] })
1948
+ }
1949
+ )
1950
+ ] })
1951
+ ] }, doc._id);
1952
+ }),
1953
+ /* @__PURE__ */ jsxRuntime.jsxs(PaginationBar, { children: [
1954
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1955
+ "Showing ",
1956
+ Math.min((currentPage - 1) * pageSize + 1, filteredAndSortedDocs.length),
1957
+ "\u2013",
1958
+ Math.min(currentPage * pageSize, filteredAndSortedDocs.length),
1959
+ " of",
1960
+ " ",
1961
+ filteredAndSortedDocs.length,
1962
+ " documents"
1963
+ ] }),
1964
+ /* @__PURE__ */ jsxRuntime.jsxs(PaginationCenter, { children: [
1965
+ /* @__PURE__ */ jsxRuntime.jsx(
1966
+ PaginationButton,
1967
+ {
1968
+ disabled: currentPage === 1,
1969
+ onClick: () => setCurrentPage((p) => Math.max(1, p - 1)),
1970
+ title: "Previous page",
1971
+ children: "\u2039"
1972
+ }
1973
+ ),
1974
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1975
+ "Page ",
1976
+ currentPage,
1977
+ " of ",
1978
+ totalPages
1979
+ ] }),
1980
+ /* @__PURE__ */ jsxRuntime.jsx(
1981
+ PaginationButton,
1982
+ {
1983
+ disabled: currentPage >= totalPages,
1984
+ onClick: () => setCurrentPage((p) => Math.min(totalPages, p + 1)),
1985
+ title: "Next page",
1986
+ children: "\u203A"
1987
+ }
1988
+ )
1989
+ ] }),
1990
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1991
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Per page:" }),
1992
+ /* @__PURE__ */ jsxRuntime.jsxs(
1993
+ StyledSelect,
1994
+ {
1995
+ value: pageSize,
1996
+ style: { height: 30, fontSize: 12, padding: "0 28px 0 8px" },
1997
+ onChange: (e) => {
1998
+ const size = Number(e.currentTarget.value);
1999
+ setPageSize(size);
2000
+ try {
2001
+ localStorage.setItem("seo-dashboard-page-size", String(size));
2002
+ } catch (e2) {
2003
+ }
2004
+ setCurrentPage(1);
2005
+ },
2006
+ children: [
2007
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 25, children: "25" }),
2008
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 50, children: "50" }),
2009
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 100, children: "100" }),
2010
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 200, children: "200" })
2011
+ ]
2012
+ }
2013
+ )
2014
+ ] })
2015
+ ] })
2016
+ ] }))
2017
+ ] }),
2018
+ activePopover && /* @__PURE__ */ jsxRuntime.jsx(
2019
+ IssuesPopover,
2020
+ {
2021
+ style: {
2022
+ top: activePopover.top,
2023
+ left: activePopover.left,
2024
+ transform: "translateY(calc(-100% - 10px))"
2025
+ },
2026
+ children: activePopover.issues.map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(PopoverIssueItem, { children: [
2027
+ "\u26A0\uFE0F ",
2028
+ issue
2029
+ ] }, issue))
2030
+ }
2031
+ )
2032
+ ] });
2033
+ return /* @__PURE__ */ jsxRuntime.jsxs(DashboardContainer, { style: currentVars, children: [
2034
+ licenseStatus === "loading" && /* @__PURE__ */ jsxRuntime.jsx(RenderLicenseLoading, { text: loadingLicense }),
2035
+ licenseStatus === "invalid" && /* @__PURE__ */ jsxRuntime.jsx(RenderLicenseInvalid, { licenseKey, validateLicense }),
2036
+ licenseStatus === "valid" && renderDashboardContent()
1748
2037
  ] });
1749
2038
  };
1750
2039
  var SeoHealthDashboard_default = SeoHealthDashboard;
1751
2040
 
1752
2041
  exports.SeoHealthDashboard_default = SeoHealthDashboard_default;
1753
- //# sourceMappingURL=chunk-Z63UP35O.cjs.map
1754
- //# sourceMappingURL=chunk-Z63UP35O.cjs.map
2042
+ //# sourceMappingURL=chunk-G2SVI2SP.cjs.map
2043
+ //# sourceMappingURL=chunk-G2SVI2SP.cjs.map