Skip to content

Commit

Permalink
Add a filter for the species search results table (#1036)
Browse files Browse the repository at this point in the history
  • Loading branch information
azangru authored Oct 12, 2023
1 parent c0a6c0b commit 13b9862
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@
padding-left: $global-padding-left;
}

.topSection {
display: grid;
grid-template-areas:
"search-field ."
"results-summary filter";
grid-template-columns: max-content max-content;
column-gap: 80px;
row-gap: 32px;
}

.searchFieldWrapper {
grid-area: search-field;
}

.resultsSummaryWrapper {
grid-area: results-summary;
}

.filterFieldWrapper {
grid-area: filter;
}

.loader {
margin-top: 40px;
margin-left: 60px;
}

.tableContainer {
overflow: auto;
padding-bottom: $global-padding-bottom;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useDeferredValue } from 'react';

import { useLazyGetSpeciesSearchResultsQuery } from 'src/content/app/new-species-selector/state/species-selector-api-slice/speciesSelectorApiSlice';

Expand All @@ -24,7 +24,10 @@ import AddSpecies from 'src/content/app/new-species-selector/components/species-
import SpeciesSearchField from '../species-search-field/SpeciesSearchField';
import SpeciesSearchResultsSummary from 'src/content/app/new-species-selector/components/species-search-results-summary/SpeciesSearchResultsSummary';
import SpeciesSearchResultsTable from 'src/content/app/new-species-selector/components/species-search-results-table/SpeciesSearchResultsTable';
import GenomesFilterField from 'src/content/app/new-species-selector/components/genomes-filter-field/GenomesFilterField';
import { CircleLoader } from 'src/shared/components/loader';

import type { SpeciesSearchResponse } from 'src/content/app/new-species-selector/state/species-selector-api-slice/speciesSelectorApiSlice';
import type { SpeciesSearchMatch } from 'src/content/app/new-species-selector/types/speciesSearchMatch';

