84 lines
3.8 KiB
TypeScript
84 lines
3.8 KiB
TypeScript
import { useNavigation } from "react-router";
|
|
import clsx from "clsx";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Stat } from "~/application/dtos/stats/Stat";
|
|
import { StatChange } from "~/application/dtos/stats/StatChange";
|
|
import { ArrowUpIcon, ArrowDownIcon } from "~/components/ui/icons/vendor";
|
|
|
|
interface Props {
|
|
items: Stat[];
|
|
}
|
|
|
|
function getStatChangeScreenReaderKey(changeType: StatChange | undefined) {
|
|
if (changeType === StatChange.Increase) {
|
|
return "components.stats.change.increasedBy";
|
|
}
|
|
if (changeType === StatChange.Decrease) {
|
|
return "components.stats.change.decreasedBy";
|
|
}
|
|
return "components.stats.change.noChange";
|
|
}
|
|
|
|
export function EntitiesStats({ items }: Props) {
|
|
const { t } = useTranslation();
|
|
const navigation = useNavigation();
|
|
const loading = navigation.state === "loading";
|
|
return (
|
|
<div>
|
|
<h3 className="text-text-neutral-primary leading-4 font-medium">{t("app.shared.periods.LAST_30_DAYS")}</h3>
|
|
<dl
|
|
className={clsx(
|
|
// Subframe StatsCard layout: rounded-md container (divided-panel structure kept)
|
|
"divide-border-default bg-surface-neutral-primary mt-3 grid grid-cols-1 divide-y overflow-hidden rounded-md shadow-xs md:divide-x md:divide-y-0",
|
|
items.length === 1 && "md:grid-cols-1",
|
|
items.length === 2 && "md:grid-cols-2",
|
|
items.length === 3 && "md:grid-cols-3",
|
|
items.length === 4 && "md:grid-cols-4",
|
|
items.length === 5 && "md:grid-cols-5",
|
|
items.length === 6 && "md:grid-cols-6"
|
|
)}
|
|
>
|
|
{items.map((item) => (
|
|
// Subframe StatsCard layout: px-4 py-4 cell padding, body-default label, subtitle value
|
|
<span key={item.name} className="px-4 py-4">
|
|
<dt className="text-text-neutral-primary text-body-default-regular-0 font-body-default-regular-0 flex items-baseline space-x-1">
|
|
<div>{t(item.name)}</div>
|
|
{item.hint && <div className="text-text-neutral-tertiary text-body-small-regular-0 font-body-small-regular-0 hidden xl:block">({t(item.hint)})</div>}
|
|
</dt>
|
|
<dd className="mt-1 flex items-baseline justify-between md:block lg:flex">
|
|
<div className="text-text-neutral-primary text-subtitle-0 font-subtitle-0 flex items-baseline">
|
|
<div>{loading ? "..." : item.stat}</div>
|
|
<span className="text-text-neutral-tertiary text-body-small-regular-0 font-body-small-regular-0 ml-2 hidden xl:block">
|
|
{!loading && item.previousStat !== undefined && <span>{t("shared.statComparison", { 0: item.previousStat })}</span>}
|
|
</span>
|
|
</div>
|
|
|
|
{!loading && (
|
|
<div
|
|
className={clsx(
|
|
item.changeType === StatChange.Increase && "text-teal-800",
|
|
item.changeType === StatChange.Decrease && "text-rose-800",
|
|
item.changeType === StatChange.Equal && "text-text-neutral-tertiary",
|
|
"text-body-small-regular-0 font-body-small-regular-0 inline-flex items-baseline rounded-full px-2.5 py-0.5 md:mt-2 lg:mt-0"
|
|
)}
|
|
>
|
|
{item.changeType === StatChange.Increase ? (
|
|
<ArrowUpIcon className="mr-0.5 -ml-1 h-5 w-5 shrink-0 self-center text-teal-500" />
|
|
) : item.changeType === StatChange.Decrease ? (
|
|
<ArrowDownIcon className="mr-0.5 -ml-1 h-5 w-5 shrink-0 self-center text-rose-500" />
|
|
) : (
|
|
<div className="text-text-neutral-tertiary">-</div>
|
|
)}
|
|
|
|
<span className="sr-only">{t(getStatChangeScreenReaderKey(item.changeType))}</span>
|
|
{item.change}
|
|
</div>
|
|
)}
|
|
</dd>
|
|
</span>
|
|
))}
|
|
</dl>
|
|
</div>
|
|
);
|
|
}
|