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") ? (
) : (
),
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 && (
)}
);
}