From 11b3859487afa0b9da9e53f544937030f0c07be1 Mon Sep 17 00:00:00 2001 From: oweitman Date: Wed, 13 Nov 2024 12:04:29 +0100 Subject: [PATCH 1/6] Design implementation for the Card view for dedicated breakpoints in the ConfigTable component --- .../src/JsonConfigComponent/ConfigTable.tsx | 422 +++++++++++++++++- packages/jsonConfig/src/types.d.ts | 2 + 2 files changed, 416 insertions(+), 8 deletions(-) diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 91edc9a3e..0743db556 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, @@ -22,6 +27,7 @@ import { Tooltip, Typography, FormHelperText, + Box, } from '@mui/material'; import { @@ -37,6 +43,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'; @@ -461,12 +468,13 @@ class ConfigTable extends ConfigGeneric { handleRequestSort = (property: string, orderCheck: boolean = false): void => { const { order, orderBy } = this.state; - if (orderBy) { + if (orderBy || 'asc') { 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), + this.setState( + { value: newValue, order: newOrder, orderBy: property, iteration: this.state.iteration + 10000 }, + () => this.applyFilter(false, newValue), ); } }; @@ -1100,14 +1108,387 @@ 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; + 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; + 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; - } + visibleValue = visibleValue || this.state.value.map((_, i) => i); + + const doAnyFilterSet = this.isAnyFilterSet(); + + let tdStyle: React.CSSProperties | undefined; + 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 ? ( + i ? ( + + this.onMoveUp(idx)} + > + + + + ) : ( +
+ ) + ) : null} + {!doAnyFilterSet && !this.state.orderBy ? ( + i < visibleValue.length - 1 ? ( + + this.onMoveDown(idx)} + > + + + + ) : ( +
+ ) + ) : 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); @@ -1295,6 +1676,31 @@ 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(); + } else { + return this.renderTable(); + } + } } export default ConfigTable; diff --git a/packages/jsonConfig/src/types.d.ts b/packages/jsonConfig/src/types.d.ts index 7d7f0a4a4..3785874b5 100644 --- a/packages/jsonConfig/src/types.d.ts +++ b/packages/jsonConfig/src/types.d.ts @@ -719,6 +719,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 { From 0155631c8621765e114d08f263e79e8cce620efc Mon Sep 17 00:00:00 2001 From: oweitman Date: Wed, 13 Nov 2024 13:32:43 +0100 Subject: [PATCH 2/6] remove useless key --- packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 0743db556..897685cef 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -1118,7 +1118,7 @@ class ConfigTable extends ConfigGeneric { const { order, orderBy } = this.state; return ( Date: Wed, 13 Nov 2024 15:29:46 +0100 Subject: [PATCH 3/6] improve sort --- .../src/JsonConfigComponent/ConfigTable.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 897685cef..6e3d3f1f9 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -27,7 +27,6 @@ import { Tooltip, Typography, FormHelperText, - Box, } from '@mui/material'; import { @@ -468,15 +467,15 @@ class ConfigTable extends ConfigGeneric { handleRequestSort = (property: string, orderCheck: boolean = false): void => { const { order, orderBy } = this.state; - 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), - ); - } + //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[] => { From 192af4041ebb947b97176e86b620a67bd6396dc6 Mon Sep 17 00:00:00 2001 From: oweitman Date: Wed, 13 Nov 2024 15:32:28 +0100 Subject: [PATCH 4/6] improve compact mode for cards --- .../src/JsonConfigComponent/ConfigTable.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 6e3d3f1f9..120059314 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -1115,6 +1115,10 @@ class ConfigTable extends ConfigGeneric { 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 ( { schema.items.map((headCell: ConfigItemTableIndexed, i: number) => ( @@ -1263,8 +1261,11 @@ class ConfigTable extends ConfigGeneric { {this.getText('Actions')} + + {!schema.noDelete && schema.import ? ( { 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; - + 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(); - let tdStyle: React.CSSProperties | undefined; return ( {this.showImportDialog()} From 20e1474a93a2308d89d5f7cd9244cc4e82693b78 Mon Sep 17 00:00:00 2001 From: oweitman Date: Wed, 13 Nov 2024 15:34:17 +0100 Subject: [PATCH 5/6] add tooltips for icons --- .../src/JsonConfigComponent/ConfigTable.tsx | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 120059314..48833672f 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -1267,24 +1267,34 @@ class ConfigTable extends ConfigGeneric { {!schema.noDelete && schema.import ? ( - this.setState({ showImportDialog: true })} - title={I18n.t('ra_import data from %s file', 'CSV')} + - - + this.setState({ showImportDialog: true })} + /* title={I18n.t('ra_import data from %s file', 'CSV')} */ + > + + + ) : null} {schema.export ? ( - this.onExport()} + - - + this.onExport()} + /* title={I18n.t('ra_Export data to %s file', 'CSV')} */ + > + + + ) : null} { style={tdStyle} > {!doAnyFilterSet && !this.state.orderBy ? ( - i ? ( - + + this.onMoveUp(idx)} + disabled={i === 0} > - - ) : ( -
- ) + + ) : null} {!doAnyFilterSet && !this.state.orderBy ? ( - i < visibleValue.length - 1 ? ( - + + this.onMoveDown(idx)} + disabled={i === visibleValue.length - 1} > - - ) : ( -
- ) + + ) : null} Date: Wed, 13 Nov 2024 15:38:18 +0100 Subject: [PATCH 6/6] minor improvements --- .../src/JsonConfigComponent/ConfigTable.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx index 48833672f..7d0138139 100644 --- a/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx +++ b/packages/jsonConfig/src/JsonConfigComponent/ConfigTable.tsx @@ -1112,7 +1112,7 @@ class ConfigTable extends ConfigGeneric { query = query.replace(/^@media( ?)/m, ''); return window.matchMedia(query).matches; } - enhancedFilterCard(buttonsWidth: number, doAnyFilterSet: boolean): JSX.Element { + enhancedFilterCard(/* buttonsWidth: number, doAnyFilterSet: boolean */): JSX.Element { const { schema } = this.props; const { order, orderBy } = this.state; let tdStyle: React.CSSProperties | undefined; @@ -1132,13 +1132,13 @@ class ConfigTable extends ConfigGeneric { > - + } aria-controls="panel1-content" id="panel1-header" > - Filter and Data Actions + Filter and Data Actions @@ -1383,7 +1383,7 @@ class ConfigTable extends ConfigGeneric { {this.showImportDialog()} {this.showTypeOfImportDialog()} - {this.enhancedFilterCard(0, doAnyFilterSet)} + {this.enhancedFilterCard(/* 0, doAnyFilterSet */)} {visibleValue.map((idx, i) => ( { if (schema.usecardfor && schema.usecardfor.map(el => el.toUpperCase()).includes(isBreakpoint)) { return this.renderCard(); - } else { - return this.renderTable(); } + return this.renderTable(); } }