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;