diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 749cf8304..c48feba38 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -2,11 +2,16 @@ import React, { createRef, type JSX, type RefObject } from 'react'; import Dropzone from 'react-dropzone'; import { + Accordion, + AccordionDetails, + AccordionSummary, Button, + Card, Dialog, DialogActions, DialogContent, DialogTitle, + Grid2, IconButton, InputAdornment, Paper, @@ -37,6 +42,7 @@ import { Warning as ErrorIcon, UploadFile as ImportIcon, Close as IconClose, + ExpandMore as ExpandMoreIcon, } from '@mui/icons-material'; import { I18n } from '@iobroker/adapter-react-v5'; @@ -448,14 +454,15 @@ class ConfigTable extends ConfigGeneric { handleRequestSort = (property: string, orderCheck: boolean = false): void => { const { order, orderBy } = this.state; - if (orderBy) { - const isAsc = orderBy === property && order === 'asc'; - const newOrder = orderCheck ? order : isAsc ? 'desc' : 'asc'; - const newValue = this.stableSort(newOrder, property); - this.setState({ order: newOrder, orderBy: property, iteration: this.state.iteration + 10000 }, () => - this.applyFilter(false, newValue), - ); - } + //if (orderBy || 'asc') { + const isAsc = orderBy === property && order === 'asc'; + const newOrder = orderCheck ? order : isAsc ? 'desc' : 'asc'; + const newValue = this.stableSort(newOrder, property); + this.setState( + { value: newValue, order: newOrder, orderBy: property, iteration: this.state.iteration + 10000 }, + () => this.applyFilter(false, newValue), + ); + //} }; stableSort = (order: 'desc' | 'asc', orderBy: string): Record[] => { @@ -1087,14 +1094,401 @@ class ConfigTable extends ConfigGeneric { ); } - - renderItem(/* error, disabled, defaultValue */): JSX.Element | null { + iobUseMediaQuery(key: any): boolean { + let query = this.props.theme.breakpoints.only(key); + query = query.replace(/^@media( ?)/m, ''); + return window.matchMedia(query).matches; + } + enhancedFilterCard(/* buttonsWidth: number, doAnyFilterSet: boolean */): JSX.Element { + const { schema } = this.props; + const { order, orderBy } = this.state; + let tdStyle: React.CSSProperties | undefined; + if (this.props.schema.compact) { + tdStyle = { paddingTop: 1, paddingBottom: 1 }; + } + return ( + + + + + } + aria-controls="panel1-content" + id="panel1-header" + > + Filter and Data Actions + + + + + {schema.items && + schema.items.map((headCell: ConfigItemTableIndexed, i: number) => ( + + +
+ {headCell.sort && ( + + this.handleRequestSort(headCell.attr) + } + /> + )} + {headCell.filter && + this.state.filterOn.includes(headCell.attr) ? ( + this.applyFilter()} + title={I18n.t( + 'ra_You can filter entries by entering here some text', + )} + slotProps={{ + input: { + endAdornment: ConfigTable.getFilterValue( + this.filterRefs[headCell.attr], + ) && ( + + { + ConfigTable.setFilterValue( + this.filterRefs[ + headCell.attr + ], + '', + ); + this.applyFilter(); + }} + > + + + + ), + }, + }} + fullWidth + placeholder={this.getText(headCell.title)} + /> + ) : ( + + {this.getText(headCell.title)} + + )} + {headCell.filter ? ( + { + const filterOn = [...this.state.filterOn]; + const pos = this.state.filterOn.indexOf( + headCell.attr, + ); + if (pos === -1) { + filterOn.push(headCell.attr); + } else { + filterOn.splice(pos, 1); + } + this.setState({ filterOn }, () => { + if ( + pos && + ConfigTable.getFilterValue( + this.filterRefs[headCell.attr], + ) + ) { + ConfigTable.setFilterValue( + this.filterRefs[headCell.attr], + '', + ); + this.applyFilter(); + } + }); + }} + > + {this.state.filterOn.includes(headCell.attr) ? ( + + ) : ( + + )} + + ) : null} +
+
+
+ ))} + + + {this.getText('Actions')} + + + {!schema.noDelete && schema.import ? ( + + this.setState({ showImportDialog: true })} + /* title={I18n.t('ra_import data from %s file', 'CSV')} */ + > + + + + ) : null} + {schema.export ? ( + + this.onExport()} + /* title={I18n.t('ra_Export data to %s file', 'CSV')} */ + > + + + + ) : null} + + + + + +
+
+
+
+
+
+
+ ); + } + enhancedBottomCard(): JSX.Element { + const { schema } = this.props; + let tdStyle: React.CSSProperties | undefined; + if (this.props.schema.compact) { + tdStyle = { paddingTop: 1, paddingBottom: 1 }; + } + const doAnyFilterSet = this.isAnyFilterSet(); + return ( + + + + + + + + + + + + + + + + + +
+
+
+
+ ); + } + renderCard(): JSX.Element | null { const { schema } = this.props; let { visibleValue } = this.state; - - if (!this.state.value || !Array.isArray(this.state.value)) { - return null; + let tdStyle: React.CSSProperties | undefined; + if (this.props.schema.compact) { + tdStyle = { paddingTop: 1, paddingBottom: 1 }; } + visibleValue = visibleValue || this.state.value.map((_, i) => i); + + const doAnyFilterSet = this.isAnyFilterSet(); + + return ( + + {this.showImportDialog()} + {this.showTypeOfImportDialog()} + {this.enhancedFilterCard(/* 0, doAnyFilterSet */)} + {visibleValue.map((idx, i) => ( + + + + + + {schema.items && + schema.items.map((headCell: ConfigItemTableIndexed) => ( + + + + {this.getText(headCell.title)} + + + + {this.itemTable(headCell.attr, this.state.value[idx], idx)} + + + ))} + + + {this.getText('Actions')} + + + {!doAnyFilterSet && !this.state.orderBy ? ( + + + this.onMoveUp(idx)} + disabled={i === 0} + > + + + + + ) : null} + {!doAnyFilterSet && !this.state.orderBy ? ( + + + this.onMoveDown(idx)} + disabled={i === visibleValue.length - 1} + > + + + + + ) : null} + + + + + + {this.props.schema.clone ? ( + + + + + + ) : null} + + + +
+
+
+
+ ))} + {this.enhancedBottomCard()} +
+ ); + } + renderTable(): JSX.Element | null { + const { schema } = this.props; + let { visibleValue } = this.state; visibleValue = visibleValue || this.state.value.map((_, i) => i); @@ -1282,6 +1676,30 @@ class ConfigTable extends ConfigGeneric { ); } + renderItem(/* error, disabled, defaultValue */): JSX.Element | null { + const { schema } = this.props; + + if (!this.state.value || !Array.isArray(this.state.value)) { + return null; + } + + const isBreakpoint = this.iobUseMediaQuery('xs') + ? 'XS' + : this.iobUseMediaQuery('sm') + ? 'SM' + : this.iobUseMediaQuery('md') + ? 'MD' + : this.iobUseMediaQuery('lg') + ? 'LG' + : this.iobUseMediaQuery('xl') + ? 'XL' + : ''; + + if (schema.usecardfor && schema.usecardfor.map(el => el.toUpperCase()).includes(isBreakpoint)) { + return this.renderCard(); + } + return this.renderTable(); + } } export default ConfigTable; diff --git a/packages/jsonConfig/src/types.d.ts b/packages/jsonConfig/src/types.d.ts index d2a23a9a0..cba9c1731 100644 --- a/packages/jsonConfig/src/types.d.ts +++ b/packages/jsonConfig/src/types.d.ts @@ -779,6 +779,8 @@ export interface ConfigItemTable extends ConfigItem { uniqueColumns?: string[]; /** These items will be encrypted before saving with simple (not SHA) encryption method */ encryptedAttributes?: string[]; + /** Breakpoint that will be rendered as cards */ + usecardfor?: string[]; } export interface ConfigItemTimePicker extends ConfigItem {