Skip to content

Commit 8e39b80

Browse files
committed
feat: improve table
1 parent c82698e commit 8e39b80

File tree

10 files changed

+317
-156
lines changed

10 files changed

+317
-156
lines changed

web/src/components/pagination/index.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Button } from "@chakra-ui/button";
2-
import { ButtonGroup, Select, Stack } from "@chakra-ui/react";
2+
import { ButtonGroup, Center, Select, Spinner, Stack } from "@chakra-ui/react";
33
import { ReactNode } from "react";
44

55
export interface PaginationProps {
6+
loading?: boolean;
67
page?: number;
78
size?: number;
89
totalPages?: number;
@@ -13,6 +14,7 @@ export interface PaginationProps {
1314

1415
export const Pagination = ({
1516
onChange,
17+
loading,
1618
page = 1,
1719
size = 10,
1820
totalPages = 1,
@@ -47,6 +49,23 @@ export const Pagination = ({
4749
onChange && onChange(page, size);
4850
}
4951

52+
if (loading) {
53+
return (
54+
<Stack direction="row" spacing="5">
55+
<Center>
56+
<Spinner color="gray" />
57+
</Center>
58+
<Select
59+
disabled
60+
borderRadius="5"
61+
w="52"
62+
size="sm"
63+
placeholder='Itens por pagina'
64+
/>
65+
</ Stack>
66+
)
67+
}
68+
5069
return (
5170
<Stack direction="row" spacing="5">
5271
<ButtonGroup size='sm' isAttached variant="outline">
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Button, ButtonProps, HStack } from "@chakra-ui/react";
2+
import { PropsWithChildren } from "react";
3+
import { BsFillPenFill, BsTrash2, BsEye } from 'react-icons/bs';
4+
import { Column } from "./types";
5+
6+
type CustomButtonProps<T> = {
7+
onClick?: (row?: T) => void;
8+
options?: Omit<ButtonProps, 'onClick'>;
9+
}
10+
11+
interface ActionsProps<T> {
12+
row?: T,
13+
show?: CustomButtonProps<T>;
14+
edit?: CustomButtonProps<T>;
15+
remove?: CustomButtonProps<T>;
16+
}
17+
18+
export const Actions = function <T>({ row, show, edit, remove, children }: ActionsProps<T> & PropsWithChildren) {
19+
return (
20+
<HStack>
21+
{show && (
22+
<Button
23+
size="sm"
24+
variant="outline"
25+
{...show.options}
26+
onClick={() => show.onClick && show.onClick(row)}>
27+
<BsEye />
28+
</Button>
29+
)}
30+
{edit && (
31+
<Button
32+
size="sm"
33+
variant="outline"
34+
{...edit.options}
35+
onClick={() => edit.onClick && edit.onClick(row)}>
36+
<BsFillPenFill />
37+
</Button>
38+
)}
39+
{remove && (
40+
<Button
41+
size="sm"
42+
variant="outline"
43+
{...remove.options}
44+
onClick={() => remove.onClick && remove.onClick(row)}>
45+
<BsTrash2 />
46+
</Button>
47+
)}
48+
{children}
49+
</HStack>
50+
)
51+
}
52+
53+
export const actions = function <T>(props: ActionsProps<T>): Column<T> {
54+
return ({
55+
title: 'Actions',
56+
options: { w: '28' },
57+
disableSkeleton: true,
58+
render: (row) => <Actions row={row} {...props} />
59+
})
60+
}

web/src/components/table/body.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Box, Skeleton } from "@chakra-ui/react";
2+
import { Tbody, Td, Tr } from "@chakra-ui/table";
3+
import { Arrays } from "../../utils/arrays";
4+
import { Random } from "../../utils/random";
5+
import { Column } from "./types";
6+
7+
interface BodyProps<T> {
8+
loading?: boolean;
9+
columns: Column<T>[];
10+
content: T[];
11+
}
12+
13+
export function Body<T>({ loading, columns, content }: BodyProps<T>): JSX.Element {
14+
15+
if (Arrays.isNullOrEmpty(content)) {
16+
return (
17+
<Tbody>
18+
{Array(10)
19+
.fill(0)
20+
.map((_, row) => (
21+
<Tr key={row}>
22+
{columns.map((_, index) => (
23+
<Td key={index}>
24+
<Box py="2.5">
25+
<Skeleton height="13px" borderRadius="3" width={`${Random.between(25, 100)}%`} />
26+
</Box>
27+
</Td>
28+
))}
29+
</Tr>
30+
))}
31+
</Tbody>
32+
)
33+
}
34+
35+
const render = function <T>(row: T, column: Column<T>, index: number) {
36+
const { render, property, defaultValue, disableSkeleton } = column;
37+
if (loading && !disableSkeleton) {
38+
return (
39+
<Td key={index}>
40+
<Box py="2.5">
41+
<Skeleton height="13px" borderRadius="3" width={`${Random.between(25, 100)}%`} />
42+
</Box>
43+
</Td>
44+
)
45+
}
46+
47+
if (property) {
48+
const types = ["number", "boolean", "string"];
49+
const value = row[property];
50+
51+
if (value && types.includes(typeof value)) {
52+
return <Td key={index}>{`${value}`}</Td>;
53+
}
54+
}
55+
56+
if (render) {
57+
const element = render(row);
58+
if (element) {
59+
return <Td key={index}>{element}</Td>;
60+
}
61+
}
62+
63+
return <Td key={index}>{defaultValue ?? ""}</Td>;
64+
}
65+
66+
return (
67+
<Tbody>
68+
{content.map((row, key) => (
69+
<Tr key={key}>
70+
{columns.map((column, index) => render(row, column, index))}
71+
</Tr>
72+
))}
73+
</Tbody>
74+
);
75+
}

web/src/components/table/head.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Th, Thead, Tr } from "@chakra-ui/table";
2+
import { Column } from "./types";
3+
4+
interface HeadProps<T> {
5+
columns: Column<T>[];
6+
}
7+
8+
export function Head<T>({ columns }: HeadProps<T>) {
9+
return (
10+
<Thead>
11+
<Tr>
12+
{columns.map(({ title: name, property, options }, index) => (
13+
<Th {...options} key={index}>
14+
{name ?? property?.toString()}
15+
</Th>
16+
))}
17+
</Tr>
18+
</Thead>
19+
);
20+
}

web/src/components/table/index.tsx

Lines changed: 34 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,41 @@
1-
import { ReactNode } from "react";
2-
3-
import { Table as ChakraUiTable, TableContainer, Tbody, Td, Th, Thead, Tr, TableHeadProps, TableColumnHeaderProps } from "@chakra-ui/table";
4-
import { Box, Skeleton } from "@chakra-ui/react";
1+
import { Box, Center, Skeleton } from "@chakra-ui/react";
2+
import { Table as ChakraUiTable, TableContainer, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/table";
53
import { Random } from "../../utils/random";
4+
import { Pagination, PaginationProps } from "../pagination";
5+
import { Body } from "./body";
6+
import { Head } from "./head";
7+
import { Options } from "./types";
68

7-
export interface Column<T> {
8-
title?: string;
9-
disableSkeleton?: boolean;
10-
options?: TableColumnHeaderProps
11-
property?: keyof T;
12-
defaultValue?: string;
13-
render?: (row: T) => ReactNode;
14-
}
15-
16-
export interface Options<T> {
17-
loading?: boolean;
18-
variant?: 'simple' | 'striped' | 'unstyled';
19-
size?: 'sm' | 'md' | 'lg';
20-
content: T[];
21-
columns: Column<T>[];
9+
interface TableProps<T> extends Options<T> {
10+
pagination?: PaginationProps
2211
}
2312

24-
export function Table<T>({ loading, content, columns, size, variant }: Options<T>) {
13+
export function Table<T>({ loading, content, columns, size, variant, pagination }: TableProps<T>) {
2514
return (
26-
<TableContainer>
27-
<ChakraUiTable size={size} variant={variant}>
28-
<Thead>
29-
<Tr>
30-
{columns.map(({ title: name, property, options }, index) => (
31-
<Th {...options} key={index}>
32-
{name ?? property?.toString()}
33-
</Th>
34-
))}
35-
</Tr>
36-
</Thead>
37-
<Tbody>
38-
{content && content.length === 0 && (
39-
Array(10)
40-
.fill(0)
41-
.map((_, rowIndex) => (
42-
<Tr key={rowIndex}>
43-
{columns.map((_, columnIndex) => (
44-
<Td key={columnIndex}>
45-
<Box py="2">
46-
<Skeleton height="12px" borderRadius="3" width={`${Random.between(25, 100)}%`} />
47-
</Box>
48-
</Td>
49-
))}
50-
</Tr>
51-
))
52-
)}
53-
{content.map((row, index) => (
54-
<Tr key={index}>
55-
{columns.map(
56-
({ render, property, defaultValue, disableSkeleton }, tdIndex) => {
57-
if (loading && !disableSkeleton) {
58-
return (
59-
<Td key={tdIndex}>
60-
<Box py="2.5">
61-
<Skeleton height="13px" borderRadius="3" width={`${Random.between(25, 100)}%`} />
62-
</Box>
63-
</Td>
64-
)
65-
}
66-
67-
if (property) {
68-
const types = ["number", "boolean", "string"];
69-
const value = row[property];
70-
71-
if (value && types.includes(typeof value)) {
72-
return <Td key={tdIndex}>{`${value}`}</Td>;
73-
}
74-
}
75-
76-
if (render) {
77-
const element = render(row);
78-
if (element) {
79-
return <Td key={tdIndex}>{element}</Td>;
80-
}
81-
}
82-
83-
return <Td key={tdIndex}>{defaultValue ?? ""}</Td>;
84-
}
85-
)}
86-
</Tr>
87-
))}
88-
</Tbody>
89-
</ChakraUiTable>
90-
</TableContainer>
15+
<Box>
16+
<TableContainer>
17+
<ChakraUiTable size={size} variant={variant}>
18+
<Head columns={columns} />
19+
<Body
20+
loading={loading}
21+
columns={columns}
22+
content={content}
23+
/>
24+
</ChakraUiTable>
25+
</TableContainer>
26+
{pagination && (
27+
<Center my="12">
28+
<Pagination
29+
loading={pagination.loading}
30+
onChange={pagination.onChange}
31+
page={pagination.page}
32+
size={pagination.size}
33+
sizes={pagination.sizes}
34+
totalElements={pagination.totalElements}
35+
totalPages={pagination.totalPages}
36+
/>
37+
</Center>
38+
)}
39+
</Box>
9140
);
9241
}

web/src/components/table/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { TableColumnHeaderProps } from "@chakra-ui/react";
2+
import { ReactNode } from "react";
3+
4+
export interface Column<T> {
5+
title?: string;
6+
disableSkeleton?: boolean;
7+
options?: TableColumnHeaderProps
8+
property?: keyof T;
9+
defaultValue?: string;
10+
render?: (row: T) => ReactNode;
11+
}
12+
13+
export interface Options<T> {
14+
loading?: boolean;
15+
variant?: 'simple' | 'striped' | 'unstyled';
16+
size?: 'sm' | 'md' | 'lg';
17+
content: T[];
18+
columns: Column<T>[];
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Badge, Stack } from "@chakra-ui/react";
2+
3+
interface RolesProps {
4+
roles: string[];
5+
}
6+
7+
export const Roles = ({ roles }: RolesProps) => (
8+
<Stack direction='row'>
9+
{roles.map(role => <Badge key={role}>{role}</Badge>)}
10+
</Stack>
11+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Badge } from "@chakra-ui/react";
2+
3+
interface StatusProps {
4+
active: boolean;
5+
}
6+
7+
export const Status = ({ active }: StatusProps) => active ?
8+
<Badge colorScheme='green'>Active</Badge> :
9+
<Badge colorScheme='red'>Inative</Badge>

0 commit comments

Comments
 (0)