diff --git a/README.md b/README.md index 46de7ff4..69e21de8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/dashboard/src/components/forms/select/FilterDropdown.tsx b/src/dashboard/src/components/forms/select/FilterDropdown.tsx index 637e5a80..cea8d65f 100644 --- a/src/dashboard/src/components/forms/select/FilterDropdown.tsx +++ b/src/dashboard/src/components/forms/select/FilterDropdown.tsx @@ -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 { options: IOption[]; @@ -38,76 +38,81 @@ export const FilterDropdown = ({ onChange, onBlur, }: FilterDropdownProps) => { - const [selected, setSelected] = React.useState( - value, - ); - const [selectedLabel, setSelectedLabel] = React.useState(undefined); - const [searchTerm, setSearchTerm] = React.useState(''); - const [filteredOptions, setFilteredOptions] = React.useState(options); - const [isOpen, setIsOpen] = React.useState(false); - const wrapperRef = React.useRef(null); - const inputRef = React.useRef(null); + const [selected, setSelected] = React.useState( + value, + ); + const [selectedLabel, setSelectedLabel] = React.useState(undefined); + const [searchTerm, setSearchTerm] = React.useState(''); + const [filteredOptions, setFilteredOptions] = React.useState(options); + const [isOpen, setIsOpen] = React.useState(false); + const wrapperRef = React.useRef(null); + const inputRef = React.useRef(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); + }, [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 (
{label && } {loading && } - +
setIsOpen(!isOpen)}>

{selectedLabel ? selectedLabel : placeholder}

{isOpen && (
  • - e.stopPropagation()} onChange={(e) => setSearchTerm(e.target.value)} - /> + />
  • {filteredOptions.map((option) => (
  • ({ 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 }