import { Link } from "react-router"; import { t as i18nT } from "i18next"; import clsx from "clsx"; import { lazy, Suspense, useEffect, useId, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import UrlUtils from "~/utils/app/UrlUtils"; import { useRootData } from "~/utils/data/useRootData"; import type { AnalyticsOverviewDto } from "~/utils/helpers/.server/AnalyticsService"; import { useLocaleFormatters } from "~/locale/format"; import { Card } from "../ui/card"; import { Label } from "../ui/label"; const AnalyticsOverviewChart = lazy(() => import("./AnalyticsOverviewChart")); function joinRootUrl(rootUrl: string | undefined, path: string) { if (!rootUrl) { return path; } return `${rootUrl.replace(/\/+$/g, "")}/${path.replace(/^\/+/g, "")}`; } const MetricCard = ({ label, value }: { label: string; value: string | number }) => { const formatters = useLocaleFormatters(); return (
{formatters.number(Number(value), { maximumFractionDigits: 0 })}
); }; const Metrics = ({ authenticated, overview, rootUrl }: { authenticated: Boolean; overview: AnalyticsOverviewDto; rootUrl?: string }) => { const { t } = useTranslation(); return (
{authenticated ? ( <> ) : ( <> )}
); }; interface TopItemDto { title: string; items: { name: string | null; count: number; image?: any }[]; viewMoreRoute?: string; fallbackName?: string; bgClassName?: string; tabTitle?: string; } function TopItemsData({ title, items, viewMoreRoute, fallbackName, bgClassName }: TopItemDto) { const { authenticated } = useRootData(); const formatters = useLocaleFormatters(); const maxCount = useMemo(() => Math.max(0, ...items.map((item) => item.count)), [items]); function getWidthPercentageCss(current: { count: number }) { const percentage = maxCount > 0 ? (current.count / maxCount) * 100 : 0; if (percentage >= 95) { return "w-[95%]"; } else if (percentage >= 90) { return "w-[90%]"; } else if (percentage >= 80) { return "w-[80%]"; } else if (percentage >= 70) { return "w-[70%]"; } else if (percentage >= 60) { return "w-[60%]"; } else if (percentage >= 50) { return "w-[50%]"; } else if (percentage >= 40) { return "w-[40%]"; } else if (percentage >= 30) { return "w-[30%]"; } else if (percentage >= 20) { return "w-[20%]"; } else if (percentage >= 10) { return "w-[10%]"; } else if (percentage >= 3) { return "w-[3%]"; } return "w-[3%]"; } const renderTopItems = () => { return items.map((item, idx) => (
{item.image !== undefined &&
{item.image}
} {fallbackName ? {!item.name ? fallbackName : item.name} :
{item.name}
}
{formatters.number(item.count, { maximumFractionDigits: 0 })}
)); }; return ( <>
{renderTopItems()}
{viewMoreRoute && authenticated && ( {i18nT("analytics.viewMore")} )} ); } function TopItems({ tabs }: { tabs: TopItemDto[] }) { const [selectedTab, setSelectedTab] = useState(0); const tab = tabs[selectedTab] ?? tabs[0]; const baseId = useId(); useEffect(() => { if (selectedTab >= tabs.length) { setSelectedTab(0); } }, [selectedTab, tabs.length]); function selectAdjacentTab(offset: number) { setSelectedTab((current) => (current + offset + tabs.length) % tabs.length); } return (

{tab.title}

{tabs.length > 1 && (
{tabs.map((item, idx) => { const selected = selectedTab === idx; return ( ); })}
)}
1 ? "tabpanel" : undefined} aria-labelledby={tabs.length > 1 ? `${baseId}-tab-${selectedTab}` : undefined}>
); } export default function AnalyticsOverview({ overview, withUsers, rootUrl }: { overview: AnalyticsOverviewDto; withUsers: boolean; rootUrl?: string }) { const { authenticated } = useRootData(); const { t } = useTranslation(); return (
{overview.charts && ( }> )}
{ return { image: !item.name || item.name === "Direct" ? ( ) : item.name.startsWith("localhost") ? ( ) : ( {item.name} ), name: UrlUtils.stripTrailingSlash(item.name?.replace("https://", "").replace("http://", "").replace("www.", "") ?? ""), count: item.count, }; }), fallbackName: t("analytics.direct"), bgClassName: "bg-orange-50 dark:bg-orange-900 dark:text-white", }, ]} /> {withUsers && ( )}
); }