From 6cde54393832d3cbc625cab7b10636705488a785 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Fri, 1 Aug 2025 12:03:15 +0200 Subject: [PATCH] Enhance webpack configuration for improved performance and development experience. Add filesystem caching and watch options. Update KreditorSelector to handle prefilled data and improve state management. Refactor TransactionsTable to manage focus during dialog interactions. Update admin tables to manage focus restoration and improve dialog handling. Implement IBAN filtering in IbanSelectionFilter and enhance document rendering with Kreditor information. Update SQL schema to allow multiple IBANs for the same Kreditor and adjust API routes for better data handling. --- client/public/index.html | 2 - client/src/components/KreditorSelector.js | 52 +++++- client/src/components/TransactionsTable.js | 34 +++- client/src/components/admin/BUTable.js | 59 ++++++- client/src/components/admin/KontoTable.js | 59 ++++++- client/src/components/admin/KreditorTable.js | 59 ++++++- .../cellRenderers/DocumentRenderer.js | 153 +++++++++++++++++- .../cellRenderers/RecipientRenderer.js | 33 +++- client/src/components/config/gridConfig.js | 14 +- .../src/components/filters/CheckboxFilter.js | 23 ++- .../components/filters/IbanSelectionFilter.js | 142 ++++++++++++---- client/src/components/headers/SortHeader.js | 5 +- .../headers/TextHeaderWithFilter.js | 16 +- src/database/schema.sql | 6 +- src/routes/admin.js | 2 +- src/routes/data.js | 119 +++++++------- webpack.config.js | 33 ++++ 17 files changed, 671 insertions(+), 140 deletions(-) diff --git a/client/public/index.html b/client/public/index.html index 075fc78..32879a4 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -70,9 +70,7 @@ setTimeout(() => { const betragHeader = document.querySelector('.ag-header-cell[col-id="numericAmount"]'); if (betragHeader) { - console.log('Found Betrag header:', betragHeader); console.log('Header classes:', betragHeader.className); - console.log('Header HTML:', betragHeader.innerHTML); } else { console.log('Could not find Betrag header with col-id="numericAmount"'); // Try to find it by text content diff --git a/client/src/components/KreditorSelector.js b/client/src/components/KreditorSelector.js index 5f635b2..5b6a37d 100644 --- a/client/src/components/KreditorSelector.js +++ b/client/src/components/KreditorSelector.js @@ -41,12 +41,48 @@ class KreditorSelector extends Component { componentDidMount() { this.loadKreditors(); + + // If prefilled data is provided, set it in the newKreditor state + const updates = {}; + if (this.props.prefilledIban) { + updates.iban = this.props.prefilledIban; + } + if (this.props.prefilledName) { + updates.name = this.props.prefilledName; + } + + if (Object.keys(updates).length > 0) { + this.setState({ + newKreditor: { + ...this.state.newKreditor, + ...updates + } + }); + } } componentDidUpdate(prevProps) { if (prevProps.selectedKreditorId !== this.props.selectedKreditorId) { this.setState({ selectedKreditorId: this.props.selectedKreditorId || '' }); } + + // If prefilled props change, update the newKreditor state + const updates = {}; + if (prevProps.prefilledIban !== this.props.prefilledIban && this.props.prefilledIban) { + updates.iban = this.props.prefilledIban; + } + if (prevProps.prefilledName !== this.props.prefilledName && this.props.prefilledName) { + updates.name = this.props.prefilledName; + } + + if (Object.keys(updates).length > 0) { + this.setState({ + newKreditor: { + ...this.state.newKreditor, + ...updates + } + }); + } } loadKreditors = async () => { @@ -83,7 +119,11 @@ class KreditorSelector extends Component { handleCreateDialogClose = () => { this.setState({ createDialogOpen: false, - newKreditor: { iban: '', name: '', kreditorId: '' }, + newKreditor: { + iban: this.props.prefilledIban || '', + name: this.props.prefilledName || '', + kreditorId: '' + }, validationErrors: [], error: null }); @@ -181,10 +221,12 @@ class KreditorSelector extends Component { {kreditor.name} ({kreditor.kreditorId}) - {kreditor.iban} ))} - - - Neuen Kreditor erstellen - + {(this.props.allowCreate !== false) && ( + + + Neuen Kreditor erstellen + + )} diff --git a/client/src/components/TransactionsTable.js b/client/src/components/TransactionsTable.js index dd3ba74..e2bb612 100644 --- a/client/src/components/TransactionsTable.js +++ b/client/src/components/TransactionsTable.js @@ -51,6 +51,31 @@ class TransactionsTable extends Component { }; window.addEventListener('resize', this.handleResize); + + // Add dialog open listener to blur grid focus + this.handleDialogOpen = () => { + if (this.gridApi) { + // Clear any focused cells to prevent aria-hidden conflicts + this.gridApi.clearFocusedCell(); + // Also blur any focused elements within the grid + const gridElement = document.querySelector('.ag-root-wrapper'); + if (gridElement) { + const focusedElement = gridElement.querySelector(':focus'); + if (focusedElement) { + focusedElement.blur(); + } + } + } + }; + + // Listen for dialog open events (Material-UI dialogs) + this.handleFocusIn = (event) => { + // If focus moves to a dialog, blur the grid + if (event.target.closest('[role="dialog"]')) { + this.handleDialogOpen(); + } + }; + document.addEventListener('focusin', this.handleFocusIn); } componentWillUnmount() { @@ -59,7 +84,13 @@ class TransactionsTable extends Component { window.removeEventListener('resize', this.handleResize); } - if (this.gridApi) { + // Clean up dialog focus listener + if (this.handleFocusIn) { + document.removeEventListener('focusin', this.handleFocusIn); + } + + // Check if grid API is still valid before removing listeners + if (this.gridApi && !this.gridApi.isDestroyed()) { this.gridApi.removeEventListener('modelUpdated', this.onModelUpdated); this.gridApi.removeEventListener('filterChanged', this.onFilterChanged); } @@ -317,7 +348,6 @@ class TransactionsTable extends Component { animateRows={true} // Maintain state across data updates maintainColumnOrder={true} - suppressColumnStateEvents={false} /> diff --git a/client/src/components/admin/BUTable.js b/client/src/components/admin/BUTable.js index aaf91e3..01924cb 100644 --- a/client/src/components/admin/BUTable.js +++ b/client/src/components/admin/BUTable.js @@ -44,6 +44,11 @@ class BUTable extends Component { }, }; this.authService = new AuthService(); + + // Focus management refs + this.triggerRef = React.createRef(); + this.dialogRef = React.createRef(); + this.confirmDialogRef = React.createRef(); } componentDidMount() { @@ -66,6 +71,9 @@ class BUTable extends Component { }; handleOpenDialog = (bu = null) => { + // Store reference to the trigger element for focus restoration + this.triggerRef.current = document.activeElement; + this.setState({ dialogOpen: true, editingBU: bu, @@ -91,6 +99,13 @@ class BUTable extends Component { vst: '', }, }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); }; handleInputChange = (field) => (event) => { @@ -145,6 +160,9 @@ class BUTable extends Component { }; handleDeleteClick = (bu) => { + // Store reference to the trigger element for focus restoration + this.triggerRef.current = document.activeElement; + this.setState({ confirmDialogOpen: true, itemToDelete: bu, @@ -156,6 +174,13 @@ class BUTable extends Component { if (!itemToDelete) return; this.setState({ confirmDialogOpen: false, itemToDelete: null }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); try { const response = await this.authService.apiCall(`/admin/buchungsschluessel/${itemToDelete.id}`, { @@ -179,6 +204,13 @@ class BUTable extends Component { confirmDialogOpen: false, itemToDelete: null, }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); }; render() { @@ -250,11 +282,22 @@ class BUTable extends Component { - - + + {editingBU ? 'Buchungsschlüssel bearbeiten' : 'Neuer Buchungsschlüssel'} - + - Löschen bestätigen - + Löschen bestätigen + {this.state.itemToDelete && `Buchungsschlüssel "${this.state.itemToDelete.bu} - ${this.state.itemToDelete.name}" wirklich löschen?` diff --git a/client/src/components/admin/KontoTable.js b/client/src/components/admin/KontoTable.js index 176c1c1..074b276 100644 --- a/client/src/components/admin/KontoTable.js +++ b/client/src/components/admin/KontoTable.js @@ -43,6 +43,11 @@ class KontoTable extends Component { }, }; this.authService = new AuthService(); + + // Focus management refs + this.triggerRef = React.createRef(); + this.dialogRef = React.createRef(); + this.confirmDialogRef = React.createRef(); } componentDidMount() { @@ -65,6 +70,9 @@ class KontoTable extends Component { }; handleOpenDialog = (konto = null) => { + // Store reference to the trigger element for focus restoration + this.triggerRef.current = document.activeElement; + this.setState({ dialogOpen: true, editingKonto: konto, @@ -87,6 +95,13 @@ class KontoTable extends Component { name: '', }, }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); }; handleInputChange = (field) => (event) => { @@ -134,6 +149,9 @@ class KontoTable extends Component { }; handleDeleteClick = (konto) => { + // Store reference to the trigger element for focus restoration + this.triggerRef.current = document.activeElement; + this.setState({ confirmDialogOpen: true, itemToDelete: konto, @@ -145,6 +163,13 @@ class KontoTable extends Component { if (!itemToDelete) return; this.setState({ confirmDialogOpen: false, itemToDelete: null }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); try { const response = await this.authService.apiCall(`/admin/konten/${konto.id}`, { @@ -168,6 +193,13 @@ class KontoTable extends Component { confirmDialogOpen: false, itemToDelete: null, }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); }; render() { @@ -235,11 +267,22 @@ class KontoTable extends Component { - - + + {editingKonto ? 'Konto bearbeiten' : 'Neues Konto'} - + - Löschen bestätigen - + Löschen bestätigen + {this.state.itemToDelete && `Konto "${this.state.itemToDelete.konto} - ${this.state.itemToDelete.name}" wirklich löschen?` diff --git a/client/src/components/admin/KreditorTable.js b/client/src/components/admin/KreditorTable.js index 2d19722..ad5225e 100644 --- a/client/src/components/admin/KreditorTable.js +++ b/client/src/components/admin/KreditorTable.js @@ -44,6 +44,11 @@ class KreditorTable extends Component { }, }; this.authService = new AuthService(); + + // Focus management refs + this.triggerRef = React.createRef(); + this.dialogRef = React.createRef(); + this.confirmDialogRef = React.createRef(); } componentDidMount() { @@ -66,6 +71,9 @@ class KreditorTable extends Component { }; handleOpenDialog = (kreditor = null) => { + // Store reference to the trigger element for focus restoration + this.triggerRef.current = document.activeElement; + this.setState({ dialogOpen: true, editingKreditor: kreditor, @@ -91,6 +99,13 @@ class KreditorTable extends Component { kreditorId: '', }, }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); }; handleInputChange = (field) => (event) => { @@ -139,6 +154,9 @@ class KreditorTable extends Component { }; handleDeleteClick = (kreditor) => { + // Store reference to the trigger element for focus restoration + this.triggerRef.current = document.activeElement; + this.setState({ confirmDialogOpen: true, itemToDelete: kreditor, @@ -150,6 +168,13 @@ class KreditorTable extends Component { if (!itemToDelete) return; this.setState({ confirmDialogOpen: false, itemToDelete: null }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); try { const response = await this.authService.apiCall(`/admin/kreditoren/${kreditor.id}`, { @@ -173,6 +198,13 @@ class KreditorTable extends Component { confirmDialogOpen: false, itemToDelete: null, }); + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (this.triggerRef.current && this.triggerRef.current.focus) { + this.triggerRef.current.focus(); + } + }, 100); }; render() { @@ -242,11 +274,22 @@ class KreditorTable extends Component { - - + + {editingKreditor ? 'Kreditor bearbeiten' : 'Neuer Kreditor'} - + - Löschen bestätigen - + Löschen bestätigen + {this.state.itemToDelete && `Kreditor "${this.state.itemToDelete.name}" wirklich löschen?` diff --git a/client/src/components/cellRenderers/DocumentRenderer.js b/client/src/components/cellRenderers/DocumentRenderer.js index 64e58ef..8da44ee 100644 --- a/client/src/components/cellRenderers/DocumentRenderer.js +++ b/client/src/components/cellRenderers/DocumentRenderer.js @@ -15,7 +15,9 @@ import { Divider, Tabs, Tab, - Alert + Alert, + Chip, + Paper } from '@mui/material'; import { PictureAsPdf as PdfIcon, @@ -35,6 +37,10 @@ const DocumentRenderer = (params) => { const [tabValue, setTabValue] = useState(0); const [error, setError] = useState(null); + // Focus management refs + const triggerRef = React.useRef(null); + const dialogRef = React.useRef(null); + // Always show something clickable, even if no documents const hasDocuments = pdfs.length > 0 || links.length > 0; @@ -47,6 +53,8 @@ const DocumentRenderer = (params) => { const totalCount = allDocuments.length; const handleClick = () => { + // Store reference to the trigger element for focus restoration + triggerRef.current = document.activeElement; setDialogOpen(true); }; @@ -54,6 +62,13 @@ const DocumentRenderer = (params) => { setDialogOpen(false); setTabValue(0); // Reset to first tab when closing setError(null); // Clear any errors when closing + + // Restore focus to the trigger element after dialog closes + setTimeout(() => { + if (triggerRef.current && triggerRef.current.focus) { + triggerRef.current.focus(); + } + }, 100); }; const handleTabChange = (event, newValue) => { @@ -321,11 +336,22 @@ const DocumentRenderer = (params) => { )} - - + + {hasDocuments ? `Dokumente (${totalCount})` : 'Dokumentinformationen'} - + {error && ( setError(null)}> {error} @@ -335,6 +361,23 @@ const DocumentRenderer = (params) => { + @@ -431,6 +474,108 @@ const DocumentRenderer = (params) => { )} )} + + {tabValue === 2 && ( + + + Kreditor Information + + + + + IBAN + + + {params.data['Kontonummer/IBAN'] || 'Keine IBAN verfügbar'} + + + {/* Show different content based on IBAN availability and Kreditor status */} + {!params.data['Kontonummer/IBAN'] ? ( + + + + Ohne IBAN kann kein Kreditor zugeordnet werden. + + + ) : params.data.hasKreditor ? ( + + + + + Kreditor Details + + + Name: {params.data.kreditor.name} + + + Kreditor ID: {params.data.kreditor.kreditorId} + + + + ) : ( + + + + Sie können einen neuen Kreditor für diese IBAN erstellen: + + { + console.log('Kreditor selected/created:', kreditor); + if (kreditor) { + // Update the transaction data to reflect the new kreditor + params.data.kreditor = kreditor; + params.data.hasKreditor = true; + + // Update all transactions with the same IBAN in the grid + if (params.api && kreditor.iban) { + const nodesToRefresh = []; + params.api.forEachNode((node) => { + if (node.data && node.data['Kontonummer/IBAN'] === kreditor.iban) { + node.data.kreditor = kreditor; + node.data.hasKreditor = true; + nodesToRefresh.push(node); + } + }); + // Refresh specific cells to show updated colors and data + if (nodesToRefresh.length > 0) { + params.api.refreshCells({ + rowNodes: nodesToRefresh, + columns: ['Kontonummer/IBAN'], + force: true + }); + } + } + + // Close and reopen dialog to show updated status + setDialogOpen(false); + setTimeout(() => setDialogOpen(true), 100); + } + }} + prefilledIban={params.data['Kontonummer/IBAN']} + prefilledName={params.data['Beguenstigter/Zahlungspflichtiger']} + allowCreate={true} + /> + + )} + + + )} diff --git a/client/src/components/cellRenderers/RecipientRenderer.js b/client/src/components/cellRenderers/RecipientRenderer.js index 9227860..e9f53f5 100644 --- a/client/src/components/cellRenderers/RecipientRenderer.js +++ b/client/src/components/cellRenderers/RecipientRenderer.js @@ -21,17 +21,44 @@ const RecipientRenderer = (params) => { } }; + // Determine color based on Kreditor status for IBAN column + const getIbanColor = () => { + if (!isIbanColumn || !value) return 'inherit'; + + // Check if this transaction has Kreditor information + if (params.data && params.data.hasKreditor) { + return '#2e7d32'; // Green for found Kreditor + } else if (params.data && value) { + return '#ed6c02'; // Orange for IBAN without Kreditor + } + + return '#1976d2'; // Default blue for clickable IBAN + }; + + const getTitle = () => { + if (!isIbanColumn || !value) return undefined; + + if (params.data && params.data.hasKreditor) { + return `IBAN "${value}" - Kreditor: ${params.data.kreditor?.name || 'Unbekannt'} (zum Filtern klicken)`; + } else if (params.data && value) { + return `IBAN "${value}" - Kein Kreditor gefunden (zum Filtern klicken)`; + } + + return `Nach IBAN "${value}" filtern`; + }; + return ( {value} diff --git a/client/src/components/config/gridConfig.js b/client/src/components/config/gridConfig.js index 6d76a87..78c593d 100644 --- a/client/src/components/config/gridConfig.js +++ b/client/src/components/config/gridConfig.js @@ -28,7 +28,7 @@ export const getColumnDefs = () => [ sortable: false, filter: false, resizable: false, - suppressMenu: true, + suppressHeaderMenuButton: true, cellRenderer: SelectionRenderer, headerComponent: SelectionHeader, headerComponentParams: { @@ -101,7 +101,6 @@ export const getColumnDefs = () => [ width: 70, cellRenderer: TypeRenderer, sortable: false, - suppressSorting: true, filter: CheckboxFilter, filterParams: { filterOptions: [ @@ -138,7 +137,6 @@ export const getColumnDefs = () => [ width: 70, cellRenderer: JtlRenderer, sortable: false, - suppressSorting: true, filter: CheckboxFilter, filterParams: { filterOptions: [ @@ -212,7 +210,10 @@ export const defaultColDef = { export const gridOptions = { animateRows: true, - rowSelection: false, + rowSelection: { + mode: 'multiRow', + enableClickSelection: false + }, rowBuffer: 10, // Enable virtualization (default behavior) suppressRowVirtualisation: false, @@ -225,8 +226,7 @@ export const gridOptions = { // Pagination (optional - can be removed for infinite scrolling) pagination: false, paginationPageSize: 100, - // Disable cell selection - suppressCellSelection: true, - suppressRowClickSelection: true, + // Disable cell selection and focus + cellSelection: false, suppressCellFocus: true }; \ No newline at end of file diff --git a/client/src/components/filters/CheckboxFilter.js b/client/src/components/filters/CheckboxFilter.js index 1dc47cd..2c11b48 100644 --- a/client/src/components/filters/CheckboxFilter.js +++ b/client/src/components/filters/CheckboxFilter.js @@ -103,9 +103,13 @@ export default class CheckboxFilter { }; destroy() { - if (this.reactRoot) { - this.reactRoot.unmount(); - } + // Use setTimeout to avoid unmounting during render + setTimeout(() => { + if (this.reactRoot) { + this.reactRoot.unmount(); + this.reactRoot = null; + } + }, 0); } renderReactComponent() { @@ -172,10 +176,13 @@ export default class CheckboxFilter { ); // Recreate React root every time to avoid state corruption - if (this.reactRoot) { - this.reactRoot.unmount(); - } - this.reactRoot = createRoot(this.eGui); - this.reactRoot.render(); + // Use setTimeout to avoid unmounting during render + setTimeout(() => { + if (this.reactRoot) { + this.reactRoot.unmount(); + } + this.reactRoot = createRoot(this.eGui); + this.reactRoot.render(); + }, 0); } } \ No newline at end of file diff --git a/client/src/components/filters/IbanSelectionFilter.js b/client/src/components/filters/IbanSelectionFilter.js index c1138bb..ed475ad 100644 --- a/client/src/components/filters/IbanSelectionFilter.js +++ b/client/src/components/filters/IbanSelectionFilter.js @@ -8,20 +8,27 @@ import { ListItemText, Box, Chip, - Button + Button, + TextField, + Typography, + Divider } from '@mui/material'; export default class IbanSelectionFilter { constructor() { this.state = { selectedValues: [], - availableValues: [] + availableValues: [], + partialIban: '' }; // Create the DOM element that AG Grid expects this.eGui = document.createElement('div'); this.eGui.style.minWidth = '250px'; this.eGui.style.padding = '8px'; + + // Create a ref for the text input + this.textInputRef = React.createRef(); } init(params) { @@ -35,10 +42,13 @@ export default class IbanSelectionFilter { } destroy() { - if (this.reactRoot) { - this.reactRoot.unmount(); - this.reactRoot = null; - } + // Use setTimeout to avoid unmounting during render + setTimeout(() => { + if (this.reactRoot) { + this.reactRoot.unmount(); + this.reactRoot = null; + } + }, 0); } updateAvailableValues() { @@ -72,15 +82,27 @@ export default class IbanSelectionFilter { } isFilterActive() { - return this.state.selectedValues.length > 0; + return this.state.selectedValues.length > 0 || this.state.partialIban.trim() !== ''; } doesFilterPass(params) { - const { selectedValues } = this.state; - if (selectedValues.length === 0) return true; - + const { selectedValues, partialIban } = this.state; const value = params.data['Kontonummer/IBAN']; - return selectedValues.includes(value); + + // If no filters are active, show all rows + if (selectedValues.length === 0 && partialIban.trim() === '') { + return true; + } + + // Check if row matches selected IBANs + const matchesSelected = selectedValues.length === 0 || selectedValues.includes(value); + + // Check if row matches partial IBAN (case-insensitive) + const matchesPartial = partialIban.trim() === '' || + (value && value.toLowerCase().includes(partialIban.toLowerCase())); + + // Both conditions must be true (AND logic) + return matchesSelected && matchesPartial; } getModel() { @@ -88,16 +110,28 @@ export default class IbanSelectionFilter { return { filterType: 'iban-selection', - values: this.state.selectedValues + values: this.state.selectedValues, + partialIban: this.state.partialIban }; } setModel(model) { if (!model) { this.state.selectedValues = []; + this.state.partialIban = ''; } else { this.state.selectedValues = model.values || []; + this.state.partialIban = model.partialIban || ''; } + + // Update the text field value directly if it exists + if (this.textInputRef.current) { + const inputElement = this.textInputRef.current.querySelector('input'); + if (inputElement) { + inputElement.value = this.state.partialIban; + } + } + this.renderReactComponent(); } @@ -111,8 +145,39 @@ export default class IbanSelectionFilter { } }; + handlePartialIbanChange = (partialIban) => { + this.state.partialIban = partialIban; + + // Update the clear button visibility without full re-render + this.updateClearButtonVisibility(); + + // Notify AG Grid that filter changed + if (this.params && this.params.filterChangedCallback) { + this.params.filterChangedCallback(); + } + }; + + updateClearButtonVisibility = () => { + // Find the clear button container and update its visibility + const clearButtonContainer = this.eGui.querySelector('.clear-button-container'); + if (clearButtonContainer) { + const shouldShow = this.state.selectedValues.length > 0 || this.state.partialIban.trim() !== ''; + clearButtonContainer.style.display = shouldShow ? 'block' : 'none'; + } + }; + clearFilter = () => { this.state.selectedValues = []; + this.state.partialIban = ''; + + // Clear the text field directly using the ref + if (this.textInputRef.current) { + const inputElement = this.textInputRef.current.querySelector('input'); + if (inputElement) { + inputElement.value = ''; + } + } + this.renderReactComponent(); if (this.params && this.params.filterChangedCallback) { @@ -127,6 +192,27 @@ export default class IbanSelectionFilter { const FilterComponent = () => ( + + IBAN Filter + + + this.handlePartialIbanChange(event.target.value)} + sx={{ mb: 2 }} + /> + + + + + Oder aus der Liste auswählen: + +