itmpwk.ac.id/resources/js/components/data-cards.tsx

175 lines
6.2 KiB
TypeScript

import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from 'lucide-react';
import * as React from 'react';
import type { PaginationState } from '@/components/data-table';
import { useDebounce } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
export type { PaginationState } from '@/components/data-table';
interface DataCardsProps<TData> {
data: TData[];
renderCard: (item: TData, index: number) => React.ReactNode;
getRowId?: (item: TData) => string | number;
searchKey?: string;
searchPlaceholder?: string;
emptyText?: string;
toolbar?: React.ReactNode;
pagination: PaginationState;
onPageChange: (page: number) => void;
onPerPageChange?: (perPage: number) => void;
onSearchChange?: (search: string) => void;
searchValue?: string;
}
export function DataCards<TData>({
data,
renderCard,
getRowId,
searchKey,
searchPlaceholder = 'Ketikkan sesuatu...',
emptyText = 'Tidak ada data.',
toolbar,
pagination,
onPageChange,
onPerPageChange,
onSearchChange,
searchValue,
}: DataCardsProps<TData>) {
const [localSearch, setLocalSearch] = React.useState(searchValue ?? '');
const [previousSearchValue, setPreviousSearchValue] =
React.useState(searchValue);
if (searchValue !== previousSearchValue) {
setPreviousSearchValue(searchValue);
setLocalSearch(searchValue ?? '');
}
const handleSearchDebounced = useDebounce(
(value: string) => onSearchChange?.(value),
300,
);
function handleSearchChange(value: string) {
setLocalSearch(value);
handleSearchDebounced(value);
}
const currentPage = pagination.current_page;
const totalPages = pagination.last_page;
return (
<>
{(searchKey || toolbar || onPerPageChange) && (
<div className="flex items-center gap-2">
{searchKey && (
<Input
placeholder={searchPlaceholder}
value={localSearch}
onChange={(event) =>
handleSearchChange(event.target.value)
}
className="max-w-sm"
/>
)}
{toolbar}
<div className="ml-auto flex items-center gap-2">
{onPerPageChange && (
<Select
value={String(pagination.per_page)}
onValueChange={(value) =>
onPerPageChange(Number(value))
}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
<SelectItem value="999999">
Semua
</SelectItem>
</SelectContent>
</Select>
)}
</div>
</div>
)}
{data.length ? (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{data.map((item, index) => (
<Card
key={getRowId ? getRowId(item) : index}
className="overflow-hidden"
>
<CardContent>{renderCard(item, index)}</CardContent>
</Card>
))}
</div>
) : (
<Card>
<CardContent className="flex h-24 items-center justify-center text-center text-muted-foreground">
{emptyText}
</CardContent>
</Card>
)}
<div className="flex items-center justify-end gap-2">
<span className="text-sm text-muted-foreground">
{`Halaman ${currentPage} dari ${totalPages}`}
</span>
<div className="flex items-center gap-1">
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(1)}
disabled={currentPage <= 1}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage <= 1}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage >= totalPages}
>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(totalPages)}
disabled={currentPage >= totalPages}
>
<ChevronsRight className="h-4 w-4" />
</Button>
</div>
</div>
</>
);
}