@applica-software-guru/react-admin 1.5.361 → 1.5.362

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
@@ -108,5 +108,5 @@
108
108
  "type": "module",
109
109
  "types": "dist/index.d.ts",
110
110
  "typings": "dist/index.d.ts",
111
- "version": "1.5.361"
111
+ "version": "1.5.362"
112
112
  }
@@ -5,19 +5,29 @@ import clsx from 'clsx';
5
5
  import difference from 'lodash/difference';
6
6
  import union from 'lodash/union';
7
7
  import { Identifier, sanitizeListRestProps, useListContext, useTranslate } from 'ra-core';
8
- import { FC, cloneElement, createElement, isValidElement, useCallback, useEffect, useMemo, useRef } from 'react';
8
+ import {
9
+ FC,
10
+ cloneElement,
11
+ createElement,
12
+ isValidElement,
13
+ useCallback,
14
+ useEffect,
15
+ useMemo,
16
+ useRef,
17
+ useState
18
+ } from 'react';
9
19
  import * as React from 'react';
10
20
  import {
11
21
  BulkDeleteButton,
12
22
  DatagridBody,
13
23
  DatagridClasses,
14
- DatagridHeader,
15
24
  DatagridLoading,
16
25
  DatagridRoot,
17
26
  PureDatagridBody,
18
27
  DatagridProps as RaDatagridProps
19
28
  } from 'react-admin';
20
29
  import { Empty } from '@/components/ra-lists/Empty';
30
+ import { DatagridHeader, SortItem } from './DatagridHeader';
21
31
 
22
32
  const defaultBulkActionButtons = <BulkDeleteButton />;
23
33
 
