@addev-be/ui 0.9.1 → 0.10.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@addev-be/ui",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "watch": "tsc -b --watch",
@@ -73,7 +73,7 @@ export const LoginForm: FC = () => {
73
73
 
74
74
  {error && <Message type="error">{error}</Message>}
75
75
 
76
- <Button color="primary" onClick={onLoginClicked}>
76
+ <Button $color="primary" onClick={onLoginClicked}>
77
77
  Se connecter
78
78
  </Button>
79
79
 
@@ -41,7 +41,7 @@ export const PasswordRecoveryForm: FC = () => {
41
41
  />
42
42
  </StackedLabel>
43
43
 
44
- <Button color="primary" onClick={onSubmitClicked}>
44
+ <Button $color="primary" onClick={onSubmitClicked}>
45
45
  Envoyer un lien de récupération
46
46
  </Button>
47
47
  </>
@@ -100,7 +100,7 @@ export const PasswordResetForm: FC = () => {
100
100
  </Message>
101
101
  )}
102
102
 
103
- <Button color="primary" onClick={onSubmitClicked}>
103
+ <Button $color="primary" onClick={onSubmitClicked}>
104
104
  Réinitialiser le mot de passe
105
105
  </Button>
106
106
  </>
@@ -43,7 +43,7 @@ export const useDataGridColumnsModal = <R,>(context: DataGridContext<R>) => {
43
43
  <Modal.Footer>
44
44
  <Button onClick={closeModal}>Annuler</Button>
45
45
  <Button
46
- color="primary"
46
+ $color="primary"
47
47
  style={{ marginLeft: 'auto' }}
48
48
  onClick={onApplyClicked}
49
49
  >
@@ -55,12 +55,12 @@ export const useFilterModal = <R,>({
55
55
  />
56
56
  </Modal.ContentWithIcon>
57
57
  <Modal.Buttons>
58
- <Button color="danger" icon={FilterSlashIcon} onClick={onClearClicked}>
58
+ <Button $color="danger" icon={FilterSlashIcon} onClick={onClearClicked}>
59
59
  Supprimer
60
60
  </Button>
61
61
  <Button
62
62
  style={{ marginLeft: 'auto' }}
63
- color="primary"
63
+ $color="primary"
64
64
  icon={FilterIcon}
65
65
  onClick={onApplyClicked}
66
66
  >
@@ -46,6 +46,7 @@ import {
46
46
  import { ContextMenu } from '../../../ui/ContextMenu';
47
47
  import { FilterValuesScroller } from './FilterValuesScroller';
48
48
  import { Input } from '../../../forms';
49
+ import { MenuContainer } from '../../../ui/ContextMenu/styles';
49
50
  import { useFilterModal } from './hooks';
50
51
 
51
52
  type FilterValuesProps<R> = {
@@ -53,6 +54,8 @@ type FilterValuesProps<R> = {
53
54
  columnIndex: number;
54
55
  context: DataGridContext<R>;
55
56
  onClose?: () => void;
57
+ contextMenu?: boolean;
58
+ showTotalButton?: boolean;
56
59
  };
57
60
 
58
61
  const sortAsc: Record<DataGridFilterType, [string, IconFC]> = {
@@ -87,6 +90,8 @@ export const DataGridFilterMenu = <R,>({
87
90
  columnKey,
88
91
  context,
89
92
  onClose,
93
+ contextMenu = true,
94
+ showTotalButton = true,
90
95
  }: FilterValuesProps<R>) => {
91
96
  const { openModal, modal } = useFilterModal({ columnKey, context });
92
97
  const {
@@ -258,8 +263,8 @@ export const DataGridFilterMenu = <R,>({
258
263
  onClose?.();
259
264
  }, [columnKey, onClose, setFooters]);
260
265
 
261
- return (
262
- <ContextMenu>
266
+ const content = (
267
+ <>
263
268
  {modal}
264
269
  {column.sortGetter && (
265
270
  <>
@@ -283,48 +288,55 @@ export const DataGridFilterMenu = <R,>({
283
288
  <ContextMenu.Divider />
284
289
  </>
285
290
  )}
286
- {!isFooterVisible && typeof column.footer === 'function' && (
291
+ {showTotalButton && (
287
292
  <>
288
- <ContextMenu.Item onClick={() => showFooter('count')}>
289
- <TableFooterIcon />
290
- Afficher le total
291
- </ContextMenu.Item>
292
- <ContextMenu.Divider />
293
- </>
294
- )}
295
- {typeof column.footer === 'object' && (
296
- <>
297
- <ContextMenu.ParentItem>
298
- <TableFooterIcon />
299
- Afficher le total
300
- <ContextMenu.SubMenu>
301
- {Object.keys(column.footer).map((key) => {
302
- const TotalIcon =
303
- footerFunctionsIcons[
304
- key as DataGridFooterPredefinedFunction
305
- ] ?? TableFooterIcon;
306
- return (
307
- <ContextMenu.Item key={key} onClick={() => showFooter(key)}>
308
- <TotalIcon />
309
- {key in footerFunctionsTexts
310
- ? footerFunctionsTexts[
311
- key as DataGridFooterPredefinedFunction
312
- ]
313
- : key}
293
+ {!isFooterVisible && typeof column.footer === 'function' && (
294
+ <>
295
+ <ContextMenu.Item onClick={() => showFooter('count')}>
296
+ <TableFooterIcon />
297
+ Afficher le total
298
+ </ContextMenu.Item>
299
+ <ContextMenu.Divider />
300
+ </>
301
+ )}
302
+ {showTotalButton && typeof column.footer === 'object' && (
303
+ <>
304
+ <ContextMenu.ParentItem>
305
+ <TableFooterIcon />
306
+ Afficher le total
307
+ <ContextMenu.SubMenu>
308
+ {Object.keys(column.footer).map((key) => {
309
+ const TotalIcon =
310
+ footerFunctionsIcons[
311
+ key as DataGridFooterPredefinedFunction
312
+ ] ?? TableFooterIcon;
313
+ return (
314
+ <ContextMenu.Item
315
+ key={key}
316
+ onClick={() => showFooter(key)}
317
+ >
318
+ <TotalIcon />
319
+ {key in footerFunctionsTexts
320
+ ? footerFunctionsTexts[
321
+ key as DataGridFooterPredefinedFunction
322
+ ]
323
+ : key}
324
+ </ContextMenu.Item>
325
+ );
326
+ })}
327
+ <ContextMenu.Divider />
328
+ <ContextMenu.Item
329
+ onClick={hideFooter}
330
+ disabled={!isFooterVisible}
331
+ >
332
+ <TableFooterSlashIcon />
333
+ Masquer le total
314
334
  </ContextMenu.Item>
315
- );
316
- })}
335
+ </ContextMenu.SubMenu>
336
+ </ContextMenu.ParentItem>
317
337
  <ContextMenu.Divider />
318
- <ContextMenu.Item
319
- onClick={hideFooter}
320
- disabled={!isFooterVisible}
321
- >
322
- <TableFooterSlashIcon />
323
- Masquer le total
324
- </ContextMenu.Item>
325
- </ContextMenu.SubMenu>
326
- </ContextMenu.ParentItem>
327
- <ContextMenu.Divider />
338
+ </>
339
+ )}
328
340
  </>
329
341
  )}
330
342
  <ContextMenu.Item onClick={openModal}>
@@ -355,6 +367,12 @@ export const DataGridFilterMenu = <R,>({
355
367
  <styles.CheckboxesContainer>
356
368
  {checkboxesComponent}
357
369
  </styles.CheckboxesContainer>
358
- </ContextMenu>
370
+ </>
359
371
  );
372
+
373
+ if (!contextMenu) {
374
+ return <MenuContainer>{content}</MenuContainer>;
375
+ }
376
+
377
+ return <ContextMenu>{content}</ContextMenu>;
360
378
  };
@@ -76,16 +76,16 @@ export const DataGridHeader = <R,>({
76
76
  Rafraîchir
77
77
  </Button>
78
78
  )}
79
- <Button color="emerald" size="small" onClick={runCopyTable}>
79
+ <Button $color="emerald" size="small" onClick={runCopyTable}>
80
80
  <CopyIcon />
81
81
  Copier la table
82
82
  </Button>
83
- <Button size="small" color="danger" onClick={() => setFilters({})}>
83
+ <Button size="small" $color="danger" onClick={() => setFilters({})}>
84
84
  <FilterSlashIcon />
85
85
  Supprimer les filtres
86
86
  </Button>
87
87
  {name && (
88
- <Button color="info" size="small" onClick={openModal}>
88
+ <Button $color="info" size="small" onClick={openModal}>
89
89
  <TableColumnsIcon />
90
90
  Paramètres des colonnes
91
91
  </Button>
@@ -108,7 +108,7 @@ export const DataGridHeaderCell = <R,>({
108
108
  className={hasFilters ? 'danger' : ''}
109
109
  ref={filterButtonRef}
110
110
  icon={ChevronDownIcon}
111
- color={hasFilters ? 'danger' : headerColor}
111
+ $color={hasFilters ? 'danger' : headerColor}
112
112
  onClick={onFilterButtonClicked}
113
113
  />
114
114
  )}
@@ -66,6 +66,7 @@ export const DataGridInner = <R,>(
66
66
  >
67
67
  <DataGridHeader context={DataGridContext} />
68
68
  <VirtualScroller<R, { context: Context<DataGridContextProps<R>> }>
69
+ integrated
69
70
  gridTemplateColumns={gridTemplateColumns}
70
71
  items={sortedRows}
71
72
  itemTemplate={rowTemplate}
@@ -1,7 +1,7 @@
1
1
  import styled, { css } from 'styled-components';
2
2
 
3
3
  import { ThemeColor } from '../../../providers/ThemeProvider/types';
4
- import { VirtualScrollerContainer } from '../VirtualScroller/styles';
4
+ import { VirtualScrollerFiller } from '../VirtualScroller/styles';
5
5
 
6
6
  export const TOOLBAR_HEIGHT = 40;
7
7
  export const DEFAULT_HEADER_ROW_HEIGHT = 40;
@@ -168,7 +168,7 @@ export const DataGridContainer = styled.div<{
168
168
  box-sizing: border-box;
169
169
  }
170
170
 
171
- ${VirtualScrollerContainer} {
171
+ ${VirtualScrollerFiller} {
172
172
  grid-column-start: 1;
173
173
  grid-column-end: -1;
174
174
  grid-row: 3;
@@ -3,6 +3,7 @@
3
3
  import { SqlRequestDataGridColumn, SqlRequestDataGridColumns } from '../types';
4
4
  import {
5
5
  buildExcelFormat,
6
+ dateFilter,
6
7
  numberFilter,
7
8
  textFilter,
8
9
  } from '../../DataGrid/helpers';
@@ -110,7 +111,7 @@ export const sqlDateColumn = <R extends Record<string, any>>(
110
111
  excelFormatter: () => 'dd/mm/yyyy',
111
112
  excelValue: (value) => formatDate(value, 'YYYY-MM-DD'),
112
113
  filter: {
113
- ...textFilter(key),
114
+ ...dateFilter(key),
114
115
  getter: (value) => value[key] ?? '',
115
116
  formatter: (value) => formatDate(value),
116
117
  renderer: (value) => formatDate(value),
@@ -134,7 +135,7 @@ export const sqlDateTimeColumn = <R extends Record<string, any>>(
134
135
  excelFormatter: () => 'dd/mm/yyyy hh:mm:ss',
135
136
  excelValue: (value) => formatDateTime(value, 'YYYY-MM-DD HH:mm:ss'),
136
137
  filter: {
137
- ...textFilter(key),
138
+ ...dateFilter(key),
138
139
  getter: (value) => value[key] ?? '',
139
140
  formatter: (value) => formatDateTime(value),
140
141
  renderer: (value) => formatDateTime(value),
@@ -172,7 +173,7 @@ export const sqlNumberColumn = <R extends Record<string, any>>(
172
173
  excelFormatter: () => buildExcelFormat(decimals),
173
174
  excelValue: (value) => formatNumberInvariant(value, decimals),
174
175
  getter: (row) => row[key] ?? '',
175
- sortGetter: (row) => row[key] ?? '',
176
+ sortGetter: (row) => row[key] ?? 0,
176
177
  filter: {
177
178
  ...numberFilter(key),
178
179
  getter: (value) => value[key] ?? 0,
@@ -202,7 +203,7 @@ export const sqlMoneyColumn = <R extends Record<string, any>>(
202
203
  excelFormatter: () => buildExcelFormat(decimals, ' €'),
203
204
  excelValue: (value) => formatNumberInvariant(value, decimals),
204
205
  getter: (row) => row[key] ?? '',
205
- sortGetter: (row) => row[key] ?? '',
206
+ sortGetter: (row) => row[key] ?? 0,
206
207
  filter: {
207
208
  ...numberFilter(key),
208
209
  getter: (value) => value[key] ?? 0,
@@ -231,7 +232,7 @@ export const sqlPercentageColumn = <R extends Record<string, any>>(
231
232
  excelFormatter: () => buildExcelFormat(decimals, '%'),
232
233
  excelValue: (value) => formatNumberInvariant(value, decimals),
233
234
  getter: (row) => row[key] ?? '',
234
- sortGetter: (row) => row[key] ?? '',
235
+ sortGetter: (row) => row[key] ?? 0,
235
236
  filter: {
236
237
  ...numberFilter(key),
237
238
  getter: (value) => value[key] ?? 0,
@@ -0,0 +1,102 @@
1
+ import * as styles from './styles';
2
+
3
+ import {
4
+ CircleXMarkIcon,
5
+ FilterSlashIcon,
6
+ LeftIcon,
7
+ RightIcon,
8
+ } from '../../../../Icons';
9
+ import { MouseEvent, useCallback, useState } from 'react';
10
+
11
+ import { DataGridContext } from '../../DataGrid/types';
12
+ import { DataGridFilterMenu } from '../../DataGrid/DataGridFilterMenu';
13
+ import { IconButton } from '../../../forms';
14
+ import { SqlRequestGridProps } from '../types';
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
17
+ type FiltersSidebarProps<R, P extends object = {}> = {
18
+ props: SqlRequestGridProps<R, P>;
19
+ context: DataGridContext<R>;
20
+ onClose?: () => void;
21
+ onClearFilters?: () => void;
22
+ };
23
+
24
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
25
+ export const FiltersSidebar = <R, P extends object = {}>({
26
+ props,
27
+ context,
28
+ onClose,
29
+ onClearFilters,
30
+ }: FiltersSidebarProps<R, P>) => {
31
+ const [columnKey, setColumnKey] = useState<string | null>(null);
32
+ const column = columnKey ? props.fields[columnKey] : null;
33
+ const visible = !!(columnKey && column);
34
+
35
+ const stopPropagation = useCallback((e: MouseEvent) => {
36
+ e.stopPropagation();
37
+ }, []);
38
+
39
+ const onFilterClick = useCallback((columnKey: string | null) => {
40
+ setColumnKey(columnKey);
41
+ }, []);
42
+
43
+ return (
44
+ <styles.Backdrop onClick={onClose}>
45
+ <styles.FiltersSidebarContainer onClick={stopPropagation}>
46
+ {visible ? (
47
+ <>
48
+ <styles.FiltersSidebarHeader>
49
+ <IconButton
50
+ rounded
51
+ icon={LeftIcon}
52
+ color="secondary"
53
+ onClick={() => onFilterClick(null)}
54
+ />
55
+ <h3>{column.name}</h3>
56
+ <IconButton
57
+ rounded
58
+ icon={CircleXMarkIcon}
59
+ color="secondary"
60
+ onClick={onClose}
61
+ />
62
+ </styles.FiltersSidebarHeader>
63
+ <DataGridFilterMenu
64
+ contextMenu={false}
65
+ columnKey={columnKey}
66
+ columnIndex={0}
67
+ context={context}
68
+ showTotalButton={false}
69
+ />
70
+ </>
71
+ ) : (
72
+ <>
73
+ <styles.FiltersSidebarHeader>
74
+ <h3>Filtres</h3>
75
+ <IconButton
76
+ rounded
77
+ icon={FilterSlashIcon}
78
+ color="danger"
79
+ onClick={onClearFilters}
80
+ />
81
+ <IconButton
82
+ rounded
83
+ icon={CircleXMarkIcon}
84
+ color="secondary"
85
+ onClick={onClose}
86
+ />
87
+ </styles.FiltersSidebarHeader>
88
+ {Object.entries(props.fields).map(([key, field], index) => (
89
+ <styles.FiltersSidebarFilter
90
+ key={index}
91
+ onClick={() => onFilterClick(key)}
92
+ >
93
+ {field.name}
94
+ <RightIcon />
95
+ </styles.FiltersSidebarFilter>
96
+ ))}
97
+ </>
98
+ )}
99
+ </styles.FiltersSidebarContainer>
100
+ </styles.Backdrop>
101
+ );
102
+ };
@@ -0,0 +1,83 @@
1
+ import {
2
+ Divider,
3
+ MenuContainer,
4
+ MenuItemContainer,
5
+ } from '../../../ui/ContextMenu/styles';
6
+
7
+ import styled from 'styled-components';
8
+
9
+ export const FiltersSidebarContainer = styled.div`
10
+ position: absolute;
11
+ left: 0;
12
+ top: 0;
13
+ bottom: 0;
14
+ width: 100%;
15
+ max-width: var(--space-96);
16
+ border-radius: var(--rounded-none);
17
+ padding: 0;
18
+ z-index: 1;
19
+ overflow-y: auto;
20
+ box-sizing: border-box;
21
+ background-color: var(--color-neutral-50);
22
+ border-right: 1px solid var(--color-neutral-300);
23
+ box-shadow: var(--shadow-sm);
24
+
25
+ & > ${MenuContainer} {
26
+ position: relative;
27
+ background: none;
28
+ padding: var(--space-2) 0;
29
+
30
+ ${MenuItemContainer} {
31
+ background: none;
32
+ /* height: var(--size-6); */
33
+ padding: var(--space-2) var(--space-4);
34
+ }
35
+
36
+ ${Divider} {
37
+ margin: var(--space-2) 0;
38
+ }
39
+ }
40
+ `;
41
+
42
+ export const FiltersSidebarHeader = styled.div`
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ gap: var(--space-4);
47
+ padding: var(--space-2);
48
+
49
+ padding-bottom: var(--space-2);
50
+ border-bottom: 1px solid var(--color-neutral-300);
51
+
52
+ h3 {
53
+ margin: 0;
54
+ margin-right: auto;
55
+ }
56
+ `;
57
+
58
+ export const FiltersSidebarFilter = styled.div`
59
+ display: flex;
60
+ align-items: center;
61
+ padding: var(--space-4) var(--space-2);
62
+ border-bottom: 1px solid var(--color-neutral-300);
63
+ cursor: pointer;
64
+
65
+ &:hover {
66
+ background-color: var(--color-neutral-100);
67
+ }
68
+ svg {
69
+ margin-left: auto;
70
+ width: var(--space-4);
71
+ height: var(--space-4);
72
+ }
73
+ `;
74
+
75
+ export const Backdrop = styled.div`
76
+ position: sticky;
77
+ top: 0;
78
+ left: 0;
79
+ width: 100%;
80
+ height: 100%;
81
+ z-index: 1;
82
+ background-color: rgba(0, 0, 0, 0.5);
83
+ `;
@@ -15,30 +15,53 @@ import {
15
15
  useRef,
16
16
  useState,
17
17
  } from 'react';
18
- import { SqlRequestGridProps, SqlRequestGridRefProps } from './types';
18
+ import {
19
+ SqlRequestGridFilters,
20
+ SqlRequestGridProps,
21
+ SqlRequestGridRefProps,
22
+ } from './types';
23
+ import { debounce, pickBy } from 'lodash';
19
24
 
25
+ import { DataGridSort } from '../DataGrid/types';
26
+ import { FilterFullIcon } from '../../../Icons';
27
+ import { FiltersSidebar } from './filters/FiltersSidebar';
28
+ import { IconButton } from '../../forms';
20
29
  import { VirtualScroller } from '../VirtualScroller';
21
- import { debounce } from 'lodash';
30
+ import { convertSqlFiltersToConditions } from './helpers';
22
31
  import { isColumnVisible } from '../DataGrid/helpers';
32
+ import { useDataGrid } from '../DataGrid/hooks';
23
33
 
24
34
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
25
35
  export const SqlRequestGridInner = <R, P extends object = {}>(
26
- {
36
+ allProps: SqlRequestGridProps<R, P>,
37
+ ref: ForwardedRef<SqlRequestGridRefProps>
38
+ ) => {
39
+ const {
27
40
  rowHeight = styles.DEFAULT_ROW_HEIGHT,
28
41
  itemsPerRow,
29
42
  itemTemplate,
30
43
  itemProps,
31
44
  gap,
32
45
  ...props
33
- }: SqlRequestGridProps<R, P>,
34
- ref: ForwardedRef<SqlRequestGridRefProps>
35
- ) => {
46
+ } = allProps;
36
47
  const currentRows = useRef<R[]>([]);
37
48
  const [rows, setRows] = useState<R[]>([]);
38
49
  const [start, setStart] = useState(0);
39
50
  const [length, setLength] = useState(50);
40
51
  const [count, setCount] = useState(-1);
41
52
  const [sqlRequest] = useSqlRequestHandler<R>(props.type);
53
+ const [conditions, setConditions] = useState<Record<string, ConditionDTO>>(
54
+ {}
55
+ );
56
+ const [orderBy, setOrderBy] = useState<OrderByDTO[]>(
57
+ Object.entries(props.initialSorts ?? {}).map(
58
+ ([columnKey, direction]): OrderByDTO => ({
59
+ field: props.fields[columnKey].field?.fieldAlias ?? columnKey,
60
+ type: props.fields[columnKey].type ?? 'text',
61
+ direction: direction.toUpperCase() as 'ASC' | 'DESC',
62
+ })
63
+ )
64
+ );
42
65
 
43
66
  const [columnsKeys, visibleColumnsKeys] = useMemo(
44
67
  () => [
@@ -107,6 +130,69 @@ export const SqlRequestGridInner = <R, P extends object = {}>(
107
130
  )
108
131
  );
109
132
 
133
+ const refresh = useCallback(() => {
134
+ setRows([]);
135
+ setStart(0);
136
+ setLength(50);
137
+ setCount(-1);
138
+ }, []);
139
+
140
+ const onFiltersChanged = useCallback(
141
+ (filters: SqlRequestGridFilters) => {
142
+ const newConditions = convertSqlFiltersToConditions(filters);
143
+ setConditions(newConditions);
144
+ refresh();
145
+ },
146
+ [refresh]
147
+ );
148
+
149
+ const onSortsChanged = useCallback(
150
+ (sorts: Record<string, DataGridSort>) => {
151
+ refresh();
152
+ const newOrderBy = Object.entries(sorts).map(
153
+ ([columnKey, direction]) =>
154
+ ({
155
+ field: columnKey,
156
+ direction: direction.toUpperCase(),
157
+ } as OrderByDTO)
158
+ );
159
+ setOrderBy(newOrderBy);
160
+ },
161
+ [refresh]
162
+ );
163
+
164
+ const loadFilterValues = useCallback(
165
+ (columnKey: string) => {
166
+ return sqlRequest({
167
+ columns: columnsKeys,
168
+ returnColumns: [columnKey],
169
+ columnTypes: [props.fields[columnKey].type ?? 'text'],
170
+ conditions: [
171
+ ...(props.conditions ?? []),
172
+ ...Object.values(pickBy(conditions, (_, key) => key !== columnKey)),
173
+ ].filter((condition) => condition.field !== columnKey),
174
+ orderBy: [
175
+ {
176
+ field:
177
+ props.fields[columnKey].filterField ??
178
+ props.fields[columnKey].field?.fieldAlias ??
179
+ props.fields[columnKey].field?.fieldName ??
180
+ columnKey,
181
+ type: props.fields[columnKey].type ?? 'text',
182
+ direction: 'ASC',
183
+ },
184
+ ],
185
+ getCount: false,
186
+ unique: true,
187
+ }).then((response) =>
188
+ response.data.map(
189
+ (row) => props.fields[columnKey].filter?.getter?.(row) ?? null
190
+ )
191
+ );
192
+ },
193
+ [columnsKeys, conditions, props.fields, props.conditions, sqlRequest]
194
+ );
195
+
110
196
  useEffect(() => {
111
197
  if (
112
198
  !rows.length ||
@@ -115,8 +201,8 @@ export const SqlRequestGridInner = <R, P extends object = {}>(
115
201
  loadRows.current(
116
202
  columnsKeys,
117
203
  visibleColumnsKeys,
118
- props.conditions,
119
- props.orderBy,
204
+ [...(props.conditions ?? []), ...Object.values(conditions)],
205
+ orderBy,
120
206
  start,
121
207
  length,
122
208
  count < 0
@@ -127,11 +213,12 @@ export const SqlRequestGridInner = <R, P extends object = {}>(
127
213
  start,
128
214
  length,
129
215
  count,
130
- props.conditions,
216
+ conditions,
131
217
  columnsKeys,
132
218
  visibleColumnsKeys,
133
219
  rows,
134
- props.orderBy,
220
+ orderBy,
221
+ props.conditions,
135
222
  ]);
136
223
 
137
224
  const onVisibleRowsChanged = useCallback(
@@ -144,13 +231,6 @@ export const SqlRequestGridInner = <R, P extends object = {}>(
144
231
  [length, start]
145
232
  );
146
233
 
147
- const refresh = useCallback(() => {
148
- setRows([]);
149
- setStart(0);
150
- setLength(50);
151
- setCount(-1);
152
- }, []);
153
-
154
234
  useImperativeHandle(
155
235
  ref,
156
236
  () => ({
@@ -159,19 +239,69 @@ export const SqlRequestGridInner = <R, P extends object = {}>(
159
239
  [refresh]
160
240
  );
161
241
 
242
+ const [contextProps, DataGridContext] = useDataGrid({
243
+ columns: props.fields,
244
+ rows,
245
+ onFiltersChanged,
246
+ filterValuesLoader: loadFilterValues,
247
+ onSortsChanged,
248
+ onSelectionChange: () => {},
249
+ onSelectedRowsChanged: () => {},
250
+ selectable: false,
251
+ rowKey: () => '',
252
+ filter: false,
253
+ sort: false,
254
+ });
255
+
256
+ const [sidebarVisible, setSidebarVisible] = useState(false);
257
+ const onSidebarClose = useCallback(() => {
258
+ setSidebarVisible(false);
259
+ }, []);
260
+ const onClearFilters = useCallback(() => {
261
+ onFiltersChanged({});
262
+ setSidebarVisible(false);
263
+ }, [onFiltersChanged]);
264
+
162
265
  return (
163
- <styles.SqlRequestGridContainer>
164
- <VirtualScroller
165
- gridTemplateColumns={`repeat(${itemsPerRow}, 1fr)`}
166
- items={rows}
167
- itemTemplate={itemTemplate}
168
- itemsPerRow={itemsPerRow}
169
- itemProps={itemProps}
170
- rowHeightInPx={rowHeight}
171
- onRangeChanged={onVisibleRowsChanged}
172
- gap={gap}
173
- />
174
- </styles.SqlRequestGridContainer>
266
+ <DataGridContext.Provider value={contextProps}>
267
+ <styles.SqlRequestGridContainer>
268
+ {sidebarVisible ? (
269
+ <>
270
+ <FiltersSidebar
271
+ props={allProps}
272
+ context={DataGridContext}
273
+ onClose={onSidebarClose}
274
+ onClearFilters={onClearFilters}
275
+ />
276
+ </>
277
+ ) : (
278
+ <IconButton
279
+ size="large"
280
+ onClick={() => setSidebarVisible(true)}
281
+ icon={FilterFullIcon}
282
+ color="primary"
283
+ rounded
284
+ style={{
285
+ position: 'absolute',
286
+ bottom: 'var(--space-2)',
287
+ right: 'var(--space-2)',
288
+ zIndex: 1,
289
+ }}
290
+ />
291
+ )}
292
+
293
+ <VirtualScroller
294
+ gridTemplateColumns={`repeat(${itemsPerRow}, 1fr)`}
295
+ items={rows}
296
+ itemTemplate={itemTemplate}
297
+ itemsPerRow={itemsPerRow}
298
+ itemProps={itemProps}
299
+ rowHeightInPx={rowHeight}
300
+ onRangeChanged={onVisibleRowsChanged}
301
+ gap={gap}
302
+ />
303
+ </styles.SqlRequestGridContainer>
304
+ </DataGridContext.Provider>
175
305
  );
176
306
  };
177
307
 
@@ -1,3 +1,4 @@
1
+ import { VirtualScrollerContainer } from '../VirtualScroller/styles';
1
2
  import styled from 'styled-components';
2
3
 
3
4
  export const DEFAULT_ROW_HEIGHT = 200;
@@ -8,43 +9,12 @@ export const SqlRequestGridContainer = styled.div.attrs({
8
9
  height: 100%;
9
10
  width: 100%;
10
11
  overflow: auto;
11
- `;
12
-
13
- export const TopPaddingRow = styled.div``;
14
- export const BottomPaddingRow = styled.div``;
15
-
16
- export const VirtualScrollerContainer = styled.div<{
17
- $height: number;
18
- }>`
19
12
  position: relative;
20
- overflow: visible;
21
- height: ${({ $height }) => `${$height}px`};
22
- `;
23
- export const VirtualScrollerRowsContainer = styled.div.attrs<{
24
- $gridTemplateColumns: string;
25
- $topPadding: number;
26
- $rowHeight?: number;
27
- }>(({ $gridTemplateColumns, $topPadding, $rowHeight = DEFAULT_ROW_HEIGHT }) => {
28
- const rowHeightValue = `${$rowHeight}px`;
29
- return {
30
- style: {
31
- top: `${$topPadding}px`,
32
- gridTemplateColumns: $gridTemplateColumns,
33
- gridAutoRows: rowHeightValue,
34
- },
35
- };
36
- })`
37
- display: grid;
38
- position: absolute;
39
13
 
40
- ${TopPaddingRow} {
41
- grid-column-start: 1;
42
- grid-column-end: -1;
43
- grid-row: 1;
44
- }
45
- ${BottomPaddingRow} {
46
- grid-column-start: 1;
47
- grid-column-end: -1;
48
- grid-row: -1;
14
+ ${VirtualScrollerContainer} {
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ right: 0;
49
19
  }
50
20
  `;
@@ -10,6 +10,7 @@ import {
10
10
  DataGridColumn,
11
11
  DataGridFilter,
12
12
  DataGridFilterType,
13
+ DataGridSort,
13
14
  } from '../DataGrid/types';
14
15
 
15
16
  import { VirtualScrollerTemplateFC } from '../VirtualScroller/types';
@@ -44,6 +45,7 @@ export type SqlRequestGridProps<R, P extends object = {}> = {
44
45
  conditions?: ConditionDTO[];
45
46
  parser?: (row: SqlRequestRow<R>) => R;
46
47
  itemProps: P;
48
+ initialSorts?: Record<string, DataGridSort>;
47
49
  };
48
50
 
49
51
  export type SqlRequestGridRefProps = {
@@ -17,6 +17,7 @@ type VirtualScrollerProps<R, P extends object> = {
17
17
  tolerance?: number;
18
18
  itemProps: P;
19
19
  onRangeChanged?: (index: number, length: number) => void;
20
+ integrated?: boolean;
20
21
  };
21
22
 
22
23
  export const VirtualScroller = <R, P extends object>({
@@ -29,6 +30,7 @@ export const VirtualScroller = <R, P extends object>({
29
30
  gap,
30
31
  itemsPerRow,
31
32
  tolerance,
33
+ integrated,
32
34
  }: VirtualScrollerProps<R, P>) => {
33
35
  const [scrollableElement, setScrollableElement] =
34
36
  useState<HTMLElement | null>(null);
@@ -51,25 +53,33 @@ export const VirtualScroller = <R, P extends object>({
51
53
  }, [indexWithTolerance, lengthWithTolerance, onRangeChanged]);
52
54
 
53
55
  return (
54
- <styles.VirtualScrollerContainer
55
- $height={totalHeight}
56
- ref={(element) => setScrollableElement(element?.parentElement ?? null)}
57
- >
58
- <styles.VirtualScrollerItemsContainer
59
- $gridTemplateColumns={gridTemplateColumns}
60
- $topPadding={topPadding}
61
- $itemHeight={rowHeightInPx}
62
- $gap={gap}
56
+ <styles.VirtualScrollerContainer $integrated={integrated}>
57
+ <styles.VirtualScrollerFiller
58
+ $height={totalHeight}
59
+ ref={(element) =>
60
+ setScrollableElement(
61
+ (integrated
62
+ ? element?.parentElement?.parentElement
63
+ : element?.parentElement) ?? null
64
+ )
65
+ }
63
66
  >
64
- {visibleItems.map((item, currentIndex) => (
65
- <ItemTemplate
66
- key={indexWithTolerance + currentIndex}
67
- item={item}
68
- index={currentIndex}
69
- {...itemProps}
70
- />
71
- ))}
72
- </styles.VirtualScrollerItemsContainer>
67
+ <styles.VirtualScrollerItemsContainer
68
+ $gridTemplateColumns={gridTemplateColumns}
69
+ $topPadding={topPadding}
70
+ $itemHeight={rowHeightInPx}
71
+ $gap={gap}
72
+ >
73
+ {visibleItems.map((item, currentIndex) => (
74
+ <ItemTemplate
75
+ key={indexWithTolerance + currentIndex}
76
+ item={item}
77
+ index={currentIndex}
78
+ {...itemProps}
79
+ />
80
+ ))}
81
+ </styles.VirtualScrollerItemsContainer>
82
+ </styles.VirtualScrollerFiller>
73
83
  </styles.VirtualScrollerContainer>
74
84
  );
75
85
  };
@@ -5,7 +5,16 @@ export const DEFAULT_ROW_HEIGHT = 32;
5
5
  export const TopPaddingItem = styled.div``;
6
6
  export const BottomPaddingItem = styled.div``;
7
7
 
8
- export const VirtualScrollerContainer = styled.div.attrs<{
8
+ export const VirtualScrollerContainer = styled.div<{
9
+ $integrated?: boolean;
10
+ }>`
11
+ position: relative;
12
+ overflow: auto;
13
+ height: 100%;
14
+ display: ${({ $integrated }) => ($integrated ? 'contents' : 'block')};
15
+ `;
16
+
17
+ export const VirtualScrollerFiller = styled.div.attrs<{
9
18
  $height: number;
10
19
  }>(({ $height }) => ({
11
20
  className: 'VirtualScrollerFiller',
@@ -4,9 +4,14 @@ import styled, { css } from 'styled-components';
4
4
  import { IconFC } from '../../Icons';
5
5
  import { ThemeColor } from '../../providers/ThemeProvider/types';
6
6
 
7
+ type StyledButtonProps = {
8
+ $color?: ThemeColor;
9
+ $rounded?: boolean;
10
+ };
11
+
7
12
  export const StyledButton = styled.button.withConfig({
8
13
  shouldForwardProp: () => true,
9
- })<ButtonProps>`
14
+ })<StyledButtonProps>`
10
15
  display: inline-flex;
11
16
  vertical-align: middle;
12
17
  align-items: center;
@@ -17,18 +22,18 @@ export const StyledButton = styled.button.withConfig({
17
22
  font-family: var(--font-sans);
18
23
  cursor: pointer;
19
24
 
20
- ${({ color }) =>
21
- color
25
+ ${({ $color }) =>
26
+ $color
22
27
  ? css`
23
- background-color: var(--color-${color}-400);
24
- border: 1px solid var(--color-${color}-500);
28
+ background-color: var(--color-${$color}-400);
29
+ border: 1px solid var(--color-${$color}-500);
25
30
  &:hover {
26
- background-color: var(--color-${color}-300);
27
- border: 1px solid var(--color-${color}-400);
31
+ background-color: var(--color-${$color}-300);
32
+ border: 1px solid var(--color-${$color}-400);
28
33
  }
29
- color: var(--color-${color}-950);
34
+ color: var(--color-${$color}-950);
30
35
  svg {
31
- fill: var(--color-${color}-950);
36
+ fill: var(--color-${$color}-950);
32
37
  }
33
38
  `
34
39
  : css`
@@ -44,7 +49,7 @@ export const StyledButton = styled.button.withConfig({
44
49
  }
45
50
  `}
46
51
 
47
- ${({ rounded }) => rounded && 'border-radius: var(--rounded-full);'}
52
+ ${({ $rounded }) => $rounded && 'border-radius: var(--rounded-full);'}
48
53
 
49
54
  &.small {
50
55
  padding: var(--space-1) var(--space-1_5);
@@ -91,9 +96,28 @@ export type ButtonProps = {
91
96
  export const Button = forwardRef<
92
97
  HTMLButtonElement,
93
98
  ComponentProps<typeof StyledButton> & ButtonProps
94
- >(({ children, size = 'medium', icon: Icon, className, ...props }, ref) => (
95
- <StyledButton ref={ref} className={`${size} ${className}`} {...props}>
96
- {Icon && <Icon />}
97
- {children}
98
- </StyledButton>
99
- ));
99
+ >(
100
+ (
101
+ {
102
+ children,
103
+ size = 'medium',
104
+ icon: Icon,
105
+ className,
106
+ rounded = false,
107
+ color = 'secondary',
108
+ ...props
109
+ },
110
+ ref
111
+ ) => (
112
+ <StyledButton
113
+ ref={ref}
114
+ className={`${size} ${className}`}
115
+ $color={color}
116
+ $rounded={rounded}
117
+ {...props}
118
+ >
119
+ {Icon && <Icon />}
120
+ {children}
121
+ </StyledButton>
122
+ )
123
+ );
@@ -0,0 +1,90 @@
1
+ import { ComponentProps, forwardRef } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import { ThemeColor } from '../../providers/ThemeProvider/types';
5
+
6
+ export const StyledLabel = styled.span.withConfig({
7
+ shouldForwardProp: () => true,
8
+ })<LabelProps>`
9
+ display: inline-flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ font-family: var(--font-sans);
13
+ font-weight: 500;
14
+
15
+ ${({ color }) =>
16
+ color
17
+ ? css`
18
+ background-color: var(--color-${color}-100);
19
+ color: var(--color-${color}-700);
20
+ svg {
21
+ fill: var(--color-${color}-700);
22
+ }
23
+ `
24
+ : css`
25
+ background-color: var(--color-neutral-100);
26
+ color: var(--color-neutral-700);
27
+ svg {
28
+ fill: var(--color-neutral-700);
29
+ }
30
+ `}
31
+
32
+ ${({ bordered, color }) =>
33
+ bordered
34
+ ? css`
35
+ border: 1px solid
36
+ ${color ? `var(--color-${color}-200)` : 'var(--color-neutral-200)'};
37
+ `
38
+ : 'border: none;'}
39
+
40
+ ${({ rounded }) => rounded && 'border-radius: var(--rounded-full);'}
41
+
42
+ &.small {
43
+ padding: var(--space-0_5) var(--space-1_5);
44
+ font-size: var(--text-xs);
45
+ border-radius: var(--rounded-sm);
46
+ svg {
47
+ margin-right: var(--space-1);
48
+ width: var(--space-3);
49
+ height: var(--space-3);
50
+ }
51
+ }
52
+
53
+ &.medium {
54
+ padding: var(--space-1) var(--space-2);
55
+ font-size: var(--text-sm);
56
+ border-radius: var(--rounded-md);
57
+ svg {
58
+ margin-right: var(--space-1);
59
+ width: var(--space-3_5);
60
+ height: var(--space-3_5);
61
+ }
62
+ }
63
+
64
+ &.large {
65
+ padding: var(--space-1_5) var(--space-2_5);
66
+ font-size: var(--text-base);
67
+ border-radius: var(--rounded-lg);
68
+ svg {
69
+ margin-right: var(--space-1_5);
70
+ width: var(--space-4);
71
+ height: var(--space-4);
72
+ }
73
+ }
74
+ `;
75
+
76
+ export type LabelProps = {
77
+ color?: ThemeColor;
78
+ size?: 'small' | 'medium' | 'large';
79
+ rounded?: boolean;
80
+ bordered?: boolean;
81
+ };
82
+
83
+ export const Label = forwardRef<
84
+ HTMLSpanElement,
85
+ ComponentProps<typeof StyledLabel> & LabelProps
86
+ >(({ children, size = 'medium', className = '', ...props }, ref) => (
87
+ <StyledLabel ref={ref} className={`${size} ${className}`} {...props}>
88
+ {children}
89
+ </StyledLabel>
90
+ ));
@@ -1,4 +1,5 @@
1
1
  export * from './Avatar';
2
2
  export * from './Card';
3
3
  export * from './ContextMenu';
4
+ export * from './Label';
4
5
  export * from './Message';
@@ -400,6 +400,12 @@ export const defaultTheme: Theme = {
400
400
  '32': '8rem',
401
401
  '40': '10rem',
402
402
  '48': '12rem',
403
+ '56': '14rem',
404
+ '64': '16rem',
405
+ '72': '18rem',
406
+ '80': '20rem',
407
+ '96': '24rem',
408
+ '128': '32rem',
403
409
  },
404
410
 
405
411
  sizes: {
@@ -422,6 +428,12 @@ export const defaultTheme: Theme = {
422
428
  '32': '8rem',
423
429
  '40': '10rem',
424
430
  '48': '12rem',
431
+ '56': '14rem',
432
+ '64': '16rem',
433
+ '72': '18rem',
434
+ '80': '20rem',
435
+ '96': '24rem',
436
+ '128': '32rem',
425
437
  },
426
438
 
427
439
  rounded: {
@@ -82,7 +82,13 @@ export type ThemeSpace =
82
82
  | '24'
83
83
  | '32'
84
84
  | '40'
85
- | '48';
85
+ | '48'
86
+ | '56'
87
+ | '64'
88
+ | '72'
89
+ | '80'
90
+ | '96'
91
+ | '128';
86
92
  export type ThemeSize =
87
93
  | '0'
88
94
  | '0_5'