Files
dashboard/src/components/ui/NoResults.tsx
sakuradairong f2fc11b89e feat(i18n): localize Peers, Access Control, Groups modules to Chinese
- Expand en.ts/zh.ts with extensive translation keys for all modules
- Localize Peers table, peer detail page, peer action cells
- Localize Access Control table, modal, action cells
- Localize Groups table, action cells, main page
- Add common helpers (GroupsRow, NoPeersGettingStarted) translations

Continuation of the localization effort.
2026-06-18 21:22:45 +08:00

111 lines
3.2 KiB
TypeScript

import Button from "@components/Button";
import Paragraph from "@components/Paragraph";
import { cn } from "@utils/helpers";
import { FilterX } from "lucide-react";
import { useTranslations } from 'next-intl';
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import React, { useCallback } from "react";
import Skeleton from "react-loading-skeleton";
import SquareIcon from "@components/SquareIcon";
type Props = {
icon?: React.ReactNode;
title?: string;
description?: string;
children?: React.ReactNode;
className?: string;
hasFiltersApplied?: boolean;
onResetFilters?: () => void;
contentClassName?: string;
};
export default function NoResults({
icon,
title,
description,
children,
className,
hasFiltersApplied = false,
onResetFilters,
contentClassName,
}: Props) {
const t = useTranslations('table');
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const defaultTitle = title || t('noResults');
const defaultDescription = description || t('noResultsDescription');
const handleResetClick = useCallback(() => {
if (onResetFilters) {
onResetFilters();
const params = new URLSearchParams();
const page_size = searchParams.get("page_size");
params.set("page", "1");
if (page_size) {
params.set("page_size", page_size);
}
const newUrl = `${pathname}?${params.toString()}`;
router.push(newUrl);
}
}, [onResetFilters, router, pathname, searchParams]);
return (
<div className={cn("relative overflow-hidden", className)}>
<div
className={
"absolute z-20 bg-gradient-to-b dark:to-nb-gray-950 dark:from-nb-gray-950/50 w-full h-full overflow-hidden top-0"
}
></div>
<div
className={
"absolute w-full h-full left-0 top-0 z-10 px-5 overflow-hidden py-4"
}
>
<div className={"flex flex-col gap-2"}>
<Skeleton className={"w-full"} height={70} duration={4} />
<Skeleton className={"w-full"} height={70} duration={4} />
<Skeleton className={"w-full"} height={70} duration={4} />
<Skeleton className={"w-full"} height={70} duration={4} />
<Skeleton className={"w-full"} height={70} duration={4} />
</div>
</div>
<div
className={cn("max-w-md mx-auto relative z-20 py-6", contentClassName)}
>
<div className={"flex items-center justify-center mb-6"}>
<SquareIcon
icon={icon ? icon : <FilterX size={24} />}
color={"gray"}
size={"large"}
/>
</div>
<div className={"text-center"}>
<h1 className={"text-2xl font-medium max-w-lg mx-auto"}>{defaultTitle}</h1>
<Paragraph className={"justify-center my-2 !text-nb-gray-400"}>
{defaultDescription}
</Paragraph>
{hasFiltersApplied && onResetFilters && (
<Button
onClick={handleResetClick}
variant="secondary"
className="mt-4"
>
<FilterX size={16} />
{t('resetFilters')}
</Button>
)}
{children}
</div>
</div>
</div>
);
}