@@ -125,6 +135,29 @@ const Datagrid = React.forwardRef((props, ref) => {
125
135
 
126
136
  const translate = useTranslate();
127
137
  const { sort, data, isLoading, onSelect, onToggleItem, selectedIds, setSort, total } = useListContext(props);
138
+ const [multiSort, setMultiSort] = useState<SortItem[]>([]);
139
+
140
+ const prevMultiSortKeyRef = useRef<string>('');
141
+ const multiSortKey = useMemo(() => multiSort.map((s) => `${s.field}:${s.order}`).join('|'), [multiSort]);
142
+
143
+ useEffect(() => {
144
+ const prevKey = prevMultiSortKeyRef.current;
145
+ if (prevKey === multiSortKey) return;
146
+
147
+ if (multiSort.length > 0) {
148
+ setSort({
149
+ field: multiSort.map((s) => s.field).join(','),
150
+ order: multiSort.map((s) => s.order).join(',') as 'ASC' | 'DESC'
151
+ });
152
+ }
153
+
154
+ prevMultiSortKeyRef.current = multiSortKey;
155
+ }, [multiSortKey, multiSort, setSort]);
156
+
157
+ const onMultiSortChange = useCallback((newMultiSort: SortItem[]) => {
158
+ setMultiSort(newMultiSort);
159
+ }, []);
160
+
128
161
  const hasBulkActions = !!bulkActionButtons !== false;
129
162
  const contextValue = useMemo(() => ({ isRowExpandable, expandSingle }), [isRowExpandable, expandSingle]);
130
163
  const lastSelected = useRef(null);
@@ -216,7 +249,7 @@ const Datagrid = React.forwardRef((props, ref) => {
216
249
  <div className={DatagridClasses.tableWrapper}>
217
250
  <Table ref={ref} className={DatagridClasses.table} size={size} {...sanitizeRestProps(rest)}>
218
251
  {createOrCloneElement(
219
- header,
252
+ <DatagridHeader multiSort={multiSort} setMultiSort={onMultiSortChange} />,
220
253
  {
221
254
  children,
222
255
  sort,
@@ -0,0 +1,179 @@
1
+ import { Checkbox, TableCell, TableHead, TableRow, TableSortLabel, Typography } from '@mui/material';
2
+ import { RaRecord, useListContext, useTranslateLabel } from 'ra-core';
3
+ import React, { ReactElement, ReactNode, useCallback, useMemo } from 'react';
4
+ import { DatagridHeaderProps } from 'react-admin';
5
+
6
+ type SortItem = {
7
+ field: string;
8
+ order: 'ASC' | 'DESC';
9
+ };
10
+
11
+ interface MultiSortDatagridHeaderProps extends Omit<DatagridHeaderProps, 'setSort'> {
12
+ setMultiSort?: (sort: SortItem[]) => void;
13
+ multiSort?: SortItem[];
14
+ }
15
+
16
+ function DatagridHeader(props: MultiSortDatagridHeaderProps): ReactElement {
17
+ const {
18
+ children,
19
+ hasExpand,
20
+ hasBulkActions,
21
+ isRowSelectable,
22
+ onSelect,
23
+ selectedIds = [],
24
+ data = [],
25
+ multiSort = [],
26
+ setMultiSort,
27
+ resource
28
+ } = props;
29
+ const { setSort } = useListContext();
30
+ const translateLabel = useTranslateLabel();
31
+
32
+ const updateSort = useCallback(
33
+ (field: string, isMultiSort: boolean = false) => {
34
+ if (!setMultiSort) {
35
+ const currentSortForField = multiSort.find((s) => s.field === field);
36
+ const newOrder = currentSortForField?.order === 'ASC' ? 'DESC' : 'ASC';
37
+ setSort({ field, order: newOrder });
38
+ return;
39
+ }
40
+
41
+ const existingIndex = multiSort.findIndex((s) => s.field === field);
42
+
43
+ if (!isMultiSort) {
44
+ if (existingIndex >= 0) {
45
+ const currentOrder = multiSort[existingIndex].order;
46
+ if (currentOrder === 'ASC') {
47
+ setMultiSort([{ field, order: 'DESC' }]);
48
+ } else {
49
+ setMultiSort([]);
50
+ }
51
+ } else {
52
+ setMultiSort([{ field, order: 'ASC' }]);
53
+ }
54
+ return;
55
+ }
56
+
57
+ const newMultiSort = [...multiSort];
58
+
59
+ if (existingIndex >= 0) {
60
+ const currentOrder = newMultiSort[existingIndex].order;
61
+ if (currentOrder === 'ASC') {
62
+ newMultiSort[existingIndex] = { field, order: 'DESC' };
63
+ } else {
64
+ newMultiSort.splice(existingIndex, 1);
65
+ }
66
+ } else {
67
+ newMultiSort.push({ field, order: 'ASC' });
68
+ }
69
+
70
+ setMultiSort(newMultiSort);
71
+ },
72
+ [multiSort, setMultiSort, setSort]
73
+ );
74
+
75
+ const handleSelectAll = useCallback(
76
+ (event: React.ChangeEvent<HTMLInputElement>) => {
77
+ if (!onSelect) {
78
+ return;
79
+ }
80
+ if (event.target.checked) {
81
+ const allIds = data
82
+ .filter((record: RaRecord) => (isRowSelectable ? isRowSelectable(record) : true))
83
+ .map((record: RaRecord) => record.id);
84
+ onSelect(allIds);
85
+ } else {
86
+ onSelect([]);
87
+ }
88
+ },
89
+ [data, isRowSelectable, onSelect]
90
+ );
91
+
92
+ const getSortForField = useCallback(
93
+ (field: string): { order: 'ASC' | 'DESC'; index: number } | null => {
94
+ const index = multiSort.findIndex((s) => s.field === field);
95
+ if (index >= 0) {
96
+ return { order: multiSort[index].order, index };
97
+ }
98
+ return null;
99
+ },
100
+ [multiSort]
101
+ );
102
+
103
+ const childrenArray = useMemo(() => React.Children.toArray(children), [children]);
104
+
105
+ const selectableCount = useMemo(
106
+ () => data.filter((record: RaRecord) => (isRowSelectable ? isRowSelectable(record) : true)).length,
107
+ [data, isRowSelectable]
108
+ );
109
+
110
+ const handleSortClick = useCallback(
111
+ (source: string) => (event: React.MouseEvent<unknown>) => {
112
+ updateSort(source, event.ctrlKey || event.metaKey);
113
+ },
114
+ [updateSort]
115
+ );
116
+
117
+ return (
118
+ <TableHead>
119
+ <TableRow>
120
+ {!!hasExpand && <TableCell />}
121
+ {!!hasBulkActions && (
122
+ <TableCell padding="checkbox">
123
+ <Checkbox
124
+ color="primary"
125
+ checked={selectedIds.length > 0 && selectedIds.length === selectableCount}
126
+ onChange={handleSelectAll}
127
+ />
128
+ </TableCell>
129
+ )}
130
+ {childrenArray.length > 0 ? (
131
+ childrenArray.map((child: ReactNode, index) => {
132
+ if (!child || typeof child !== 'object' || !('props' in child)) {
133
+ return <TableCell key={index} />;
134
+ }
135
+
136
+ const source =
137
+ child.props.source ?? (typeof child.props.label === 'string' ? child.props.label : undefined);
138
+ const sortable = child.props.sortable !== false && !!source;
139
+ const sortInfo = sortable ? getSortForField(source) : null;
140
+
141
+ const translated = translateLabel({
142
+ ...child.props,
143
+ source: child.props.source ?? source,
144
+ resource
145
+ });
146
+
147
+ const displayLabel: ReactNode = translated ?? child.props.label ?? null;
148
+
149
+ return (
150
+ <TableCell
151
+ key={child.props.source ?? index}
152
+ align={child.props.textAlign || 'left'}
153
+ sx={{ cursor: sortable ? 'pointer' : 'default' }}
154
+ >
155
+ {sortable ? (
156
+ <TableSortLabel
157
+ active={!!sortInfo}
158
+ direction={(sortInfo?.order.toLowerCase() as 'asc' | 'desc') || 'asc'}
159
+ onClick={handleSortClick(source)}
160
+ >
161
+ <Typography>{displayLabel}</Typography>
162
+ </TableSortLabel>
163
+ ) : (
164
+ displayLabel
165
+ )}
166
+ </TableCell>
167
+ );
168
+ })
169
+ ) : (
170
+ <TableCell />
171
+ )}
172
+ </TableRow>
173
+ </TableHead>
174
+ );
175
+ }
176
+
177
+ export { DatagridHeader };
178
+
179
+ export type { MultiSortDatagridHeaderProps, SortItem };