import styles from './GenomeSelectorBySearchQuery.scss';
Expand All @@ -37,17 +40,23 @@ type Props = {

const GenomeSelectorBySearchQuery = (props: Props) => {
const { query, onClose } = props;
const [filterQuery, setFilterQuery] = useState('');
const [canSubmitSearch, setCanSubmitSearch] = useState(false);
const [searchTrigger, result] = useLazyGetSpeciesSearchResultsQuery();
const { currentData } = result;
const { currentData, isLoading } = result;

const {
genomes,
stagedGenomes,
isTableExpanded,
onTableExpandToggle,
onGenomePreselectToggle
} = useSelectableGenomesTable(currentData?.matches ?? []);
} = useSelectableGenomesTable({
genomes: currentData?.matches ?? [],
filterQuery
});

const deferredGenomes = useDeferredValue(genomes);

useEffect(() => {
searchTrigger({ query });
Expand All @@ -70,39 +79,104 @@ const GenomeSelectorBySearchQuery = (props: Props) => {

return (
<div className={styles.main}>
{currentData?.matches.length ? (
<AddSpecies
query={query}
canAdd={stagedGenomes.length > 0}
onAdd={onSpeciesAdd}
onCancel={onClose}
/>
) : (
<SpeciesSearchField
onInput={onSearchInput}
canSubmit={canSubmitSearch}
onSearchSubmit={onSearchSubmit}
/>
)}

{currentData && (
<>
<SpeciesSearchResultsSummary searchResult={currentData} />

{currentData.matches.length > 0 && (
<div className={styles.tableContainer}>
<SpeciesSearchResultsTable
results={genomes}
isExpanded={isTableExpanded}
onTableExpandToggle={onTableExpandToggle}
onSpeciesSelectToggle={onGenomePreselectToggle}
/>
</div>
)}
</>
<TopSection
query={query}
isLoading={isLoading}
searchResults={currentData}
canAddGenomes={stagedGenomes.length > 0}
canSubmitSearch={canSubmitSearch}
onSearchSubmit={onSearchSubmit}
onSearchInput={onSearchInput}
onGenomesAdd={onSpeciesAdd}
onFilterChange={setFilterQuery}
onClose={onClose}
/>

{currentData && currentData.matches.length > 0 && (
<div className={styles.tableContainer}>
<SpeciesSearchResultsTable
results={deferredGenomes}
isExpanded={isTableExpanded}
onTableExpandToggle={onTableExpandToggle}
onSpeciesSelectToggle={onGenomePreselectToggle}
/>
</div>
)}
</div>
);
};

// TODO: consider errors in response to search request
type TopSectionProps = {
query: string;
isLoading: boolean;
searchResults?: SpeciesSearchResponse;
canAddGenomes: boolean;
canSubmitSearch: boolean;
onSearchSubmit: () => void;
onSearchInput: () => void;
onGenomesAdd: () => void;
onFilterChange: (filter: string) => void;
onClose: () => void;
};

const TopSection = (props: TopSectionProps) => {
if (props.isLoading) {
return (
<>
<AddSpecies
query={props.query}
canAdd={false}
onAdd={props.onGenomesAdd}
onCancel={props.onClose}
/>
<CircleLoader className={styles.loader} />
</>
);
}

// search returned some results
if (props.searchResults?.matches.length) {
return (
<section className={styles.topSection}>
<div className={styles.searchFieldWrapper}>
<AddSpecies
query={props.query}
canAdd={props.canAddGenomes}
onAdd={props.onGenomesAdd}
onCancel={props.onClose}
/>
</div>
<div className={styles.resultsSummaryWrapper}>
<SpeciesSearchResultsSummary searchResults={props.searchResults} />
</div>
<div className={styles.filterFieldWrapper}>
<GenomesFilterField onFilterChange={props.onFilterChange} />
</div>
</section>
);
}

// search returned no results
if (props.searchResults?.matches.length === 0) {
return (
<section className={styles.topSection}>
<div className={styles.searchFieldWrapper}>
<SpeciesSearchField
onInput={props.onSearchInput}
canSubmit={props.canSubmitSearch}
onSearchSubmit={props.onSearchSubmit}
/>
</div>
<div className={styles.resultsSummaryWrapper}>
<SpeciesSearchResultsSummary searchResults={props.searchResults} />
</div>
</section>
);
}

// this must be an error
return <div>An unexpected error happened during search.</div>;
};

export default GenomeSelectorBySearchQuery;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
grid-area: top;
align-items: center;
justify-self: start;
grid-template-columns: [species-image] 60px [genomes-count] auto [add-button] 1fr;
grid-template-columns: [species-image] 60px [genomes-count] auto [add-button] max-content [filter] max-content;
column-gap: $standard-gutter;
}

Expand All @@ -43,6 +43,15 @@
margin-left: $standard-gutter;
}

.filterWrapper {
grid-column: filter;
padding-left: 400px;
}

.loader {
grid-area: table;
}

.tableContainer {
grid-area: table;
overflow: auto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import React, { useEffect } from 'react';
import React, { useState, useEffect } from 'react';

import { useAppDispatch } from 'src/store';

Expand All @@ -24,7 +24,9 @@ import { commitSelectedSpeciesAndSave } from 'src/content/app/new-species-select
import useSelectableGenomesTable from 'src/content/app/new-species-selector/components/selectable-genomes-table/useSelectableGenomesTable';

import SpeciesSearchResultsTable from 'src/content/app/new-species-selector/components/species-search-results-table/SpeciesSearchResultsTable';
import GenomesFilterField from 'src/content/app/new-species-selector/components/genomes-filter-field/GenomesFilterField';
import { PrimaryButton } from 'src/shared/components/button/Button';
import { CircleLoader } from 'src/shared/components/loader';
import InfoPill from 'src/shared/components/info-pill/InfoPill';

import styles from './GenomeSelectorBySpeciesTaxonomyId.scss';
Expand All @@ -36,29 +38,35 @@ type Props = {

const GenomeSelectorBySpeciesTaxonomyId = (props: Props) => {
const { speciesTaxonomyId } = props;
const [filterQuery, setFilterQuery] = useState('');
const [searchTrigger, result] = useLazyGetGenomesBySpeciesTaxonomyIdQuery();
const { currentData } = result;
const { currentData, isLoading, isError } = result;

const {
genomes,
stagedGenomes,
isTableExpanded,
onTableExpandToggle,
onGenomePreselectToggle
} = useSelectableGenomesTable(currentData?.matches ?? []);
} = useSelectableGenomesTable({
genomes: currentData?.matches ?? [],
filterQuery
});

useEffect(() => {
searchTrigger({ speciesTaxonomyId });
}, []);

return (
<div className={styles.main}>
{isLoading && <CircleLoader className={styles.loader} />}
{currentData && (
<>
<TopContent
<TopSection
{...props}
genomes={genomes}
stagedGenomes={stagedGenomes}
onFilterChange={setFilterQuery}
/>
<div className={styles.tableContainer}>
<SpeciesSearchResultsTable
Expand All @@ -70,16 +78,18 @@ const GenomeSelectorBySpeciesTaxonomyId = (props: Props) => {
</div>
</>
)}
{isError && <div>An unexpected error has occurred</div>}
</div>
);
};

type TopContentProps = Props & {
type TopSectionProps = Props & {
genomes: ReturnType<typeof useSelectableGenomesTable>['genomes'];
stagedGenomes: ReturnType<typeof useSelectableGenomesTable>['stagedGenomes'];
onFilterChange: (filter: string) => void;
};

const TopContent = (props: TopContentProps) => {
const TopSection = (props: TopSectionProps) => {
const { speciesImageUrl, genomes, stagedGenomes } = props;
const dispatch = useAppDispatch();

Expand All @@ -93,7 +103,7 @@ const TopContent = (props: TopContentProps) => {
const totalGenomesCount = genomes.length;

return (
<div className={styles.top}>
<section className={styles.top}>
{speciesImageUrl && (
<span className={styles.speciesImage}>
<img src={speciesImageUrl} />
Expand All @@ -112,7 +122,10 @@ const TopContent = (props: TopContentProps) => {
>
Add
</PrimaryButton>
</div>
<div className={styles.filterWrapper}>
<GenomesFilterField onFilterChange={props.onFilterChange} />
</div>
</section>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { type FormEvent } from 'react';

import ShadedInput from 'src/shared/components/input/ShadedInput';

const GenomesFilterField = (props: {
className?: string;
onFilterChange: (filterQuery: string) => void;
}) => {
const onInput = (event: FormEvent<HTMLInputElement>) => {
const filterQuery = event.currentTarget.value;
props.onFilterChange(filterQuery);
};

return (
<ShadedInput
placeholder="Filter results"
type="search"
onInput={onInput}
className={props.className}
/>
);
};

export default GenomesFilterField;
Loading

0 comments on commit 13b9862

Please sign in to comment.