175 lines
6.2 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|