PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / faq-builder / components / FaqStats.js

FaqStats.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/faq-builder/components/FaqStats.js

153 lines 5.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useEffect, useState } from "react";
2 import { __ } from "@wordpress/i18n";
3 import apiFetch from "@wordpress/api-fetch";
4 import { useFAQ } from "../../components/FAQContext";
5 import { useFAQScope } from "../context/ScopeContext";
6
7 /**
8 * FAQ Builder summary stat cards (FAQ Groups / Total FAQs / Published / Drafts).
9 *
10 * Design-only addition: the counts are derived from data the app already has —
11 * `groupsTotal` is the FAQ-category total, and the FAQ publish/draft split comes
12 * from the existing `docs-faq-count` REST endpoint (no new functionality).
13 */
14
15 // Stroke icons matching the standalone design mockup.
16 const Icon = ({ size = 20, children }) => (
17 <svg
18 width={size}
19 height={size}
20 viewBox="0 0 24 24"
21 fill="none"
22 stroke="currentColor"
23 strokeWidth={1.7}
24 strokeLinecap="round"
25 strokeLinejoin="round"
26 >
27 {children}
28 </svg>
29 );
30
31 const IconBook = () => (
32 <Icon>
33 <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5v14zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5" />
34 </Icon>
35 );
36 const IconFAQ = () => (
37 <Icon>
38 <path d="M21 11.5a8.38 8.38 0 0 1-8.5 8.5 8.5 8.5 0 0 1-3.8-.9L3 21l1.9-5.7A8.5 8.5 0 1 1 21 11.5z" />
39 <path d="M9.5 9a2.5 2.5 0 0 1 5 .25c0 1.5-2.5 2-2.5 3.75M12 16.5h.01" />
40 </Icon>
41 );
42 const IconCheck = () => (
43 <Icon>
44 <path d="M5 12l5 5L20 7" />
45 </Icon>
46 );
47 const IconEdit = () => (
48 <Icon>
49 <path d="M12 20h9M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
50 </Icon>
51 );
52
53 // Soft blurred-triangle glow in the top-left corner — the exact SVG the dashboard
54 // overview cards (`.betterdocs-card-wrapper.created-docs`) use, with this card's
55 // tint injected as the gradient start color so the FAQ cards share that design
56 // system (132×88, opacity 0.2, positioned `left top`).
57 const glowSvg = (tint) =>
58 `url("data:image/svg+xml,%3Csvg width='132' height='88' viewBox='0 0 132 88' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg opacity='0.2' filter='url(%23bdFaqBlur)'%3E%3Cpath d='M96 -3.50001L-0.750021 52.3586L-0.750016 -59.3586L96 -3.50001Z' fill='url(%23bdFaqGrad)'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='bdFaqBlur' x='-35.9468' y='-94.5562' width='167.144' height='182.112' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeBlend mode='normal' in='SourceGraphic' in2='BackgroundImageFix' result='shape'/%3E%3CfeGaussianBlur stdDeviation='17.5984' result='effect1_foregroundBlur'/%3E%3C/filter%3E%3ClinearGradient id='bdFaqGrad' x1='96' y1='-3.50001' x2='-33' y2='-3.50001' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='${encodeURIComponent(
59 tint
60 )}'/%3E%3Cstop offset='1' stop-color='%23FFE600' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E")`;
61
62 // "#00B884" -> "0, 184, 132" for rgba(var(--rgb), a) backgrounds.
63 const hexToRgb = (hex) => {
64 const h = String(hex).replace("#", "");
65 return `${parseInt(h.slice(0, 2), 16)}, ${parseInt(h.slice(2, 4), 16)}, ${parseInt(h.slice(4, 6), 16)}`;
66 };
67
68 const StatCard = ({ value, label, tint, icon }) => (
69 <div
70 className="bd-faq-stat-card"
71 style={{ backgroundImage: glowSvg(tint) }}
72 >
73 <div className="bd-faq-stat-card__body">
74 <h3 className="bd-faq-stat-card__value">{value}</h3>
75 <p className="bd-faq-stat-card__label">{label}</p>
76 </div>
77 <span
78 className="bd-faq-stat-card__icon"
79 style={{ "--bd-icon-tint": tint, "--bd-icon-tint-rgb": hexToRgb(tint) }}
80 >
81 {icon}
82 </span>
83 </div>
84 );
85
86 const FaqStats = ({ groupsTotal = 0, refreshSignal }) => {
87 const [counts, setCounts] = useState({});
88 const { statsVersion } = useFAQ();
89 const { isProduct } = useFAQScope();
90
91 // Re-derive whenever the FAQ list changes (add / delete / draft toggle all
92 // run the builder's category refetch, which updates `refreshSignal`), so the
93 // stat cards stay live with the rest of the page.
94 useEffect(() => {
95 let active = true;
96 apiFetch({ path: "/betterdocs/v1/docs-faq-count" })
97 .then((res) => {
98 if (active && res) {
99 setCounts(res);
100 }
101 })
102 .catch(() => {});
103 return () => {
104 active = false;
105 };
106 }, [refreshSignal, statsVersion]);
107
108 const createdKey = isProduct ? "created_product_faq" : "created_faq";
109 const publishedKey = isProduct ? "published_product_faq" : "published_faq";
110 const total = parseInt(counts[createdKey], 10) || 0;
111 const published = parseInt(counts[publishedKey], 10) || 0;
112 const drafts = Math.max(total - published, 0);
113 // Real FAQ groups only. "Uncategorized" is not a group — it's the bucket for FAQs
114 // that have none — so counting it made the card disagree with the group list (e.g.
115 // "3 Groups" next to 2 groups + Uncategorized). The FAQs inside it are still counted
116 // by the Total / Published / Drafts cards.
117 const groups = parseInt(groupsTotal, 10) || 0;
118
119 return (
120 <div className="bd-faq-stats">
121 <StatCard
122 value={groups}
123 label={__(
124 isProduct ? "Product FAQ Groups" : "General FAQ Groups",
125 "betterdocs"
126 )}
127 tint="#00B884"
128 icon={<IconBook />}
129 />
130 <StatCard
131 value={total}
132 label={__("Total FAQs", "betterdocs")}
133 tint="#F59E0B"
134 icon={<IconFAQ />}
135 />
136 <StatCard
137 value={published}
138 label={__("Published", "betterdocs")}
139 tint="#3B82F6"
140 icon={<IconCheck />}
141 />
142 <StatCard
143 value={drafts}
144 label={__("Drafts", "betterdocs")}
145 tint="#8B5CF6"
146 icon={<IconEdit />}
147 />
148 </div>
149 );
150 };
151
152 export default FaqStats;
153