diff --git a/dashboard/web/src/pages/Client.jsx b/dashboard/web/src/pages/Client.jsx
index e0ae0ef..5a77355 100644
--- a/dashboard/web/src/pages/Client.jsx
+++ b/dashboard/web/src/pages/Client.jsx
@@ -101,7 +101,8 @@ export default function Client() {
onChange={setTab}
tabs={[
{ value: "performance", label: "Performance" },
- { value: "docs", label: "Documents", count: data.docs.filter((d) => d.kind !== "setup").length },
+ { value: "docs", label: "Documents", count: data.docs.filter((d) => d.kind !== "setup" && d.kind !== "competitors").length },
+ { value: "competitors", label: "Competitor analysis", count: data.docs.filter((d) => d.kind === "competitors").length },
{ value: "runs", label: "AI runs", count: data.jobs.length },
{ value: "shots", label: "Screenshots" },
{ value: "brand", label: "Brand kit" },
@@ -120,7 +121,8 @@ export default function Client() {
))}
- {tab === "docs" &&
}
+ {tab === "docs" && d.kind !== "competitors")} reload={load} openPath={params.get("doc")} />
}
+ {tab === "competitors" && d.kind === "competitors")} reload={load} openPath={params.get("doc")} onStarted={(job) => nav(`/runs/${job.id}`)} />}
{tab === "runs" && }
{tab === "shots" && }
{tab === "brand" && }
@@ -390,7 +392,41 @@ function FormatBars({ d }) {
// ---------------------------------------------------------------- documents
const KIND_LABEL = { monthly: "Monthly reports", audit: "Audits", competitors: "Competitor analysis", trends: "Trend scans", report: "Weekly reports", calendar: "Content calendars", "profile-check": "Profile checks", setup: "Client setup files" };
-function Documents({ slug, docs, reload, openPath }) {
+function CompetitorReports({ slug, docs, reload, openPath, onStarted }) {
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState(null);
+ const scan = async () => {
+ setBusy(true);
+ setError(null);
+ try {
+ onStarted((await api("/api/jobs", { method: "POST", body: { type: "competitor-scan", slug } })).job);
+ } catch (e) {
+ setError(e);
+ setBusy(false);
+ }
+ };
+ return (
+
+
+ Tracked accounts
+ Find competitors
+ }
+ >
+
+ Each run finds this client's local rivals and niche benchmarks, checks them on Instagram and Google, and rewrites this month's report.
+ Add or dismiss the accounts it suggests under Tracked accounts.
+
+
+
+ Click Find competitors to create the first one.} />
+
+ );
+}
+
+function Documents({ slug, docs, reload, openPath, empty }) {
const [preview, setPreview] = useState(null);
const [busy, setBusy] = useState(null);
const [error, setError] = useState(null);
@@ -424,7 +460,7 @@ function Documents({ slug, docs, reload, openPath }) {
};
const groups = Object.keys(KIND_LABEL).map((k) => [k, docs.filter((d) => d.kind === k)]).filter(([, list]) => list.length);
- if (!docs.length) return Run an account audit to create the first ones. ;
+ if (!docs.length) return {empty || Run an account audit to create the first ones. } ;
return (
diff --git a/dashboard/web/src/pages/Competitors.jsx b/dashboard/web/src/pages/Competitors.jsx
index e8afe49..8ab17e7 100644
--- a/dashboard/web/src/pages/Competitors.jsx
+++ b/dashboard/web/src/pages/Competitors.jsx
@@ -199,7 +199,7 @@ export default function Competitors() {
setParams({ client: e.target.value })}>
{clients.map((c) => {c.name} )}
- {data?.report && navigate(`/clients/${client}?tab=docs&doc=${encodeURIComponent(data.report.path)}`)}>Open report }
+ {data?.report && navigate(`/clients/${client}?tab=competitors&doc=${encodeURIComponent(data.report.path)}`)}>Open report }
{data?.competitors.length > 0 && Refresh }
Find competitors
setAdding(true)} disabled={data?.competitors.length >= 10}>Add competitor