Skip to content

Commit

Permalink
clean up crud interface
Browse files Browse the repository at this point in the history
  • Loading branch information
liwoo committed Oct 18, 2024
1 parent 1da3e98 commit 342ceca
Show file tree
Hide file tree
Showing 34 changed files with 1,428 additions and 2,113 deletions.
31 changes: 31 additions & 0 deletions app/@types/contributors.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Identifiable } from '.';

export interface TPartialContributor extends Identifiable {
id: string;
firstName: string;
lastName: string;
email: string;
gender: string;
dependent: boolean;
contributionAmount: number;
contributiionMethod: 'monthly' | 'annual';
dateJoined: Date;
}

export interface TContributor extends Identifiable {
id: string;
firstName: string;
lastName: string;
email: string;
gender: string;
dependent: boolean;
contributionAmount: number;
contributiionMethod: 'monthly' | 'annual';
dateJoined: Date;
}

interface ContributorsDetailsProps {
title: string;
value: string;
color: string;
}
34 changes: 14 additions & 20 deletions app/@types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import { LucideIcon } from "lucide-react";
import { LucideIcon } from 'lucide-react';

interface NavItemProps {
label: string;
icon: LucideIcon;
href: string;
children?: NavItemProps[];
export interface Identifiable {
id: string;
}

export interface TContributors {
id: string;
firstName: string;
lastName: string;
email: string;
gender: string;
dependent: boolean;
contributionAmount: number;
contributiionMethod: "monthly" | "annual";
dateJoined: Date;
interface NavItemProps {
label: string;
icon: LucideIcon;
href: string;
children?: NavItemProps[];
}

interface ContributorsDetailsProps {
title: string;
value: string;
color: string;
export interface ICrudService<T extends Identifiable> {
getAll(): Promise<T[]>;
getById(id: string): Promise<T>;
create(item: Omit<T, 'id'>): Promise<T>;
update(id: string, item: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
1 change: 1 addition & 0 deletions app/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Welcome to Auth
2 changes: 1 addition & 1 deletion app/components/atoms/contributors/detail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContributorsSummaryData } from '@/data/contributors';
import { ContributorsSummaryData } from '@/data/contributors/contributors-summary';

export const ContributorsDetails = () => {
return (
Expand Down
44 changes: 44 additions & 0 deletions app/components/blocks/crud-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// /components/CrudList.tsx

import React from 'react';
import { Sheet, SheetContent } from '@/components/ui/sheet';
import { useNavigate, useParams, useLocation, Outlet } from '@remix-run/react';
import { ICrudService, Identifiable } from '@/@types';
import { DataTable } from './data-table';
import { ColumnDef } from '@tanstack/react-table';

interface CrudListProps<T extends Identifiable> {
columns: ColumnDef<T, any>[];
data: T[];
baseRoute: string;
}

export function CrudList<
T extends Identifiable,
TM extends Identifiable,
TS extends ICrudService<TM>
>({ columns, data, baseRoute }: CrudListProps<T>) {
const navigate = useNavigate();
const location = useLocation();
const { id } = useParams();

const isSheetOpen =
location.pathname.includes('/create') ||
location.pathname.includes('/edit') ||
location.pathname.includes('/view');

const handleClose = () => {
navigate(baseRoute);
};

return (
<div>
<DataTable columns={columns} data={data} />
<Sheet open={isSheetOpen} onOpenChange={handleClose}>
<SheetContent className="m-4 rounded-md">
<Outlet />
</SheetContent>
</Sheet>
</div>
);
}
190 changes: 190 additions & 0 deletions app/components/blocks/data-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Button } from "../ui/button";
import { useState } from "react";
import { Input } from "../ui/input";
import { exportTableToCSV } from "@/lib/xlsx";

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}

export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [rowSelection, setRowSelection] = useState({});
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
console.log(rowSelection);

const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),

onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,

state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
});

return (
<div>
<div className="flex items-center py-6">
<Input
placeholder="Filter emails..."
value={table.getColumn("first_name")?.getFilterValue() as string}
onChange={(event) =>
table.getColumn("first_name")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<Button
className="ml-4 bg-green-600"
onClick={() => exportTableToCSV(table)}
>
Export to CSV
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="ml-auto bg-orange-600 text-white"
>
Columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* table */}
<div className="rounded-md border w-full overflow-hidden p-4">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => {
return (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
);
})}
</TableHeader>

<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell>No results</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* pagination */}
<div className="flex items-center justify-start space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => {
table.previousPage();
}}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
table.nextPage();
}}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected
</div>
</div>
);
}
Loading

0 comments on commit 342ceca

Please sign in to comment.