From 16ed539c560d26183281c48b0d59987e875d8b5b Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 09:32:07 +0200 Subject: [PATCH 01/10] feat --- .../BrowserFilter/BrowserFilter.react.js | 59 +++++++++++++++++++ .../CategoryList/CategoryList.react.js | 12 ++++ src/components/CategoryList/CategoryList.scss | 14 +++++ src/dashboard/Data/Browser/Browser.react.js | 40 +++++++++++-- 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 9e2e7d7b70..a18ccb503e 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -51,6 +51,54 @@ export default class BrowserFilter extends React.Component { if (props.className !== this.props.className) { this.setState({ open: false }); } + + // Auto-open filter dialog if editFilter=true is in URL + const urlParams = new URLSearchParams(window.location.search); + const isEditFilterMode = urlParams.get('editFilter') === 'true'; + + if (isEditFilterMode && !this.state.open) { + // Get current filter info including name and relative dates setting + const currentFilter = this.getCurrentFilterInfo(); + + // Convert filters for display and open the dialog + const filters = this.convertDatesForDisplay(props.filters); + this.setState({ + open: true, + showMore: true, // Open in edit mode + filters: filters, + editMode: true, + name: currentFilter.name || '', + originalFilterName: currentFilter.name || '', + relativeDates: currentFilter.hasRelativeDates || false, + originalRelativeDates: currentFilter.hasRelativeDates || false, + originalFilters: props.filters, // Store original filters for comparison + }); + } + } + + componentDidMount() { + // Check if we should auto-open for edit mode on initial load + const urlParams = new URLSearchParams(window.location.search); + const isEditFilterMode = urlParams.get('editFilter') === 'true'; + + if (isEditFilterMode) { + // Get current filter info including name and relative dates setting + const currentFilter = this.getCurrentFilterInfo(); + + // Convert filters for display and open the dialog + const filters = this.convertDatesForDisplay(this.props.filters); + this.setState({ + open: true, + showMore: true, // Open in edit mode + filters: filters, + editMode: true, + name: currentFilter.name || '', + originalFilterName: currentFilter.name || '', + relativeDates: currentFilter.hasRelativeDates || false, + originalRelativeDates: currentFilter.hasRelativeDates || false, + originalFilters: this.props.filters, // Store original filters for comparison + }); + } } isCurrentFilterSaved() { @@ -496,6 +544,17 @@ export default class BrowserFilter extends React.Component { // Convert only Parse Date objects to JavaScript Date objects, preserve RelativeDate objects filters = this.convertDatesForDisplay(filters); } + + // If closing the dialog and we're in edit filter mode, remove the editFilter parameter + const urlParams = new URLSearchParams(window.location.search); + const isEditFilterMode = urlParams.get('editFilter') === 'true'; + + if (this.state.open && isEditFilterMode) { + urlParams.delete('editFilter'); + const newUrl = `${window.location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`; + window.history.replaceState({}, '', newUrl); + } + this.setState(prevState => ({ open: !prevState.open, filters: filters, diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index 2127901b67..bc05dc3858 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -185,6 +185,17 @@ export default class CategoryList extends React.Component { > {name} + {this.props.onEditFilter && ( + { + e.preventDefault(); + this.props.onEditFilter(c.name, filterData); + }} + > + + + )} ); })} @@ -202,4 +213,5 @@ CategoryList.propTypes = { ), current: PropTypes.string.describe('Id of current category to be highlighted.'), linkPrefix: PropTypes.string.describe('Link prefix used to generate link path.'), + onEditFilter: PropTypes.func.describe('Callback function for editing a filter.'), }; diff --git a/src/components/CategoryList/CategoryList.scss b/src/components/CategoryList/CategoryList.scss index 64a5e77ef4..7f60f11f8c 100644 --- a/src/components/CategoryList/CategoryList.scss +++ b/src/components/CategoryList/CategoryList.scss @@ -117,4 +117,18 @@ margin-right: 0px !important; } } + .editFilter { + display: flex; + align-items: center; + margin-right: 6px; + cursor: pointer; + svg { + fill: #8fb9cf; + } + &:hover { + svg { + fill: white; + } + } + } } diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 685e72f56d..712ca6a8e3 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -228,6 +228,7 @@ class Browser extends DashboardView { this.showCreateClass = this.showCreateClass.bind(this); this.refresh = this.refresh.bind(this); this.deleteFilter = this.deleteFilter.bind(this); + this.editFilter = this.editFilter.bind(this); this.selectRow = this.selectRow.bind(this); this.updateRow = this.updateRow.bind(this); this.updateOrdering = this.updateOrdering.bind(this); @@ -488,6 +489,11 @@ class Browser extends DashboardView { const filters = this.extractFiltersFromQuery(props); const { className, entityId, relationName } = props.params; const isRelationRoute = entityId && relationName; + + // Check if we're in edit filter mode (don't load data) + const query = new URLSearchParams(props.location.search); + const isEditFilterMode = query.get('editFilter') === 'true'; + let relation = this.state.relation; if (isRelationRoute && !relation) { const parentObjectQuery = new Parse.Query(className); @@ -497,7 +503,7 @@ class Browser extends DashboardView { } this.setState( { - data: null, + data: isEditFilterMode ? [] : null, // Set empty array in edit mode to avoid loading newObject: null, lastMax: -1, ordering: ColumnPreferences.getColumnSort(false, context.applicationId, className), @@ -505,10 +511,13 @@ class Browser extends DashboardView { relation: isRelationRoute ? relation : null, }, () => { - if (isRelationRoute) { - this.fetchRelation(relation, filters); - } else if (className) { - this.fetchData(className, filters); + // Only fetch data if not in edit filter mode + if (!isEditFilterMode) { + if (isRelationRoute) { + this.fetchRelation(relation, filters); + } else if (className) { + this.fetchData(className, filters); + } } } ); @@ -1373,6 +1382,26 @@ class Browser extends DashboardView { super.forceUpdate(); } + editFilter(className, filterData) { + // Navigate to the class with the filter loaded for editing + const { id, filter } = filterData; + + // Build URL with filter parameters for editing + const urlParams = new URLSearchParams(); + urlParams.set('filters', filter); + if (id) { + urlParams.set('filterId', id); + } + + // Add edit mode parameter to indicate we want to edit without loading data + urlParams.set('editFilter', 'true'); + + const url = `browser/${className}?${urlParams.toString()}`; + + // Navigate to the URL which will trigger the filter dialog to open in edit mode + this.props.navigate(generatePath(this.context, url)); + } + updateOrdering(ordering) { const source = this.state.relation || this.props.params.className; this.setState( @@ -2201,6 +2230,7 @@ class Browser extends DashboardView { classClicked={() => { this.resetPage(); }} + onEditFilter={this.editFilter} categories={allCategories} /> ); From 668db522da1872ae5e1b3cbdbd9434de20496cfd Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 09:39:42 +0200 Subject: [PATCH 02/10] fix --- .../BrowserFilter/BrowserFilter.react.js | 64 +++++++++++++++++-- .../BrowserFilter/FilterRow.react.js | 6 ++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index a18ccb503e..1de27f1114 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -60,8 +60,14 @@ export default class BrowserFilter extends React.Component { // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); + // Load filter data from URL if props.filters is empty + let filtersToDisplay = props.filters; + if (props.filters.size === 0) { + filtersToDisplay = this.loadFiltersFromURL(); + } + // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(props.filters); + const filters = this.convertDatesForDisplay(filtersToDisplay); this.setState({ open: true, showMore: true, // Open in edit mode @@ -71,7 +77,7 @@ export default class BrowserFilter extends React.Component { originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: props.filters, // Store original filters for comparison + originalFilters: filtersToDisplay, // Store original filters for comparison }); } } @@ -85,8 +91,14 @@ export default class BrowserFilter extends React.Component { // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); + // Load filter data from URL if props.filters is empty + let filtersToDisplay = this.props.filters; + if (this.props.filters.size === 0) { + filtersToDisplay = this.loadFiltersFromURL(); + } + // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(this.props.filters); + const filters = this.convertDatesForDisplay(filtersToDisplay); this.setState({ open: true, showMore: true, // Open in edit mode @@ -96,7 +108,7 @@ export default class BrowserFilter extends React.Component { originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: this.props.filters, // Store original filters for comparison + originalFilters: filtersToDisplay, // Store original filters for comparison }); } } @@ -272,6 +284,50 @@ export default class BrowserFilter extends React.Component { }; } + loadFiltersFromURL() { + const urlParams = new URLSearchParams(window.location.search); + const filtersParam = urlParams.get('filters'); + const filterId = urlParams.get('filterId'); + + // If we have a filterId, load from saved filters + if (filterId) { + const preferences = ClassPreferences.getPreferences( + this.context.applicationId, + this.props.className + ); + + if (preferences.filters) { + const savedFilter = preferences.filters.find(filter => filter.id === filterId); + if (savedFilter) { + try { + const filterData = JSON.parse(savedFilter.filter); + return new List(filterData.map(filter => { + const processedFilter = { ...filter, class: filter.class || this.props.className }; + return new ImmutableMap(processedFilter); + })); + } catch (error) { + console.warn('Failed to parse saved filter:', error); + } + } + } + } + + // If we have filters in URL but no filterId, parse them directly + if (filtersParam) { + try { + const queryFilters = JSON.parse(filtersParam); + return new List(queryFilters.map(filter => { + const processedFilter = { ...filter, class: filter.class || this.props.className }; + return new ImmutableMap(processedFilter); + })); + } catch (error) { + console.warn('Failed to parse URL filters:', error); + } + } + + return new List(); + } + toggleMore() { const currentFilter = this.getCurrentFilterInfo(); diff --git a/src/components/BrowserFilter/FilterRow.react.js b/src/components/BrowserFilter/FilterRow.react.js index eb10875eb3..cf86451037 100644 --- a/src/components/BrowserFilter/FilterRow.react.js +++ b/src/components/BrowserFilter/FilterRow.react.js @@ -118,6 +118,12 @@ const FilterRow = ({ }) => { const setFocus = useCallback(input => { if (input !== null && editMode) { + // For DateTimeEntry components, don't auto-focus as it opens the calendar + // Check if the input has a focus method that opens a popover/calendar + if (input.focus && input.open) { + // This is likely a DateTimeEntry component, skip auto-focus + return; + } input.focus(); } }, []); From f3658f171d9a04c41a4c7dfd604b893c0dd96ab7 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 09:53:04 +0200 Subject: [PATCH 03/10] Revert "fix" This reverts commit 668db522da1872ae5e1b3cbdbd9434de20496cfd. --- .../BrowserFilter/BrowserFilter.react.js | 64 ++----------------- .../BrowserFilter/FilterRow.react.js | 6 -- 2 files changed, 4 insertions(+), 66 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 1de27f1114..a18ccb503e 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -60,14 +60,8 @@ export default class BrowserFilter extends React.Component { // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); - // Load filter data from URL if props.filters is empty - let filtersToDisplay = props.filters; - if (props.filters.size === 0) { - filtersToDisplay = this.loadFiltersFromURL(); - } - // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(filtersToDisplay); + const filters = this.convertDatesForDisplay(props.filters); this.setState({ open: true, showMore: true, // Open in edit mode @@ -77,7 +71,7 @@ export default class BrowserFilter extends React.Component { originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: filtersToDisplay, // Store original filters for comparison + originalFilters: props.filters, // Store original filters for comparison }); } } @@ -91,14 +85,8 @@ export default class BrowserFilter extends React.Component { // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); - // Load filter data from URL if props.filters is empty - let filtersToDisplay = this.props.filters; - if (this.props.filters.size === 0) { - filtersToDisplay = this.loadFiltersFromURL(); - } - // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(filtersToDisplay); + const filters = this.convertDatesForDisplay(this.props.filters); this.setState({ open: true, showMore: true, // Open in edit mode @@ -108,7 +96,7 @@ export default class BrowserFilter extends React.Component { originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: filtersToDisplay, // Store original filters for comparison + originalFilters: this.props.filters, // Store original filters for comparison }); } } @@ -284,50 +272,6 @@ export default class BrowserFilter extends React.Component { }; } - loadFiltersFromURL() { - const urlParams = new URLSearchParams(window.location.search); - const filtersParam = urlParams.get('filters'); - const filterId = urlParams.get('filterId'); - - // If we have a filterId, load from saved filters - if (filterId) { - const preferences = ClassPreferences.getPreferences( - this.context.applicationId, - this.props.className - ); - - if (preferences.filters) { - const savedFilter = preferences.filters.find(filter => filter.id === filterId); - if (savedFilter) { - try { - const filterData = JSON.parse(savedFilter.filter); - return new List(filterData.map(filter => { - const processedFilter = { ...filter, class: filter.class || this.props.className }; - return new ImmutableMap(processedFilter); - })); - } catch (error) { - console.warn('Failed to parse saved filter:', error); - } - } - } - } - - // If we have filters in URL but no filterId, parse them directly - if (filtersParam) { - try { - const queryFilters = JSON.parse(filtersParam); - return new List(queryFilters.map(filter => { - const processedFilter = { ...filter, class: filter.class || this.props.className }; - return new ImmutableMap(processedFilter); - })); - } catch (error) { - console.warn('Failed to parse URL filters:', error); - } - } - - return new List(); - } - toggleMore() { const currentFilter = this.getCurrentFilterInfo(); diff --git a/src/components/BrowserFilter/FilterRow.react.js b/src/components/BrowserFilter/FilterRow.react.js index cf86451037..eb10875eb3 100644 --- a/src/components/BrowserFilter/FilterRow.react.js +++ b/src/components/BrowserFilter/FilterRow.react.js @@ -118,12 +118,6 @@ const FilterRow = ({ }) => { const setFocus = useCallback(input => { if (input !== null && editMode) { - // For DateTimeEntry components, don't auto-focus as it opens the calendar - // Check if the input has a focus method that opens a popover/calendar - if (input.focus && input.open) { - // This is likely a DateTimeEntry component, skip auto-focus - return; - } input.focus(); } }, []); From 8517b56910f83f7b25eea4eec91094ff1e79513b Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 09:59:18 +0200 Subject: [PATCH 04/10] fix --- .../BrowserFilter/BrowserFilter.react.js | 102 +++++++++++++++--- .../BrowserFilter/FilterRow.react.js | 6 ++ src/dashboard/Data/Browser/Browser.react.js | 12 +-- 3 files changed, 97 insertions(+), 23 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index a18ccb503e..df42a8515f 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -51,17 +51,23 @@ export default class BrowserFilter extends React.Component { if (props.className !== this.props.className) { this.setState({ open: false }); } - + // Auto-open filter dialog if editFilter=true is in URL const urlParams = new URLSearchParams(window.location.search); const isEditFilterMode = urlParams.get('editFilter') === 'true'; - + if (isEditFilterMode && !this.state.open) { // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); - + + // Load filter data from URL if props.filters is empty + let filtersToDisplay = props.filters; + if (props.filters.size === 0) { + filtersToDisplay = this.loadFiltersFromURL(); + } + // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(props.filters); + const filters = this.convertDatesForDisplay(filtersToDisplay); this.setState({ open: true, showMore: true, // Open in edit mode @@ -71,7 +77,7 @@ export default class BrowserFilter extends React.Component { originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: props.filters, // Store original filters for comparison + originalFilters: filtersToDisplay, // Store original filters for comparison }); } } @@ -80,13 +86,19 @@ export default class BrowserFilter extends React.Component { // Check if we should auto-open for edit mode on initial load const urlParams = new URLSearchParams(window.location.search); const isEditFilterMode = urlParams.get('editFilter') === 'true'; - + if (isEditFilterMode) { // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); - + + // Load filter data from URL if props.filters is empty + let filtersToDisplay = this.props.filters; + if (this.props.filters.size === 0) { + filtersToDisplay = this.loadFiltersFromURL(); + } + // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(this.props.filters); + const filters = this.convertDatesForDisplay(filtersToDisplay); this.setState({ open: true, showMore: true, // Open in edit mode @@ -96,7 +108,7 @@ export default class BrowserFilter extends React.Component { originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: this.props.filters, // Store original filters for comparison + originalFilters: filtersToDisplay, // Store original filters for comparison }); } } @@ -165,16 +177,23 @@ export default class BrowserFilter extends React.Component { } return false; - } getCurrentFilterInfo() { + } + + getCurrentFilterInfo() { // Extract filterId from URL if present const urlParams = new URLSearchParams(window.location.search); const filterId = urlParams.get('filterId'); const filtersParam = urlParams.get('filters'); + // Extract className from URL path to handle cross-class navigation + const pathParts = window.location.pathname.split('/'); + const browserIndex = pathParts.indexOf('browser'); + const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + if (filterId) { const preferences = ClassPreferences.getPreferences( this.context.applicationId, - this.props.className + urlClassName ); if (preferences.filters) { @@ -207,14 +226,14 @@ export default class BrowserFilter extends React.Component { if (filtersParam && this.props.filters.size > 0) { const preferences = ClassPreferences.getPreferences( this.context.applicationId, - this.props.className + urlClassName ); if (preferences.filters) { // Normalize current filters for comparison (remove class property if it matches current className) const currentFilters = this.props.filters.toJS().map(filter => { const normalizedFilter = { ...filter }; - if (normalizedFilter.class === this.props.className) { + if (normalizedFilter.class === urlClassName) { delete normalizedFilter.class; } return normalizedFilter; @@ -227,7 +246,7 @@ export default class BrowserFilter extends React.Component { // Normalize saved filters for comparison (remove class property if it matches current className) const normalizedSavedFilters = savedFilters.map(filter => { const normalizedFilter = { ...filter }; - if (normalizedFilter.class === this.props.className) { + if (normalizedFilter.class === urlClassName) { delete normalizedFilter.class; } return normalizedFilter; @@ -272,6 +291,55 @@ export default class BrowserFilter extends React.Component { }; } + loadFiltersFromURL() { + const urlParams = new URLSearchParams(window.location.search); + const filtersParam = urlParams.get('filters'); + const filterId = urlParams.get('filterId'); + + // Extract className from URL path to handle cross-class navigation + const pathParts = window.location.pathname.split('/'); + const browserIndex = pathParts.indexOf('browser'); + const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + + // If we have a filterId, load from saved filters + if (filterId) { + const preferences = ClassPreferences.getPreferences( + this.context.applicationId, + urlClassName + ); + + if (preferences.filters) { + const savedFilter = preferences.filters.find(filter => filter.id === filterId); + if (savedFilter) { + try { + const filterData = JSON.parse(savedFilter.filter); + return new List(filterData.map(filter => { + const processedFilter = { ...filter, class: filter.class || urlClassName }; + return new ImmutableMap(processedFilter); + })); + } catch (error) { + console.warn('Failed to parse saved filter:', error); + } + } + } + } + + // If we have filters in URL but no filterId, parse them directly + if (filtersParam) { + try { + const queryFilters = JSON.parse(filtersParam); + return new List(queryFilters.map(filter => { + const processedFilter = { ...filter, class: filter.class || urlClassName }; + return new ImmutableMap(processedFilter); + })); + } catch (error) { + console.warn('Failed to parse URL filters:', error); + } + } + + return new List(); + } + toggleMore() { const currentFilter = this.getCurrentFilterInfo(); @@ -544,17 +612,17 @@ export default class BrowserFilter extends React.Component { // Convert only Parse Date objects to JavaScript Date objects, preserve RelativeDate objects filters = this.convertDatesForDisplay(filters); } - + // If closing the dialog and we're in edit filter mode, remove the editFilter parameter const urlParams = new URLSearchParams(window.location.search); const isEditFilterMode = urlParams.get('editFilter') === 'true'; - + if (this.state.open && isEditFilterMode) { urlParams.delete('editFilter'); const newUrl = `${window.location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`; window.history.replaceState({}, '', newUrl); } - + this.setState(prevState => ({ open: !prevState.open, filters: filters, diff --git a/src/components/BrowserFilter/FilterRow.react.js b/src/components/BrowserFilter/FilterRow.react.js index eb10875eb3..cf86451037 100644 --- a/src/components/BrowserFilter/FilterRow.react.js +++ b/src/components/BrowserFilter/FilterRow.react.js @@ -118,6 +118,12 @@ const FilterRow = ({ }) => { const setFocus = useCallback(input => { if (input !== null && editMode) { + // For DateTimeEntry components, don't auto-focus as it opens the calendar + // Check if the input has a focus method that opens a popover/calendar + if (input.focus && input.open) { + // This is likely a DateTimeEntry component, skip auto-focus + return; + } input.focus(); } }, []); diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 712ca6a8e3..829e4849e1 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -489,11 +489,11 @@ class Browser extends DashboardView { const filters = this.extractFiltersFromQuery(props); const { className, entityId, relationName } = props.params; const isRelationRoute = entityId && relationName; - + // Check if we're in edit filter mode (don't load data) const query = new URLSearchParams(props.location.search); const isEditFilterMode = query.get('editFilter') === 'true'; - + let relation = this.state.relation; if (isRelationRoute && !relation) { const parentObjectQuery = new Parse.Query(className); @@ -1385,19 +1385,19 @@ class Browser extends DashboardView { editFilter(className, filterData) { // Navigate to the class with the filter loaded for editing const { id, filter } = filterData; - + // Build URL with filter parameters for editing const urlParams = new URLSearchParams(); urlParams.set('filters', filter); if (id) { urlParams.set('filterId', id); } - + // Add edit mode parameter to indicate we want to edit without loading data urlParams.set('editFilter', 'true'); - + const url = `browser/${className}?${urlParams.toString()}`; - + // Navigate to the URL which will trigger the filter dialog to open in edit mode this.props.navigate(generatePath(this.context, url)); } From ae723de5ff817e784ce46eca3a7d2cb4991ea643 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 10:10:29 +0200 Subject: [PATCH 05/10] fix --- .../BrowserFilter/BrowserFilter.react.js | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index df42a8515f..e53cfa6eb4 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -117,11 +117,16 @@ export default class BrowserFilter extends React.Component { // First check if there's a filterId in the URL (means we're definitely viewing a saved filter) const urlParams = new URLSearchParams(window.location.search); const filterId = urlParams.get('filterId'); + + // Extract className from URL path to handle cross-class navigation + const pathParts = window.location.pathname.split('/'); + const browserIndex = pathParts.indexOf('browser'); + const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; if (filterId) { const preferences = ClassPreferences.getPreferences( this.context.applicationId, - this.props.className + urlClassName ); if (preferences.filters) { @@ -137,22 +142,30 @@ export default class BrowserFilter extends React.Component { // Check for legacy filters (filters parameter without filterId) const filtersParam = urlParams.get('filters'); - if (filtersParam && this.props.filters.size > 0) { + if (filtersParam) { const preferences = ClassPreferences.getPreferences( this.context.applicationId, - this.props.className + urlClassName ); if (preferences.filters) { - // Normalize current filters for comparison (remove class property if it matches current className) - const currentFilters = this.props.filters.toJS().map(filter => { + // Parse the URL filters parameter to get the actual filter data + let urlFilters; + try { + urlFilters = JSON.parse(filtersParam); + } catch { + return false; + } + + // Normalize URL filters for comparison (remove class property if it matches current className) + const normalizedUrlFilters = urlFilters.map(filter => { const normalizedFilter = { ...filter }; - if (normalizedFilter.class === this.props.className) { + if (normalizedFilter.class === urlClassName) { delete normalizedFilter.class; } return normalizedFilter; }); - const currentFiltersString = JSON.stringify(currentFilters); + const urlFiltersString = JSON.stringify(normalizedUrlFilters); const matchingFilter = preferences.filters.find(savedFilter => { try { @@ -160,13 +173,13 @@ export default class BrowserFilter extends React.Component { // Normalize saved filters for comparison (remove class property if it matches current className) const normalizedSavedFilters = savedFilters.map(filter => { const normalizedFilter = { ...filter }; - if (normalizedFilter.class === this.props.className) { + if (normalizedFilter.class === urlClassName) { delete normalizedFilter.class; } return normalizedFilter; }); const savedFiltersString = JSON.stringify(normalizedSavedFilters); - return savedFiltersString === currentFiltersString; + return savedFiltersString === urlFiltersString; } catch { return false; } @@ -223,22 +236,37 @@ export default class BrowserFilter extends React.Component { } // Check for legacy filters (filters parameter without filterId) - if (filtersParam && this.props.filters.size > 0) { + if (filtersParam) { const preferences = ClassPreferences.getPreferences( this.context.applicationId, urlClassName ); if (preferences.filters) { - // Normalize current filters for comparison (remove class property if it matches current className) - const currentFilters = this.props.filters.toJS().map(filter => { + // Parse the URL filters parameter to get the actual filter data + let urlFilters; + try { + urlFilters = JSON.parse(filtersParam); + } catch (error) { + console.warn('Failed to parse URL filters:', error); + return { + id: null, + name: '', + isApplied: false, + hasRelativeDates: false, + isLegacy: false + }; + } + + // Normalize URL filters for comparison (remove class property if it matches current className) + const normalizedUrlFilters = urlFilters.map(filter => { const normalizedFilter = { ...filter }; if (normalizedFilter.class === urlClassName) { delete normalizedFilter.class; } return normalizedFilter; }); - const currentFiltersString = JSON.stringify(currentFilters); + const urlFiltersString = JSON.stringify(normalizedUrlFilters); const matchingFilter = preferences.filters.find(savedFilter => { try { @@ -252,7 +280,7 @@ export default class BrowserFilter extends React.Component { return normalizedFilter; }); const savedFiltersString = JSON.stringify(normalizedSavedFilters); - return savedFiltersString === currentFiltersString; + return savedFiltersString === urlFiltersString; } catch { return false; } @@ -377,9 +405,14 @@ export default class BrowserFilter extends React.Component { } isFilterNameExists(name) { + // Extract className from URL path to handle cross-class navigation + const pathParts = window.location.pathname.split('/'); + const browserIndex = pathParts.indexOf('browser'); + const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + const preferences = ClassPreferences.getPreferences( this.context.applicationId, - this.props.className + urlClassName ); if (preferences.filters && name) { From 864ba1ee466c2574eaf2a8d00e43227e045a5a7c Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 10:53:19 +0200 Subject: [PATCH 06/10] simplify cat list structure --- .../CategoryList/CategoryList.react.js | 11 ++-- src/components/CategoryList/CategoryList.scss | 54 +++++++++++-------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index bc05dc3858..aa3ef1ca34 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -140,9 +140,13 @@ export default class CategoryList extends React.Component { return (
- this.props.classClicked()}> - {count} - {c.name} + this.props.classClicked()} + > + {c.name} {c.onEdit && ( )} + {count} {(c.filters || []).length !== 0 && ( Date: Sun, 27 Jul 2025 10:56:03 +0200 Subject: [PATCH 07/10] lint --- src/components/BrowserFilter/BrowserFilter.react.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index e53cfa6eb4..2f3d1ee6f5 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -117,7 +117,7 @@ export default class BrowserFilter extends React.Component { // First check if there's a filterId in the URL (means we're definitely viewing a saved filter) const urlParams = new URLSearchParams(window.location.search); const filterId = urlParams.get('filterId'); - + // Extract className from URL path to handle cross-class navigation const pathParts = window.location.pathname.split('/'); const browserIndex = pathParts.indexOf('browser'); @@ -409,7 +409,7 @@ export default class BrowserFilter extends React.Component { const pathParts = window.location.pathname.split('/'); const browserIndex = pathParts.indexOf('browser'); const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; - + const preferences = ClassPreferences.getPreferences( this.context.applicationId, urlClassName From 4fa70d6e11844c1756ab2ce4716c012e21f3676f Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 11:27:44 +0200 Subject: [PATCH 08/10] optimizations --- .../BrowserFilter/BrowserFilter.react.js | 80 ++++++------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 2f3d1ee6f5..ca6fdd3379 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -47,70 +47,50 @@ export default class BrowserFilter extends React.Component { this.wrapRef = React.createRef(); } - componentWillReceiveProps(props) { - if (props.className !== this.props.className) { - this.setState({ open: false }); - } + getClassNameFromURL() { + const pathParts = window.location.pathname.split('/'); + const browserIndex = pathParts.indexOf('browser'); + return browserIndex >= 0 && pathParts[browserIndex + 1] + ? pathParts[browserIndex + 1] + : this.props.className; + } - // Auto-open filter dialog if editFilter=true is in URL + initializeEditFilterMode() { const urlParams = new URLSearchParams(window.location.search); const isEditFilterMode = urlParams.get('editFilter') === 'true'; if (isEditFilterMode && !this.state.open) { - // Get current filter info including name and relative dates setting const currentFilter = this.getCurrentFilterInfo(); - - // Load filter data from URL if props.filters is empty - let filtersToDisplay = props.filters; - if (props.filters.size === 0) { + let filtersToDisplay = this.props.filters; + if (this.props.filters.size === 0) { filtersToDisplay = this.loadFiltersFromURL(); } - // Convert filters for display and open the dialog const filters = this.convertDatesForDisplay(filtersToDisplay); this.setState({ open: true, - showMore: true, // Open in edit mode + showMore: true, filters: filters, editMode: true, name: currentFilter.name || '', originalFilterName: currentFilter.name || '', relativeDates: currentFilter.hasRelativeDates || false, originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: filtersToDisplay, // Store original filters for comparison + originalFilters: filtersToDisplay, }); } } - componentDidMount() { - // Check if we should auto-open for edit mode on initial load - const urlParams = new URLSearchParams(window.location.search); - const isEditFilterMode = urlParams.get('editFilter') === 'true'; - - if (isEditFilterMode) { - // Get current filter info including name and relative dates setting - const currentFilter = this.getCurrentFilterInfo(); + componentWillReceiveProps(props) { + if (props.className !== this.props.className) { + this.setState({ open: false }); + } - // Load filter data from URL if props.filters is empty - let filtersToDisplay = this.props.filters; - if (this.props.filters.size === 0) { - filtersToDisplay = this.loadFiltersFromURL(); - } + this.initializeEditFilterMode(); + } - // Convert filters for display and open the dialog - const filters = this.convertDatesForDisplay(filtersToDisplay); - this.setState({ - open: true, - showMore: true, // Open in edit mode - filters: filters, - editMode: true, - name: currentFilter.name || '', - originalFilterName: currentFilter.name || '', - relativeDates: currentFilter.hasRelativeDates || false, - originalRelativeDates: currentFilter.hasRelativeDates || false, - originalFilters: filtersToDisplay, // Store original filters for comparison - }); - } + componentDidMount() { + this.initializeEditFilterMode(); } isCurrentFilterSaved() { @@ -118,10 +98,7 @@ export default class BrowserFilter extends React.Component { const urlParams = new URLSearchParams(window.location.search); const filterId = urlParams.get('filterId'); - // Extract className from URL path to handle cross-class navigation - const pathParts = window.location.pathname.split('/'); - const browserIndex = pathParts.indexOf('browser'); - const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + const urlClassName = this.getClassNameFromURL(); if (filterId) { const preferences = ClassPreferences.getPreferences( @@ -198,10 +175,7 @@ export default class BrowserFilter extends React.Component { const filterId = urlParams.get('filterId'); const filtersParam = urlParams.get('filters'); - // Extract className from URL path to handle cross-class navigation - const pathParts = window.location.pathname.split('/'); - const browserIndex = pathParts.indexOf('browser'); - const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + const urlClassName = this.getClassNameFromURL(); if (filterId) { const preferences = ClassPreferences.getPreferences( @@ -324,10 +298,7 @@ export default class BrowserFilter extends React.Component { const filtersParam = urlParams.get('filters'); const filterId = urlParams.get('filterId'); - // Extract className from URL path to handle cross-class navigation - const pathParts = window.location.pathname.split('/'); - const browserIndex = pathParts.indexOf('browser'); - const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + const urlClassName = this.getClassNameFromURL(); // If we have a filterId, load from saved filters if (filterId) { @@ -405,10 +376,7 @@ export default class BrowserFilter extends React.Component { } isFilterNameExists(name) { - // Extract className from URL path to handle cross-class navigation - const pathParts = window.location.pathname.split('/'); - const browserIndex = pathParts.indexOf('browser'); - const urlClassName = browserIndex >= 0 && pathParts[browserIndex + 1] ? pathParts[browserIndex + 1] : this.props.className; + const urlClassName = this.getClassNameFromURL(); const preferences = ClassPreferences.getPreferences( this.context.applicationId, From 19fb77ee1a0c748d885e71efe65995b3f7c81ff7 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:09:16 +0200 Subject: [PATCH 09/10] style fix --- src/components/CategoryList/CategoryList.scss | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/components/CategoryList/CategoryList.scss b/src/components/CategoryList/CategoryList.scss index 34d39abbae..9f3e43a902 100644 --- a/src/components/CategoryList/CategoryList.scss +++ b/src/components/CategoryList/CategoryList.scss @@ -27,6 +27,23 @@ &:hover{ color: white; } + + span { + display: block; + + &:first-of-type { + @include DosisFont; + float: right; + width: 50px; + text-align: right; + } + &:last-of-type { + margin-right: 50px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } } } @@ -53,27 +70,22 @@ display: flex !important; align-items: center; cursor: pointer; - margin-left: 8px; + width: 0px; + margin-right: 20px; padding-left: 0px !important; &:after { @include arrow('down', 10px, 7px, #8fb9cf); content: ''; + margin-left: 10px; } } .link { display: flex; - align-items: center; - a { &:first-of-type { - flex-grow: 1; - margin-right: 8px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + flex-grow: 1 } } - .count { @include DosisFont; color: #8fb9cf; @@ -83,11 +95,10 @@ min-width: 20px; text-align: right; } - .edit { display: flex; align-items: center; - margin-right: 8px; + margin-right: 6px; cursor: pointer; opacity: 0; transition: opacity 0.2s ease; @@ -112,13 +123,14 @@ &:first-of-type { flex-grow: 1; display: flex; + margin-right: 6px; } span { text-align: left !important; margin-left: 14px; - display: flex; - flex-grow: 1; margin-right: 0px !important; + font-family: Dosis, "Helvetica Neue", Helvetica, Arial, sans-serif; + flex-grow: 1; } } .editFilter { From 7fbba50082dcf6bdec44baf0152a42bad98767e1 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:38:11 +0200 Subject: [PATCH 10/10] fix margins --- src/components/CategoryList/CategoryList.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/CategoryList/CategoryList.scss b/src/components/CategoryList/CategoryList.scss index 9f3e43a902..6de579c3ab 100644 --- a/src/components/CategoryList/CategoryList.scss +++ b/src/components/CategoryList/CategoryList.scss @@ -76,7 +76,7 @@ &:after { @include arrow('down', 10px, 7px, #8fb9cf); content: ''; - margin-left: 10px; + margin-left: 6px; } } .link { @@ -91,14 +91,14 @@ color: #8fb9cf; font-size: 12px; margin-left: auto; - margin-right: 8px; + margin-right: 4px; min-width: 20px; text-align: right; } .edit { display: flex; - align-items: center; - margin-right: 6px; + align-items: flex-start; + padding-top: 2px; cursor: pointer; opacity: 0; transition: opacity 0.2s ease; @@ -135,8 +135,8 @@ } .editFilter { display: flex; - align-items: center; - margin-right: 6px; + align-items: flex-start; + padding-top: 2px; cursor: pointer; opacity: 0; transition: opacity 0.2s ease;