Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 180 additions & 20 deletions src/components/BrowserFilter/BrowserFilter.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,82 @@ 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(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() {
// 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(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
});
}
}

isCurrentFilterSaved() {
// 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) {
Expand All @@ -77,36 +142,44 @@ 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 {
const savedFilters = JSON.parse(savedFilter.filter);
// 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;
}
Expand All @@ -117,16 +190,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) {
Expand Down Expand Up @@ -156,36 +236,51 @@ 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,
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 (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 === 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 {
const savedFilters = JSON.parse(savedFilter.filter);
// 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;
}
Expand Down Expand Up @@ -224,6 +319,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();

Expand Down Expand Up @@ -261,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) {
Expand Down Expand Up @@ -496,6 +645,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,
Expand Down
6 changes: 6 additions & 0 deletions src/components/BrowserFilter/FilterRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}, []);
Expand Down
23 changes: 20 additions & 3 deletions src/components/CategoryList/CategoryList.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,13 @@ export default class CategoryList extends React.Component {
return (
<div key={id}>
<div className={styles.link}>
<Link title={c.name} to={{ pathname: link }} className={className} key={id} onClick={() => this.props.classClicked()}>
<span>{count}</span>
<span>{c.name}</span>
<Link
title={c.name}
to={{ pathname: link }}
className={className}
onClick={() => this.props.classClicked()}
>
{c.name}
</Link>
{c.onEdit && (
<a
Expand All @@ -155,6 +159,7 @@ export default class CategoryList extends React.Component {
<Icon name="edit-solid" width={14} height={14} />
</a>
)}
<span className={styles.count}>{count}</span>
{(c.filters || []).length !== 0 && (
<a
className={styles.expand}
Expand Down Expand Up @@ -185,6 +190,17 @@ export default class CategoryList extends React.Component {
>
<span>{name}</span>
</Link>
{this.props.onEditFilter && (
<a
className={styles.editFilter}
onClick={e => {
e.preventDefault();
this.props.onEditFilter(c.name, filterData);
}}
>
<Icon name="edit-solid" width={14} height={14} />
</a>
)}
</div>
);
})}
Expand All @@ -202,4 +218,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.'),
};
Loading
Loading