wca-designsystem 0.0.44 → 0.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/components/molecules/Graficos/Donut/Donut.stories.tsx +42 -0
- package/src/components/molecules/Graficos/Donut/Donut.test.tsx +57 -0
- package/src/components/molecules/Graficos/Donut/index.tsx +27 -0
- package/src/components/molecules/Graficos/Donut/styles.ts +34 -0
- package/src/components/molecules/TabelaHeader/index.tsx +100 -0
- package/src/components/molecules/TabelaHeader/styles.ts +16 -0
- package/src/components/organismos/Tabela/Tabela.stories.tsx +18 -1
- package/src/components/organismos/Tabela/body.tsx +148 -125
- package/src/components/organismos/Tabela/header.tsx +14 -9
- package/src/components/organismos/Tabela/index.tsx +31 -60
- package/src/components/organismos/Tabela/styles.ts +8 -19
- package/src/utils/functions.ts +4 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
2
|
+
"version": "0.0.46",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
@@ -93,7 +93,8 @@
|
|
|
93
93
|
"@fullcalendar/interaction": "^6.1.15",
|
|
94
94
|
"@fullcalendar/react": "^6.1.15",
|
|
95
95
|
"@fullcalendar/timegrid": "^6.1.15",
|
|
96
|
-
"@mantine/
|
|
96
|
+
"@mantine/charts": "^7.13.4",
|
|
97
|
+
"@mantine/core": "^7.13.4",
|
|
97
98
|
"@mantine/dates": "^7.13.4",
|
|
98
99
|
"@mantine/form": "^7.13.4",
|
|
99
100
|
"@mantine/hooks": "7.13.4",
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
"axios": "^1.7.2",
|
|
104
105
|
"dayjs": "^1.11.13",
|
|
105
106
|
"react-router-dom": "^6.23.1",
|
|
107
|
+
"recharts": "^2.14.1",
|
|
106
108
|
"styled-components": "^6.1.11",
|
|
107
109
|
"xlsx": "^0.18.5"
|
|
108
110
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import DonutChart, { DonutProps } from './index';
|
|
3
|
+
import { Meta } from '@storybook/react/*';
|
|
4
|
+
|
|
5
|
+
import '@mantine/charts/styles.css';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/moleculas/Graficos/Donut',
|
|
9
|
+
component: DonutChart,
|
|
10
|
+
} as Meta;
|
|
11
|
+
|
|
12
|
+
type DataProps = {
|
|
13
|
+
name: string;
|
|
14
|
+
value: number;
|
|
15
|
+
color: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const data: Array<DataProps> = [
|
|
19
|
+
{ name: 'Excelente', value: 20, color: '#4c6ef5' },
|
|
20
|
+
{ name: 'Ótimo', value: 30, color: '#fcc419' },
|
|
21
|
+
{ name: 'Bom', value: 10, color: '#12b886' },
|
|
22
|
+
{ name: 'Regular', value: 20, color: '#fa5252' },
|
|
23
|
+
{ name: 'Ruim', value: 20, color: '#7030A0' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const props: DonutProps<DataProps> = {
|
|
27
|
+
data: data,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Template: any = (args: DonutProps<DataProps>) => (
|
|
31
|
+
<>
|
|
32
|
+
<DonutChart {...args} />
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export const DonutStory = Template.bind({
|
|
37
|
+
...props,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
DonutStory.args = {
|
|
41
|
+
...props,
|
|
42
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import DonutWrapper from './index';
|
|
3
|
+
import { render } from '../../../../test/render';
|
|
4
|
+
|
|
5
|
+
describe('DonutWrapper está renderizando corretamente', () => {
|
|
6
|
+
const mockData = [
|
|
7
|
+
{ name: 'Categoria 1', value: 40, color: '#FF0000' },
|
|
8
|
+
{ name: 'Categoria 2', value: 30, color: '#00FF00' },
|
|
9
|
+
{ name: 'Categoria 3', value: 30, color: '#0000FF' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
test('Renderizando todos os itens do gráfico', () => {
|
|
13
|
+
const { getAllByText, getByText } = render(
|
|
14
|
+
<DonutWrapper data={mockData} />
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Verifica se os valores das categorias estão sendo exibidos
|
|
18
|
+
expect(getByText('40%')).toBeDefined();
|
|
19
|
+
|
|
20
|
+
// Usa getAllByText para valores duplicados
|
|
21
|
+
const elements = getAllByText('30%');
|
|
22
|
+
expect(elements).toHaveLength(2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('Renderizando o componente com dados vazios', () => {
|
|
26
|
+
const { container } = render(<DonutWrapper data={[]} />);
|
|
27
|
+
|
|
28
|
+
// Verifica se não há nenhum texto percentual na tela
|
|
29
|
+
expect(container.textContent).not.toContain('%');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Renderizando as caixas de cores corretamente', () => {
|
|
33
|
+
const { container } = render(<DonutWrapper data={mockData} />);
|
|
34
|
+
|
|
35
|
+
// Verifica se as caixas de cores têm os estilos corretos
|
|
36
|
+
mockData.forEach(item => {
|
|
37
|
+
const colorBox = container.querySelector(
|
|
38
|
+
`[style="background-color: ${item.color};"]`
|
|
39
|
+
);
|
|
40
|
+
expect(colorBox).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('Lida com dados inválidos ou ausentes', () => {
|
|
45
|
+
const invalidDataMock = [
|
|
46
|
+
{ name: 'Categoria 1', value: null, color: '#FF0000' },
|
|
47
|
+
{ name: 'Categoria 2', color: '#00FF00' },
|
|
48
|
+
{ value: 30, color: '#0000FF' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const { getByText } = render(<DonutWrapper data={invalidDataMock} />);
|
|
52
|
+
|
|
53
|
+
// Verifica que o componente ignora ou substitui valores inválidos
|
|
54
|
+
expect(getByText('Categoria 1')).toBeDefined();
|
|
55
|
+
expect(getByText('30%')).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DonutChart } from '@mantine/charts';
|
|
2
|
+
import { Flex } from '@mantine/core';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ChartInfos, InfoItem, ColorBox, Label, Value } from './styles';
|
|
5
|
+
|
|
6
|
+
export type DonutProps<T = any> = {
|
|
7
|
+
data: Array<T>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const DonutWrapper = ({ data }: DonutProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<Flex gap={20} align={'center'}>
|
|
13
|
+
<DonutChart data={data} thickness={30} withTooltip={false} />
|
|
14
|
+
<ChartInfos>
|
|
15
|
+
{data.map((item, index) => (
|
|
16
|
+
<InfoItem key={index}>
|
|
17
|
+
<ColorBox color={item.color} />
|
|
18
|
+
<Label>{item.name}</Label>
|
|
19
|
+
<Value>{item.value}%</Value>
|
|
20
|
+
</InfoItem>
|
|
21
|
+
))}
|
|
22
|
+
</ChartInfos>
|
|
23
|
+
</Flex>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default DonutWrapper;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const ChartInfos = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 8px;
|
|
7
|
+
background-color: #ffffff;
|
|
8
|
+
padding: 12px;
|
|
9
|
+
border-radius: 8px;
|
|
10
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
export const InfoItem = styled.div`
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 8px;
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const ColorBox = styled.div`
|
|
20
|
+
width: 16px;
|
|
21
|
+
height: 16px;
|
|
22
|
+
border-radius: 4px;
|
|
23
|
+
background-color: ${({ color }) => color};
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
export const Label = styled.span`
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
color: #333;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export const Value = styled.span`
|
|
32
|
+
font-size: 12px;
|
|
33
|
+
color: #666;
|
|
34
|
+
`;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as S from './styles';
|
|
3
|
+
import InputDeTexto from '../../atomos/InputDeTexto';
|
|
4
|
+
import SelectCustomizado from '../../atomos/Select';
|
|
5
|
+
import Icone from '../../atomos/Icone';
|
|
6
|
+
import IconeBotao from '../../atomos/IconeBotao';
|
|
7
|
+
import Botao from '../../atomos/Botao';
|
|
8
|
+
import pesquisar from '../../../assets/imagens/icons/Busca.svg';
|
|
9
|
+
import ordernar from '../../../assets/imagens/icons/Ordernar.svg';
|
|
10
|
+
import { SelectDataProps } from '../../organismos/Tabela';
|
|
11
|
+
import filtrar from '../../../assets/imagens/icons/Filtrar.svg';
|
|
12
|
+
import plus from '../../../assets/imagens/icons/Mais.svg';
|
|
13
|
+
|
|
14
|
+
export type TabelaHeaderProps = {
|
|
15
|
+
globalFilter: string;
|
|
16
|
+
setGlobalFilter: React.Dispatch<React.SetStateAction<string>>;
|
|
17
|
+
setToogleFiltros: React.Dispatch<React.SetStateAction<boolean>>;
|
|
18
|
+
toogleFiltros: boolean;
|
|
19
|
+
color?: string;
|
|
20
|
+
hasOrder?: boolean;
|
|
21
|
+
hasAdd?: boolean;
|
|
22
|
+
setOrdenarPor?: React.Dispatch<React.SetStateAction<SelectDataProps>>;
|
|
23
|
+
handleAdicionar?: () => void;
|
|
24
|
+
ordenarPor?: SelectDataProps;
|
|
25
|
+
hasFiltersButtons?: boolean;
|
|
26
|
+
};
|
|
27
|
+
const TabelaHeader = ({
|
|
28
|
+
globalFilter,
|
|
29
|
+
setGlobalFilter,
|
|
30
|
+
setToogleFiltros,
|
|
31
|
+
toogleFiltros,
|
|
32
|
+
color,
|
|
33
|
+
hasAdd,
|
|
34
|
+
hasFiltersButtons,
|
|
35
|
+
handleAdicionar,
|
|
36
|
+
ordenarPor,
|
|
37
|
+
setOrdenarPor,
|
|
38
|
+
hasOrder,
|
|
39
|
+
}: TabelaHeaderProps) => {
|
|
40
|
+
return (
|
|
41
|
+
<S.HeaderTableContent>
|
|
42
|
+
<InputDeTexto
|
|
43
|
+
tipo="table"
|
|
44
|
+
onChange={e => setGlobalFilter(e.currentTarget.value)}
|
|
45
|
+
color={color}
|
|
46
|
+
value={globalFilter}
|
|
47
|
+
placeholder="Pesquisar na tabela"
|
|
48
|
+
rightSection={<Icone svg={pesquisar} fill={color} />}
|
|
49
|
+
/>
|
|
50
|
+
{hasOrder && (
|
|
51
|
+
<SelectCustomizado
|
|
52
|
+
tipo="table"
|
|
53
|
+
data={[
|
|
54
|
+
{ value: 'Maior', label: 'Ordenar por: Maior' },
|
|
55
|
+
{ value: 'Menor', label: 'Ordenar por: Menor' },
|
|
56
|
+
]}
|
|
57
|
+
onChange={(_value, option) => setOrdenarPor!(option)}
|
|
58
|
+
value={ordenarPor ? ordenarPor.value : null}
|
|
59
|
+
placeholder={`Ordenar por ${ordenarPor}`}
|
|
60
|
+
maw={200}
|
|
61
|
+
comboboxProps={{
|
|
62
|
+
position: 'bottom',
|
|
63
|
+
middlewares: { flip: false, shift: false },
|
|
64
|
+
offset: 0,
|
|
65
|
+
}}
|
|
66
|
+
leftSection={
|
|
67
|
+
<Icone width={15} height={15} fill={color} svg={ordernar} />
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{/* Botão de filtros */}
|
|
73
|
+
{hasFiltersButtons && (
|
|
74
|
+
<S.ToogleButton>
|
|
75
|
+
<IconeBotao
|
|
76
|
+
tipo="table"
|
|
77
|
+
color={color}
|
|
78
|
+
toolTipInfo="Filtros especificos"
|
|
79
|
+
onClick={() => setToogleFiltros(!toogleFiltros)}
|
|
80
|
+
>
|
|
81
|
+
<Icone svg={filtrar} fill={color} width={20} />
|
|
82
|
+
</IconeBotao>
|
|
83
|
+
</S.ToogleButton>
|
|
84
|
+
)}
|
|
85
|
+
{hasAdd && (
|
|
86
|
+
<Botao
|
|
87
|
+
tipo="round"
|
|
88
|
+
leftSection={<Icone svg={plus} fill={color} />}
|
|
89
|
+
color={color}
|
|
90
|
+
onClick={() => handleAdicionar!()}
|
|
91
|
+
h={32}
|
|
92
|
+
>
|
|
93
|
+
Adicionar Usuário
|
|
94
|
+
</Botao>
|
|
95
|
+
)}
|
|
96
|
+
</S.HeaderTableContent>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default TabelaHeader;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import styled, { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const HeaderTableContent = styled.div`
|
|
4
|
+
${({ theme }) => css`
|
|
5
|
+
display: flex;
|
|
6
|
+
justify-content: end;
|
|
7
|
+
gap: 15px;
|
|
8
|
+
padding: 15px 0;
|
|
9
|
+
background-color: ${theme.colors.gray['300']};
|
|
10
|
+
width: 100%;
|
|
11
|
+
overflow-x: auto;
|
|
12
|
+
`}
|
|
13
|
+
`;
|
|
14
|
+
export const ToogleButton = styled.div`
|
|
15
|
+
${() => css``}
|
|
16
|
+
`;
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { Meta } from '@storybook/react';
|
|
3
3
|
import Tabela from './index';
|
|
4
4
|
import { theme } from '../../../styles/theme';
|
|
5
|
+
import Botao from '../../atomos/Botao';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
title: 'Components/organismos/Tabela',
|
|
@@ -22,6 +23,15 @@ const data: any[] = [
|
|
|
22
23
|
price: '$10',
|
|
23
24
|
exemplo: 'opa',
|
|
24
25
|
exemplo2: 'opa2',
|
|
26
|
+
children: [
|
|
27
|
+
{
|
|
28
|
+
id: '1-10',
|
|
29
|
+
name: 'Task 10',
|
|
30
|
+
price: '$10',
|
|
31
|
+
exemplo: 'opa',
|
|
32
|
+
exemplo2: 'opa2',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
25
35
|
},
|
|
26
36
|
{
|
|
27
37
|
id: '1-2',
|
|
@@ -158,7 +168,7 @@ const columns: any = [
|
|
|
158
168
|
{ key: 'name', header: 'Nome' },
|
|
159
169
|
{ key: 'price', header: 'Preço' },
|
|
160
170
|
{ key: 'exemplo', header: 'Exemplo' },
|
|
161
|
-
{ key: '
|
|
171
|
+
{ key: 'acoes', header: 'acoes' },
|
|
162
172
|
];
|
|
163
173
|
|
|
164
174
|
const Template: any = (args: any) => (
|
|
@@ -172,6 +182,11 @@ export const TabelaProps = Template.bind({});
|
|
|
172
182
|
TabelaProps.args = {
|
|
173
183
|
data: data,
|
|
174
184
|
hasExpand: true,
|
|
185
|
+
acoesChildren: (row: any): JSX.Element => (
|
|
186
|
+
<>
|
|
187
|
+
<Botao onClick={() => console.log(row)} tipo={'table'} children={'▶'} />
|
|
188
|
+
</>
|
|
189
|
+
),
|
|
175
190
|
columns: columns,
|
|
176
191
|
color: theme.colors.blue,
|
|
177
192
|
hasFiltersButtons: true,
|
|
@@ -183,6 +198,8 @@ TabelaProps.args = {
|
|
|
183
198
|
pageIndex: 1,
|
|
184
199
|
pageSize: 10,
|
|
185
200
|
},
|
|
201
|
+
rowSelection: [],
|
|
202
|
+
setRowSelection: () => console.log('selecionou'),
|
|
186
203
|
setFiltrosPor: () => console.log('aqui'),
|
|
187
204
|
setGlobalFilter: () => console.log('aqui'),
|
|
188
205
|
setPagination: () => console.log('aqui'),
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
2
|
import { Checkbox } from '@mantine/core';
|
|
3
3
|
import { Column } from '.';
|
|
4
4
|
import * as S from './styles';
|
|
5
|
-
import { useCallback } from 'react';
|
|
6
5
|
import { theme } from '../../../styles/theme';
|
|
7
6
|
import Icone from '../../atomos/Icone';
|
|
8
7
|
import SetaBaixo from '../../../assets/imagens/icons/SetaAbaixo.svg';
|
|
@@ -14,23 +13,25 @@ export interface BodyTableProps<
|
|
|
14
13
|
columns: Column<T>[];
|
|
15
14
|
fixedPosition?: string[] | undefined;
|
|
16
15
|
color?: string;
|
|
17
|
-
acoesChildren?: JSX.Element;
|
|
16
|
+
acoesChildren?: (row: T) => JSX.Element;
|
|
18
17
|
calcStickPosition?: (index: number) => number;
|
|
19
18
|
hasSelect?: boolean;
|
|
20
19
|
rowSelection?: Array<T>;
|
|
21
20
|
setRowSelection?: React.Dispatch<React.SetStateAction<Array<T>>> | undefined;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
function BodyTable<T extends { id: string | number; children: Array<T> }>(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
function BodyTable<T extends { id: string | number; children: Array<T> }>(
|
|
24
|
+
props: BodyTableProps<T>
|
|
25
|
+
) {
|
|
26
|
+
const {
|
|
27
|
+
data,
|
|
28
|
+
columns,
|
|
29
|
+
acoesChildren,
|
|
30
|
+
hasSelect,
|
|
31
|
+
rowSelection,
|
|
32
|
+
setRowSelection,
|
|
33
|
+
color,
|
|
34
|
+
} = props;
|
|
34
35
|
|
|
35
36
|
const [expandedRows, setExpandedRows] = useState<Set<number | string>>(
|
|
36
37
|
new Set()
|
|
@@ -61,122 +62,144 @@ function BodyTable<T extends { id: string | number; children: Array<T> }>({
|
|
|
61
62
|
});
|
|
62
63
|
}, []);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
function renderRows(
|
|
66
|
+
item: T,
|
|
67
|
+
columns: Column<T>[],
|
|
68
|
+
expandedRows: Set<string | number>,
|
|
69
|
+
toggleExpandRow: (rowId: string | number) => void,
|
|
70
|
+
acoesChildren?: (row: T) => JSX.Element,
|
|
71
|
+
rowSelection?: Array<T>,
|
|
72
|
+
setRowSelection?:
|
|
73
|
+
| React.Dispatch<React.SetStateAction<Array<T>>>
|
|
74
|
+
| undefined,
|
|
75
|
+
color?: string
|
|
76
|
+
): JSX.Element[] {
|
|
77
|
+
const isSelected = rowSelection?.some(row => row.id === item.id);
|
|
78
|
+
const hasChildren = item.children && item.children.length > 0;
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
<tr key={`row-${item.id}`}>
|
|
82
|
+
{/* Checkbox para seleção */}
|
|
83
|
+
{hasSelect && (
|
|
84
|
+
<td className="select">
|
|
85
|
+
<Checkbox
|
|
86
|
+
size="md"
|
|
87
|
+
color={color}
|
|
88
|
+
type="checkbox"
|
|
89
|
+
checked={rowSelection?.some(selected => selected.id === item.id)}
|
|
90
|
+
onChange={() => {
|
|
91
|
+
handleRowSelect(
|
|
92
|
+
item,
|
|
93
|
+
rowSelection!.some(selected => selected.id === item.id)
|
|
94
|
+
);
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</td>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{/* Colunas dinâmicas */}
|
|
101
|
+
{columns.map((column, colIndex) => {
|
|
102
|
+
if (column.key === 'expand') {
|
|
103
|
+
return (
|
|
104
|
+
<td
|
|
105
|
+
key={`expand-${colIndex}`}
|
|
106
|
+
onClick={
|
|
107
|
+
hasChildren ? () => toggleExpandRow(item.id) : undefined
|
|
108
|
+
}
|
|
109
|
+
className="expand"
|
|
110
|
+
style={{
|
|
111
|
+
cursor: hasChildren ? 'pointer' : 'not-allowed',
|
|
112
|
+
opacity: hasChildren ? 1 : 0.5,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<Icone
|
|
116
|
+
svg={SetaBaixo}
|
|
117
|
+
fill={theme.colors.white}
|
|
118
|
+
style={{
|
|
119
|
+
transform: expandedRows.has(item.id)
|
|
120
|
+
? 'rotate(-180deg)'
|
|
121
|
+
: hasChildren
|
|
122
|
+
? 'rotate(0deg)'
|
|
123
|
+
: 'rotate(-90deg)',
|
|
124
|
+
color: theme.colors.white,
|
|
125
|
+
transition: 'ease-in 0.2s',
|
|
126
|
+
}}
|
|
85
127
|
/>
|
|
86
128
|
</td>
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
columnKey => columnKey.key === 'acoes' && acoesChildren
|
|
90
|
-
)}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
91
131
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
onClick={() => toggleExpandRow(item.id)}
|
|
100
|
-
className="expand"
|
|
101
|
-
>
|
|
102
|
-
{expandedRows.has(item.id) ? (
|
|
103
|
-
<Icone
|
|
104
|
-
svg={SetaBaixo}
|
|
105
|
-
fill={theme.colors.white}
|
|
106
|
-
style={{
|
|
107
|
-
transform: 'rotate(180deg)',
|
|
108
|
-
color: theme.colors.white,
|
|
109
|
-
transition: 'ease-in 0.2s',
|
|
110
|
-
}}
|
|
111
|
-
/>
|
|
112
|
-
) : (
|
|
113
|
-
<Icone
|
|
114
|
-
svg={SetaBaixo}
|
|
115
|
-
fill={theme.colors.white}
|
|
116
|
-
style={{
|
|
117
|
-
transform: 'rotate(0deg)',
|
|
118
|
-
color: theme.colors.white,
|
|
119
|
-
transition: 'ease-in 0.2s',
|
|
120
|
-
}}
|
|
121
|
-
/>
|
|
122
|
-
)}
|
|
123
|
-
</td>
|
|
124
|
-
</>
|
|
125
|
-
) : column.render ? (
|
|
126
|
-
<th
|
|
127
|
-
key={String(column.key)}
|
|
128
|
-
style={{
|
|
129
|
-
background: rowSelection?.some(row => row.id === item.id)
|
|
130
|
-
? theme.colors.gray['500']
|
|
131
|
-
: 'transparent',
|
|
132
|
-
}}
|
|
133
|
-
>
|
|
134
|
-
{column.render(item[column.key])}
|
|
135
|
-
</th>
|
|
136
|
-
) : (
|
|
137
|
-
<td
|
|
138
|
-
key={String(column.key)}
|
|
139
|
-
style={{
|
|
140
|
-
background: rowSelection?.some(row => row.id === item.id)
|
|
141
|
-
? theme.colors.gray['500']
|
|
142
|
-
: 'transparent',
|
|
143
|
-
}}
|
|
144
|
-
>
|
|
145
|
-
{String(item[column.key])}
|
|
146
|
-
</td>
|
|
147
|
-
)}
|
|
148
|
-
</>
|
|
149
|
-
))}
|
|
150
|
-
</tr>
|
|
151
|
-
{expandedRows.has(item.id) &&
|
|
152
|
-
item.children &&
|
|
153
|
-
item.children.map((child, childIndex) => {
|
|
154
|
-
const rowKey = `${item.id}-${childIndex}`;
|
|
155
|
-
const isExpanded = expandedRows.has(item.id);
|
|
132
|
+
if (column.key === 'acoes' && acoesChildren) {
|
|
133
|
+
return (
|
|
134
|
+
<td key={`acoes-${colIndex}`} className="acoes">
|
|
135
|
+
{acoesChildren(item)}
|
|
136
|
+
</td>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
156
139
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
140
|
+
// Verifica se existe a prop 'cell'
|
|
141
|
+
if (column.cell) {
|
|
142
|
+
return <td key={`cell-${colIndex}`}>{column.cell(item)}</td>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Render padrão
|
|
146
|
+
return column.render ? (
|
|
147
|
+
<td
|
|
148
|
+
key={`column-${colIndex}`}
|
|
149
|
+
style={{
|
|
150
|
+
background: isSelected
|
|
151
|
+
? theme.colors.gray['500']
|
|
152
|
+
: 'transparent',
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
{column.render(item[column.key])}
|
|
156
|
+
</td>
|
|
157
|
+
) : (
|
|
158
|
+
<td
|
|
159
|
+
key={`cell-${colIndex}`}
|
|
160
|
+
style={{
|
|
161
|
+
background: isSelected
|
|
162
|
+
? theme.colors.gray['500']
|
|
163
|
+
: 'transparent',
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{String(item[column.key])}
|
|
167
|
+
</td>
|
|
168
|
+
);
|
|
169
|
+
})}
|
|
170
|
+
</tr>,
|
|
171
|
+
|
|
172
|
+
// Renderiza os filhos recursivamente, caso existam
|
|
173
|
+
...(expandedRows.has(item.id)
|
|
174
|
+
? item.children.flatMap(child =>
|
|
175
|
+
renderRows(
|
|
176
|
+
child,
|
|
177
|
+
columns,
|
|
178
|
+
expandedRows,
|
|
179
|
+
toggleExpandRow,
|
|
180
|
+
acoesChildren,
|
|
181
|
+
rowSelection,
|
|
182
|
+
setRowSelection,
|
|
183
|
+
color
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
: []),
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
return (
|
|
190
|
+
<S.TabelaBody color={color} hasselect={hasSelect ? 'true' : 'false'}>
|
|
191
|
+
{data.flatMap(item =>
|
|
192
|
+
renderRows(
|
|
193
|
+
item,
|
|
194
|
+
columns,
|
|
195
|
+
expandedRows,
|
|
196
|
+
toggleExpandRow,
|
|
197
|
+
acoesChildren,
|
|
198
|
+
rowSelection,
|
|
199
|
+
setRowSelection,
|
|
200
|
+
color
|
|
201
|
+
)
|
|
202
|
+
)}
|
|
180
203
|
</S.TabelaBody>
|
|
181
204
|
);
|
|
182
205
|
}
|
|
@@ -7,6 +7,7 @@ import { useCallback } from 'react';
|
|
|
7
7
|
import { theme } from '../../../styles/theme';
|
|
8
8
|
import InputDeTexto from '../../atomos/InputDeTexto';
|
|
9
9
|
import Icone from '../../atomos/Icone';
|
|
10
|
+
import { Capitalize } from '../../../utils/functions';
|
|
10
11
|
|
|
11
12
|
type HeaderOptions = {
|
|
12
13
|
[key: string | number | symbol]: string; // Um índice genérico para mapear headers a valores
|
|
@@ -66,15 +67,19 @@ const HeaderTabela = <T,>({
|
|
|
66
67
|
[headerSelection]
|
|
67
68
|
);
|
|
68
69
|
|
|
69
|
-
const renderHeaderOptions = useCallback(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
const renderHeaderOptions = useCallback(
|
|
71
|
+
(
|
|
72
|
+
header: string | number | symbol,
|
|
73
|
+
key: string | number | symbol
|
|
74
|
+
): string | undefined => {
|
|
75
|
+
const especialHeaders: HeaderOptions = {
|
|
76
|
+
ativo: 'Status',
|
|
77
|
+
};
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
return especialHeaders[header] ?? Capitalize(String(header));
|
|
80
|
+
},
|
|
81
|
+
[]
|
|
82
|
+
);
|
|
78
83
|
return (
|
|
79
84
|
<S.TabelaHeader color={color}>
|
|
80
85
|
<tr>
|
|
@@ -145,7 +150,7 @@ const HeaderTabela = <T,>({
|
|
|
145
150
|
)}
|
|
146
151
|
</div>
|
|
147
152
|
) : (
|
|
148
|
-
<>{renderHeaderOptions(header.key)}</>
|
|
153
|
+
<>{renderHeaderOptions(header.header, header.key)}</>
|
|
149
154
|
)}
|
|
150
155
|
</th>
|
|
151
156
|
);
|
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
2
|
import BodyTable from './body';
|
|
3
3
|
import HeaderTabela from './header';
|
|
4
4
|
import Paginacao from './pagination';
|
|
5
5
|
import SkeletonTable from './SkeletonTable';
|
|
6
6
|
import * as S from './styles';
|
|
7
|
-
import filtrar from '../../../assets/imagens/icons/Filtrar.svg';
|
|
8
7
|
import { useState } from 'react';
|
|
9
|
-
import
|
|
10
|
-
import Icone from '../../atomos/Icone';
|
|
11
|
-
import InputDeTexto from '../../atomos/InputDeTexto';
|
|
12
|
-
import pesquisar from '../../../assets/imagens/icons/Busca.svg';
|
|
13
|
-
import SelectCustomizado from '../../atomos/Select';
|
|
14
|
-
import { theme } from '../../../styles/theme';
|
|
15
|
-
import ordernar from '../../../assets/imagens/icons/Ordernar.svg';
|
|
8
|
+
import TabelaHeader from '../../molecules/TabelaHeader';
|
|
16
9
|
|
|
17
10
|
// Tipo genérico para as colunas
|
|
18
11
|
export type Column<T> = {
|
|
19
12
|
key: keyof T;
|
|
20
13
|
header: string;
|
|
21
14
|
render?: (value: T[keyof T]) => React.ReactNode;
|
|
22
|
-
|
|
15
|
+
cell?: (row: T) => React.ReactNode;
|
|
16
|
+
expand?: (item: T) => React.ReactNode;
|
|
23
17
|
};
|
|
24
18
|
|
|
25
19
|
export type SelectDataProps = {
|
|
@@ -33,9 +27,10 @@ export type CustomTableProps<
|
|
|
33
27
|
data: T[];
|
|
34
28
|
columns: Column<T>[];
|
|
35
29
|
fixedPosition?: Array<String>;
|
|
36
|
-
acoesChildren?: JSX.Element;
|
|
30
|
+
acoesChildren?: (row: T) => JSX.Element;
|
|
37
31
|
hasSelect?: boolean;
|
|
38
32
|
hasExpand?: boolean;
|
|
33
|
+
hasAdd?: boolean;
|
|
39
34
|
hasOrder?: boolean;
|
|
40
35
|
headerSelection?: Array<String>;
|
|
41
36
|
rowSelection?: Array<T>;
|
|
@@ -44,11 +39,12 @@ export type CustomTableProps<
|
|
|
44
39
|
color?: string;
|
|
45
40
|
setSimples: React.Dispatch<React.SetStateAction<boolean>>;
|
|
46
41
|
setGlobalFilter: React.Dispatch<React.SetStateAction<string>>;
|
|
42
|
+
globalFilter: string;
|
|
47
43
|
setPagination: React.Dispatch<any>;
|
|
44
|
+
handleAdicionar?: () => void;
|
|
48
45
|
hasFiltersButtons?: boolean;
|
|
49
46
|
setOrdenarPor?: React.Dispatch<React.SetStateAction<SelectDataProps>>;
|
|
50
47
|
ordenarPor?: SelectDataProps;
|
|
51
|
-
|
|
52
48
|
filtrosPor: {
|
|
53
49
|
value: string;
|
|
54
50
|
label: string;
|
|
@@ -76,7 +72,9 @@ const Tabela = <T extends { id: string | number; children: Array<T> }>({
|
|
|
76
72
|
setRowSelection,
|
|
77
73
|
hasFiltersButtons,
|
|
78
74
|
isLoading,
|
|
75
|
+
hasAdd,
|
|
79
76
|
hasSelect,
|
|
77
|
+
globalFilter,
|
|
80
78
|
hasExpand,
|
|
81
79
|
setFiltrosPor,
|
|
82
80
|
setGlobalFilter,
|
|
@@ -84,60 +82,33 @@ const Tabela = <T extends { id: string | number; children: Array<T> }>({
|
|
|
84
82
|
headerSelection,
|
|
85
83
|
setOrdenarPor,
|
|
86
84
|
setSimples,
|
|
85
|
+
handleAdicionar,
|
|
87
86
|
tabelaPaginacao,
|
|
88
87
|
fixedPosition,
|
|
89
88
|
}: CustomTableProps<T>) => {
|
|
90
89
|
const [toogleFiltros, setToogleFiltros] = useState(false);
|
|
90
|
+
const [loading, setLoading] = useState(isLoading);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
setLoading(isLoading);
|
|
94
|
+
}, [isLoading]);
|
|
95
|
+
|
|
91
96
|
return (
|
|
92
97
|
<S.TableWrapper>
|
|
93
98
|
<S.TableContainer>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
]}
|
|
108
|
-
onChange={(_value, option) => setOrdenarPor!(option)}
|
|
109
|
-
value={ordenarPor ? ordenarPor.value : null}
|
|
110
|
-
placeholder={`Ordenar por ${ordenarPor}`}
|
|
111
|
-
maw={200}
|
|
112
|
-
comboboxProps={{
|
|
113
|
-
position: 'bottom',
|
|
114
|
-
middlewares: { flip: false, shift: false },
|
|
115
|
-
offset: 0,
|
|
116
|
-
}}
|
|
117
|
-
leftSection={
|
|
118
|
-
<Icone
|
|
119
|
-
width={15}
|
|
120
|
-
height={15}
|
|
121
|
-
fill={theme.colors.blue}
|
|
122
|
-
svg={ordernar}
|
|
123
|
-
/>
|
|
124
|
-
}
|
|
125
|
-
/>
|
|
126
|
-
)}
|
|
127
|
-
{/* Botão de filtros */}
|
|
128
|
-
{hasFiltersButtons && (
|
|
129
|
-
<S.ToogleButton>
|
|
130
|
-
<IconeBotao
|
|
131
|
-
tipo="table"
|
|
132
|
-
color={color}
|
|
133
|
-
toolTipInfo="Filtros especificos"
|
|
134
|
-
onClick={() => setToogleFiltros(!toogleFiltros)}
|
|
135
|
-
>
|
|
136
|
-
<Icone svg={filtrar} fill={color} width={20} />
|
|
137
|
-
</IconeBotao>
|
|
138
|
-
</S.ToogleButton>
|
|
139
|
-
)}
|
|
140
|
-
</S.HeaderTableContent>
|
|
99
|
+
<TabelaHeader
|
|
100
|
+
handleAdicionar={handleAdicionar}
|
|
101
|
+
globalFilter={globalFilter}
|
|
102
|
+
setGlobalFilter={setGlobalFilter}
|
|
103
|
+
setToogleFiltros={setToogleFiltros}
|
|
104
|
+
toogleFiltros={toogleFiltros}
|
|
105
|
+
color={color}
|
|
106
|
+
hasAdd={hasAdd}
|
|
107
|
+
hasFiltersButtons={hasFiltersButtons}
|
|
108
|
+
hasOrder={hasOrder}
|
|
109
|
+
ordenarPor={ordenarPor}
|
|
110
|
+
setOrdenarPor={setOrdenarPor}
|
|
111
|
+
/>
|
|
141
112
|
<S.StyledTable>
|
|
142
113
|
<HeaderTabela
|
|
143
114
|
hasExpand={hasExpand}
|
|
@@ -153,7 +124,7 @@ const Tabela = <T extends { id: string | number; children: Array<T> }>({
|
|
|
153
124
|
fixedPosition={fixedPosition!}
|
|
154
125
|
onFilterColunas={id => console.log(id)}
|
|
155
126
|
/>
|
|
156
|
-
{
|
|
127
|
+
{loading ? (
|
|
157
128
|
<SkeletonTable columns={columns} data={data} />
|
|
158
129
|
) : (
|
|
159
130
|
<BodyTable
|
|
@@ -19,19 +19,7 @@ export const TableContainer = styled.div`
|
|
|
19
19
|
border-radius: 5px;
|
|
20
20
|
font-family: 'Open Sans', sans-serif;
|
|
21
21
|
max-width: 1500px;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export const HeaderTableContent = styled.div`
|
|
25
|
-
${() => css`
|
|
26
|
-
display: flex;
|
|
27
|
-
justify-content: end;
|
|
28
|
-
gap: 15px;
|
|
29
|
-
margin-bottom: 15px;
|
|
30
|
-
`}
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
export const ToogleButton = styled.div`
|
|
34
|
-
${() => css``}
|
|
22
|
+
width: 100%;
|
|
35
23
|
`;
|
|
36
24
|
|
|
37
25
|
export const StyledTable = styled.table`
|
|
@@ -68,11 +56,13 @@ export const StyledTable = styled.table`
|
|
|
68
56
|
export const TabelaHeader = styled.thead<ColorsProp>`
|
|
69
57
|
${({ theme, color }) => css`
|
|
70
58
|
th {
|
|
71
|
-
min-width:
|
|
59
|
+
min-width: 260px;
|
|
72
60
|
text-align: center;
|
|
73
|
-
padding: 5px;
|
|
61
|
+
padding: 0 5px;
|
|
62
|
+
max-height: 24px;
|
|
74
63
|
background-color: ${color};
|
|
75
64
|
opacity: 0.8;
|
|
65
|
+
font-size: ${theme.sizes.small};
|
|
76
66
|
color: ${theme.colors.white};
|
|
77
67
|
font-weight: bold;
|
|
78
68
|
}
|
|
@@ -102,8 +92,8 @@ export const TabelaHeader = styled.thead<ColorsProp>`
|
|
|
102
92
|
`}
|
|
103
93
|
`;
|
|
104
94
|
|
|
105
|
-
export const TabelaBody = styled.tbody<{ color?: string;
|
|
106
|
-
${({ theme, color,
|
|
95
|
+
export const TabelaBody = styled.tbody<{ color?: string; hasselect?: string }>`
|
|
96
|
+
${({ theme, color, hasselect }) => css`
|
|
107
97
|
/* height: 430px; */
|
|
108
98
|
width: 100%;
|
|
109
99
|
|
|
@@ -114,8 +104,7 @@ export const TabelaBody = styled.tbody<{ color?: string; hasSelect?: boolean }>`
|
|
|
114
104
|
|
|
115
105
|
.expand {
|
|
116
106
|
width: 50px;
|
|
117
|
-
|
|
118
|
-
left: ${hasSelect ? '52px' : '17px'};
|
|
107
|
+
left: ${hasselect === 'true' ? '52px' : '17px'};
|
|
119
108
|
background-color: ${color};
|
|
120
109
|
display: flex;
|
|
121
110
|
align-items: center;
|