From 126505fc888c3791f9185b7662e2c507a43cae80 Mon Sep 17 00:00:00 2001 From: bernard-ng Date: Tue, 18 Nov 2025 13:48:34 +0200 Subject: [PATCH] feat(dashboard): add reports --- apps/api/src/trpc/routers/_app.ts | 2 + apps/api/src/trpc/routers/reports.ts | 9 + apps/crawler/src/http/user-agent.ts | 2 +- apps/crawler/src/process/parsers/wordpress.ts | 12 +- apps/dashboard/package.json | 10 +- .../(app)/(sidebar)/articles/page.tsx | 6 +- .../(app)/(sidebar)/dashboard/page.tsx | 7 +- .../app/[locale]/(app)/(sidebar)/layout.tsx | 6 +- .../(app)/(sidebar)/sources/[id]/page.tsx | 2 +- .../[locale]/(app)/(sidebar)/sources/page.tsx | 4 +- .../src/app/[locale]/(public)/login/page.tsx | 4 +- .../src/components/charts/chart-filters.tsx | 82 +++++++--- .../components/dashboard-overview-card.tsx | 72 ++++++++ .../src/components/forms/login-form.tsx | 15 +- .../src/components/shell/page-header.tsx | 31 +++- .../src/components/shell/page-layout.tsx | 26 +-- apps/dashboard/src/components/shell/show.tsx | 22 +++ .../sidebar/app-sidebar-content.tsx | 41 +++-- .../components/sidebar/app-sidebar-info.tsx | 15 +- .../src/components/sidebar/app-sidebar.tsx | 4 +- apps/dashboard/tsconfig.json | 1 + bun.lock | 154 +++++++++++++++--- packages/db/src/queries/articles.ts | 16 +- packages/db/src/queries/index.ts | 1 + packages/db/src/queries/reports.ts | 83 ++++++++++ packages/db/src/queries/sources.ts | 6 +- packages/db/src/synchronizers/data.ts | 26 ++- packages/db/src/synchronizers/tokens.ts | 4 +- packages/db/src/utils/filters.ts | 27 ++- packages/domain/src/models/index.ts | 1 + packages/domain/src/models/reports.ts | 30 ++++ packages/ui/package.json | 2 +- 32 files changed, 553 insertions(+), 170 deletions(-) create mode 100644 apps/api/src/trpc/routers/reports.ts create mode 100644 apps/dashboard/src/components/dashboard-overview-card.tsx create mode 100644 apps/dashboard/src/components/shell/show.tsx create mode 100644 packages/db/src/queries/reports.ts create mode 100644 packages/domain/src/models/reports.ts diff --git a/apps/api/src/trpc/routers/_app.ts b/apps/api/src/trpc/routers/_app.ts index a6e97d4..e9a2083 100644 --- a/apps/api/src/trpc/routers/_app.ts +++ b/apps/api/src/trpc/routers/_app.ts @@ -3,11 +3,13 @@ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; import { createTRPCRouter } from "#api/trpc/init"; import { articlesRouter } from "#api/trpc/routers/articles"; import { authRouter } from "#api/trpc/routers/auth"; +import { reportsRouter } from "#api/trpc/routers/reports.js"; import { sourcesRouter } from "#api/trpc/routers/sources"; export const appRouter = createTRPCRouter({ articles: articlesRouter, auth: authRouter, + reports: reportsRouter, sources: sourcesRouter, }); diff --git a/apps/api/src/trpc/routers/reports.ts b/apps/api/src/trpc/routers/reports.ts new file mode 100644 index 0000000..f78be9a --- /dev/null +++ b/apps/api/src/trpc/routers/reports.ts @@ -0,0 +1,9 @@ +import { getDashboardOverview } from "@basango/db/queries"; + +import { createTRPCRouter, protectedProcedure } from "#api/trpc/init"; + +export const reportsRouter = createTRPCRouter({ + getDashboardOverview: protectedProcedure.query(async ({ ctx }) => { + return getDashboardOverview(ctx.db); + }), +}); diff --git a/apps/crawler/src/http/user-agent.ts b/apps/crawler/src/http/user-agent.ts index 3027e0c..189d9e1 100644 --- a/apps/crawler/src/http/user-agent.ts +++ b/apps/crawler/src/http/user-agent.ts @@ -8,7 +8,7 @@ import { DEFAULT_OPEN_GRAPH_USER_AGENT, DEFAULT_USER_AGENT } from "@basango/doma * @author Bernard Ngandu */ export class UserAgents { - private static readonly USER_AGENTS: string[] = [ + public static readonly USER_AGENTS: string[] = [ "Mozilla/5.0 (iPhone; CPU iPhone OS 10_4_8; like Mac OS X) AppleWebKit/603.39 (KHTML, like Gecko) Chrome/52.0.3638.271 Mobile Safari/537.5", "Mozilla/50.0 (Linux; U; Linux x86_64; en-US) Gecko/20130401 Firefox/52.7", "Mozilla/5.0 (Linux; U; Android 5.0; SM-P815 Build/LRX22G) AppleWebKit/600.4 (KHTML, like Gecko) Chrome/48.0.1562.260 Mobile Safari/600.0", diff --git a/apps/crawler/src/process/parsers/wordpress.ts b/apps/crawler/src/process/parsers/wordpress.ts index a7a2584..5a56248 100644 --- a/apps/crawler/src/process/parsers/wordpress.ts +++ b/apps/crawler/src/process/parsers/wordpress.ts @@ -36,12 +36,12 @@ export class WordPressCrawler extends BaseCrawler { readonly source: WordPressSourceConfig; private categoryMap: Map = new Map(); - private static readonly POST_QUERY = + public static readonly POST_QUERY = "_fields=date,slug,link,title.rendered,content.rendered,categories&orderby=date&order=desc"; - private static readonly CATEGORY_QUERY = + public static readonly CATEGORY_QUERY = "_fields=id,slug,count&orderby=count&order=desc&per_page=100"; - private static readonly TOTAL_PAGES_HEADER = "x-wp-totalpages"; - private static readonly TOTAL_POSTS_HEADER = "x-wp-total"; + public static readonly TOTAL_PAGES_HEADER = "x-wp-totalpages"; + public static readonly TOTAL_POSTS_HEADER = "x-wp-total"; constructor(settings: FetchCrawlerConfig, options: { persistors?: Persistor[] } = {}) { super(settings, options); @@ -196,7 +196,9 @@ export class WordPressCrawler extends BaseCrawler { * @param page - Page number */ buildEndpointUrl(page: number): string { - return `${this.baseUrl()}wp-json/wp/v2/posts?${WordPressCrawler.POST_QUERY}&page=${page}&per_page=100`; + return `${this.baseUrl()}wp-json/wp/v2/posts?${ + WordPressCrawler.POST_QUERY + }&page=${page}&per_page=100`; } /** diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index b8428b9..3ef8656 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -5,15 +5,23 @@ "@basango/ui": "workspace:*", "@date-fns/tz": "^1.4.1", "@hookform/resolvers": "^5.2.2", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.90.8", "@tanstack/react-table": "^8.21.3", "@trpc/client": "^11.7.1", "@trpc/react-query": "^11.7.1", "@trpc/server": "^11.7.1", "@trpc/tanstack-react-query": "^11.7.1", + "class-variance-authority": "^0.7.1", "client-only": "^0.0.1", "date-fns": "catalog:", - "lucide-react": "^0.553.0", + "lucide-react": "^0.554.0", "next": "catalog:", "next-international": "^1.3.1", "next-themes": "^0.4.6", diff --git a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx index 786b089..a606868 100644 --- a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx +++ b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx @@ -2,18 +2,18 @@ import { Metadata } from "next"; import { ArticlesFeed } from "#dashboard/components/articles-feed"; import { PageLayout } from "#dashboard/components/shell/page-layout"; -import { HydrateClient, batchPrefetch, trpc } from "#dashboard/trpc/server"; +import { HydrateClient, prefetch, trpc } from "#dashboard/trpc/server"; export const metadata: Metadata = { title: "Articles | Basango Dashboard", }; export default function Page() { - batchPrefetch([trpc.articles.list.infiniteQueryOptions({ limit: 12 })]); + prefetch(trpc.articles.list.infiniteQueryOptions({ limit: 12 })); return ( - + diff --git a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/dashboard/page.tsx b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/dashboard/page.tsx index 535d75a..3fa053c 100644 --- a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/dashboard/page.tsx +++ b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/dashboard/page.tsx @@ -2,6 +2,7 @@ import { Metadata } from "next"; import { PublicationGraphChart } from "#dashboard/components/charts/articles/publication-graph-chart"; import { SourceDistributionChart } from "#dashboard/components/charts/articles/source-distribution-chart"; +import { DashboardOverviewCard } from "#dashboard/components/dashboard-overview-card"; import { PageLayout } from "#dashboard/components/shell/page-layout"; import { HydrateClient, batchPrefetch, trpc } from "#dashboard/trpc/server"; @@ -11,15 +12,17 @@ export const metadata: Metadata = { export default async function Page() { batchPrefetch([ + trpc.reports.getDashboardOverview.queryOptions(), trpc.articles.getPublications.queryOptions({}), trpc.articles.getSourceDistribution.queryOptions({ limit: 8 }), ]); return ( - +
-
+
+
diff --git a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/layout.tsx b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/layout.tsx index 41e9cef..9db22c3 100644 --- a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/layout.tsx +++ b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/layout.tsx @@ -1,6 +1,5 @@ import { SidebarInset, SidebarProvider } from "@basango/ui/components/sidebar"; -import { PageHeader } from "#dashboard/components/shell/page-header"; import { AppSidebar } from "#dashboard/components/sidebar/app-sidebar"; import { HydrateClient } from "#dashboard/trpc/server"; @@ -10,10 +9,7 @@ export default async function Layout({ children }: { children: React.ReactNode } - - - {children} - + {children} ); diff --git a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/[id]/page.tsx b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/[id]/page.tsx index 548ba5c..8f02d6d 100644 --- a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/[id]/page.tsx +++ b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/[id]/page.tsx @@ -27,7 +27,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }> return ( - + Overview diff --git a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/page.tsx b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/page.tsx index 6af136c..53ee213 100644 --- a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/page.tsx +++ b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/sources/page.tsx @@ -23,8 +23,8 @@ export default async function Page() { return ( - -
+ +
-
+
verification placeholder diff --git a/apps/dashboard/src/components/charts/chart-filters.tsx b/apps/dashboard/src/components/charts/chart-filters.tsx index a92b57b..88c501b 100644 --- a/apps/dashboard/src/components/charts/chart-filters.tsx +++ b/apps/dashboard/src/components/charts/chart-filters.tsx @@ -10,7 +10,6 @@ import { SelectTrigger, SelectValue, } from "@basango/ui/components/select"; -import { ToggleGroup, ToggleGroupItem } from "@basango/ui/components/toggle-group"; import { differenceInCalendarDays, format, subDays } from "date-fns"; import { CalendarIcon, ChevronDown } from "lucide-react"; import { parseAsInteger, parseAsIsoDate, useQueryStates } from "nuqs"; @@ -136,8 +135,10 @@ export function ChartPeriodPicker({ return match ? String(match.value) : "custom"; }, [calendarRange, options]); - const handlePresetChange = (value: string) => { - if (value === "custom") { + const presetValue = selectValue === "custom" ? undefined : selectValue; + + const handlePresetChange = (value?: string) => { + if (!value || value === "custom") { return; } @@ -149,6 +150,10 @@ export function ChartPeriodPicker({ }); }; + const handlePresetClick = (value: string) => { + handlePresetChange(value); + }; + const handleCalendarSelect = (value: DateRange | undefined) => { if (value?.from && value?.to) { setState({ @@ -181,29 +186,56 @@ export function ChartPeriodPicker({ - - + +
+
+
+ +
+
+
+ {options.map((option) => { + const isActive = presetValue === String(option.value); - - -
+ return ( + + ); + })} +
+
+ +
+
+