Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HOSTSD-320 Fix filter dropdown #128

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ bash do go

The Dashboard web application is setup for hot-reload within a Docker container.

## Helpful Documentation

- [API Swagger](https://localhost:30005/api-docs)
- [Docker Compose Cheat Sheet](https://devhints.io/docker-compose)
- [Docker CLI Cheat Sheet](https://dockerlabs.collabnix.com/docker/cheatsheet/)
- [Next.js](https://nextjs.org/docs)
- [Dotnet Cheat Sheets](https://cheatography.com/tag/dotnet/)
- [Dotnet Entity Framework Tools](https://learn.microsoft.com/en-us/ef/core/cli/dotnet)

## Database Migrations

Database migrations are built with Entity Framework. Dotnet tooling provides a Code-First approach to database migration, which enables the generation of migrations that apply new versions and perform rollbacks to prior versions. These tools provide a simple repeatable and testable Infrastructure as Code implementation.
Expand Down
113 changes: 59 additions & 54 deletions src/dashboard/src/components/forms/select/FilterDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import styles from './Select.module.scss';
import { Spinner } from '@/components';
import classNames from 'classnames';
import { uniqueId } from 'lodash';
import React, { FocusEventHandler } from 'react';
import { FormError, IOption } from '..';
import styles from './Select.module.scss';
import { generateKey } from './utils';
import classNames from 'classnames';

export interface FilterDropdownProps<T> {
options: IOption<T>[];
Expand Down Expand Up @@ -38,76 +38,81 @@ export const FilterDropdown = <T extends unknown>({
onChange,
onBlur,
}: FilterDropdownProps<T>) => {
const [selected, setSelected] = React.useState<string | number | readonly string[] | undefined>(
value,
);
const [selectedLabel, setSelectedLabel] = React.useState<string | undefined>(undefined);
const [searchTerm, setSearchTerm] = React.useState('');
const [filteredOptions, setFilteredOptions] = React.useState(options);
const [isOpen, setIsOpen] = React.useState(false);
const wrapperRef = React.useRef<HTMLDivElement>(null);
const inputRef = React.useRef<HTMLInputElement>(null);
const [selected, setSelected] = React.useState<string | number | readonly string[] | undefined>(
value,
);
const [selectedLabel, setSelectedLabel] = React.useState<string | undefined>(undefined);
const [searchTerm, setSearchTerm] = React.useState('');
const [filteredOptions, setFilteredOptions] = React.useState(options);
const [isOpen, setIsOpen] = React.useState(false);
const wrapperRef = React.useRef<HTMLDivElement>(null);
const inputRef = React.useRef<HTMLInputElement>(null);

React.useEffect(() => {
const selectedOption = options.find((option) => option.value === selected);
setSelectedLabel(typeof selectedOption?.label === 'string' ? selectedOption.label : undefined);
}, [selected, options]);

React.useEffect(() => {
const lowercasedFilter = searchTerm.toLowerCase();
const filteredData = options.filter((item) =>
typeof item.label === 'string' && item.label.toLowerCase().includes(lowercasedFilter)
);
setFilteredOptions(filteredData);
}, [options, searchTerm]);

// Function to handle selection change
const handleSelectChange = (value: string | number, optionLabel: string) => {
setSelected(value);
setSelectedLabel(optionLabel);
onChange?.(value);
setSearchTerm(''); // Clear the search box upon selection
setIsOpen(false); // Close the dropdown upon selection
};

React.useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
setIsOpen(false);
}
}
React.useEffect(() => {
setSelected(value);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to update the state based on the currently set value.

}, [value]);

if (isOpen && inputRef.current) {
inputRef.current.focus();
React.useEffect(() => {
const selectedOption = options.find((option) => option.value === selected);
setSelectedLabel(typeof selectedOption?.label === 'string' ? selectedOption.label : undefined);
}, [selected, options]);

React.useEffect(() => {
const lowercasedFilter = searchTerm.toLowerCase();
const filteredData = options.filter(
(item) =>
typeof item.label === 'string' && item.label.toLowerCase().includes(lowercasedFilter),
);
setFilteredOptions(filteredData);
}, [options, searchTerm]);

React.useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
setIsOpen(false);
}

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, wrapperRef]);
}

if (isOpen && inputRef.current) {
inputRef.current.focus();
}

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, wrapperRef]);

// Function to handle selection change
const handleSelectChange = (value: string | number, optionLabel: string) => {
setSelected(value);
setSelectedLabel(optionLabel);
onChange?.(value);
setSearchTerm(''); // Clear the search box upon selection
setIsOpen(false); // Close the dropdown upon selection
};

return (
<div className={styles.dropdown} ref={wrapperRef}>
{label && <label htmlFor={id}>{label}</label>}
{loading && <Spinner className={styles.spinner} />}

<div className={styles.filterDropdown} title={title} onClick={() => setIsOpen(!isOpen)}>
<p title={selectedLabel && selectedLabel}>{selectedLabel ? selectedLabel : placeholder}</p>
{isOpen && (
<ul className={styles.dropdownList}>
<li>
<input
<input
type="text"
id={id}
name={name}
value={searchTerm}
disabled={disabled}
placeholder={"Search list"}
placeholder={'Search list'}
ref={inputRef}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setSearchTerm(e.target.value)}
/>
/>
</li>
{filteredOptions.map((option) => (
<li
Expand All @@ -117,9 +122,9 @@ export const FilterDropdown = <T extends unknown>({
selected === option.value ||
(Array.isArray(selected) && selected.includes(option.value)),
})}
onClick={() =>
(typeof option.value === 'string' || typeof option.value === 'number') &&
(typeof option.label === 'string')
onClick={() =>
(typeof option.value === 'string' || typeof option.value === 'number') &&
typeof option.label === 'string'
? handleSelectChange(option.value, option.label ?? '')
: null
}
Expand Down
Loading