diff --git a/src/MetadataDrawer.tsx b/src/MetadataDrawer.tsx index c03abf2..2374c38 100644 --- a/src/MetadataDrawer.tsx +++ b/src/MetadataDrawer.tsx @@ -13,9 +13,7 @@ import makeStyles from "@mui/styles/makeStyles"; import { theme, HtmlTooltip, icons, Typography } from "@gliff-ai/style"; import SVG from "react-inlinesvg"; import { MetaItem } from "@/interfaces"; -import { getLabelsFromKeys, MetadataLabel } from "@/search/SearchBar"; - -type MetadataNameMap = { [index: string]: string }; +import { getLabelsFromKeys } from "@/helpers"; const useStyles = makeStyles({ paperHeader: { @@ -85,7 +83,7 @@ const useStyles = makeStyles({ svgSmall: { width: "12px", height: "100%" }, }); -export const metadataNameMap: MetadataNameMap = { +export const dataNameMap: { [index: string]: string } = { imageName: "Name", size: "Size", dateCreated: "Created", @@ -103,6 +101,8 @@ export const metadataNameMap: MetadataNameMap = { assignees: "Assignees", }; +type DataKeyLabel = { key: string; label: string }; + interface Props { metadata: MetaItem; close: () => void; @@ -111,13 +111,13 @@ interface Props { export default function MetadataDrawer(props: Props): ReactElement { const classes = useStyles(); const [hover, sethover] = useState(false); - const [metaKeys, setMetaKeys] = useState([]); + const [metaKeys, setMetaKeys] = useState([]); useEffect(() => { setMetaKeys( Object.keys(props.metadata).reduce( - getLabelsFromKeys, - [] as MetadataLabel[] + getLabelsFromKeys(dataNameMap)(), + [] as DataKeyLabel[] ) ); }, [props.metadata]); @@ -178,8 +178,8 @@ export default function MetadataDrawer(props: Props): ReactElement { ; diff --git a/src/filter/index.ts b/src/filter/index.ts new file mode 100644 index 0000000..2ad97b0 --- /dev/null +++ b/src/filter/index.ts @@ -0,0 +1,204 @@ +import type { Filter, FilterData, FilterDataItem } from "./interfaces"; + +export class Filters { + activeFilters: Filter[]; + + isGrouped: boolean; + + sortedBy: string | null; + + constructor() { + this.activeFilters = []; + this.isGrouped = false; + this.sortedBy = null; + } + + private compare = ( + a: string | Date | number, + b: string | Date | number, + ascending: boolean + ): number => { + // Compare two values. Undefined values are always at the end. + if (a === undefined) { + return 1; + } + if (b === undefined) { + return -1; + } + if (a < b) { + return ascending ? -1 : 1; + } + if (a > b) { + return ascending ? 1 : -1; + } + return 0; + }; + + getDataKeys = (item: FilterDataItem): string[] => + Object.keys(item).filter((k) => k !== "selected"); + + hasAnyFilters = (): boolean => this.activeFilters.length > 0; + + private hasFilter = (filter: Filter): boolean => + this.activeFilters.some( + (filt) => filt.key === filter.key && filt.value === filter.value + ); + + resetFilters = (data: FilterData): FilterData => { + this.activeFilters = []; + return this.selectAll(data); + }; + + selectAll = (data: FilterData): FilterData => + data.map((i) => ({ ...i, filterShow: true })); + + isSelectAll = (filter: Filter): boolean => { + const { value, key } = filter; + return value === "All values" || key === "" || value === ""; + }; + + toggleFilter = (filter: Filter): void => { + if (this.hasFilter(filter)) { + this.activeFilters.splice(this.activeFilters.indexOf(filter), 1); + } else { + this.activeFilters.push(filter); + } + }; + + addFilter = (filter: Filter): void => { + if (!this.hasFilter(filter)) { + this.activeFilters.push(filter); + } + }; + + applyFilter = (data: FilterData, filter: Filter): FilterData => { + if (this.isSelectAll(filter)) { + return this.resetFilters(data); + } + this.addFilter(filter); + return this.filterData(data); + }; + + private getKeyType = (data: FilterData, key: string): string => { + if (key?.toLowerCase().includes("date")) return "date"; + for (const mitem of data) { + const someType = typeof mitem[key]; + if (someType !== "undefined") { + return someType; + } + } + return "undefined"; + }; + + sortData = ( + data: FilterData, + key: string, + ascending = true + ): FilterData | null => { + const dataCopy = [...data]; + const dataType = this.getKeyType(data, key); + + function toDate(value: string): Date { + return value !== undefined ? new Date(value) : undefined; + } + + this.sortedBy = key; + + switch (dataType) { + case "number": + dataCopy.sort((a: FilterDataItem, b: FilterDataItem): number => + this.compare(a[key] as number, b[key] as number, ascending) + ); + return dataCopy; + case "date": + dataCopy.sort((a, b): number => + this.compare( + toDate(a[key] as string), + toDate(b[key] as string), + ascending + ) + ); + return dataCopy; + + case "string": + dataCopy.sort((a: FilterDataItem, b: FilterDataItem): number => + this.compare(a[key] as number, b[key] as number, ascending) + ); + return dataCopy; + + default: + console.warn(`Cannot sort values with type "${dataType}".`); + this.sortedBy = null; + return null; + } + }; + + filterData = (data: FilterData): FilterData => { + const dataCopy = [...data]; + if (this.activeFilters.length > 0) { + dataCopy.forEach((item) => { + this.activeFilters.forEach((filter, fi) => { + const value = item[filter.key]; + + // current filter selection + const currentSel = Number( + Array.isArray(value) + ? value.some((v) => v.includes(filter.value)) + : String(value).includes(filter.value) + ); + + // selection for all filters up to current + const prevSel = fi === 0 ? 1 : Number(item.filterShow); + + // update 'filterShow' field + item.filterShow = Boolean(prevSel * currentSel); + }); + }); + return dataCopy; + } + // select all items + return this.selectAll(dataCopy); + }; + + private getMonthAndYear = (date: string): string => + date !== undefined + ? new Date(date).toLocaleDateString("en-GB", { + month: "short", + year: "numeric", + }) + : ""; + + toggleIsGrouped = (): void => { + this.isGrouped = !this.isGrouped; + }; + + resetSort = (): void => { + this.sortedBy = null; + }; + + groupByValue = (data: FilterData): FilterData => { + // Assign the newGroup field to all items, based on the same key used for sort + if (!this.sortedBy || !this.isGrouped) return data; + + const areValuesEqual = this.sortedBy?.toLowerCase().includes("date") + ? (value: unknown, previousValue: unknown) => + this.getMonthAndYear(value as string) !== + this.getMonthAndYear(previousValue as string) + : (value: unknown, previousValue: unknown) => value !== previousValue; + + let prevValue: unknown = null; + data.forEach((item) => { + if (!item.filterShow) return; + // Number.MAX_VALUE added to handle missing values + const value = (item[this.sortedBy] as string) || Number.MAX_VALUE; + if (!prevValue || areValuesEqual(value, prevValue)) { + item.newGroup = true; + } else { + item.newGroup = false; + } + prevValue = value; + }); + return data; + }; +} +export type { Filter, FilterData, FilterDataItem }; diff --git a/src/filter/interfaces.ts b/src/filter/interfaces.ts new file mode 100644 index 0000000..a9fb19b --- /dev/null +++ b/src/filter/interfaces.ts @@ -0,0 +1,14 @@ +type Filter = { + key: string; + value: string; +}; + +interface FilterDataItem { + [key: string]: string | number | boolean | string[] | undefined | null; + filterShow: boolean; + newGroup: boolean; +} + +type FilterData = FilterDataItem[]; + +export type { Filter, FilterDataItem, FilterData }; diff --git a/src/helpers.test.ts b/src/helpers.test.ts index 642d2b5..622418d 100644 --- a/src/helpers.test.ts +++ b/src/helpers.test.ts @@ -1,7 +1,8 @@ -import { Filter, MetaItem } from "@/interfaces"; -import { sortMetadata, filterMetadata } from "./helpers"; +import { MetaItem } from "@/interfaces"; +import { Filter, FilterData, Filters } from "./filter"; type TestMetaData = Partial[]; +const filters = new Filters(); const metadata: Partial[] = [ { string: "through", @@ -31,8 +32,12 @@ describe("sort metadata with missing values", () => { test.each(testSample)( `sort values of type %s`, (key: string, output: any[]) => { - const metadataAsc = sortMetadata(cloneMetadata(), key); - const metadataDes = sortMetadata(cloneMetadata(), key, false); + const metadataAsc = filters.sortData(cloneMetadata() as FilterData, key); + const metadataDes = filters.sortData( + cloneMetadata() as FilterData, + key, + false + ); const arrayUndefined = Array.from( metadata.filter((mitem) => !Object.keys(mitem).includes(key)) ).fill(undefined); @@ -48,8 +53,9 @@ describe("sort metadata with missing values", () => { ); }); -const testFilter = (filters: Filter[], outcome: TestMetaData): void => { - const newMetadata = filterMetadata(cloneMetadata(), filters); +const testFilter = (activeFilters: Filter[], outcome: TestMetaData): void => { + filters.activeFilters = activeFilters; + const newMetadata = filters.filterData(cloneMetadata() as FilterData); expect( newMetadata .filter(({ filterShow }) => filterShow) diff --git a/src/helpers.ts b/src/helpers.ts index 083ae45..c4f9666 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,3 @@ -import { Metadata, MetaItem, Filter } from "@/interfaces"; - function kCombinations(set: unknown[], k: number): unknown[][] { if (k > set.length || k <= 0) { return []; @@ -30,109 +28,6 @@ function shuffle(array: unknown[]): void { [array[i], array[j]] = [array[j], array[i]]; } } - -function compare( - a: string | Date | number, - b: string | Date | number, - ascending: boolean -): number { - // compare two values. - // undefined values always at the end of the array. - if (a === undefined) { - return 1; - } - if (b === undefined) { - return -1; - } - if (a < b) { - return ascending ? -1 : 1; - } - if (a > b) { - return ascending ? 1 : -1; - } - return 0; -} - -function getKeyType(metadata: Metadata, key: string): string { - if (key?.toLowerCase().includes("date")) return "date"; - for (const mitem of metadata) { - const someType = typeof mitem[key]; - if (someType !== "undefined") { - return someType; - } - } - return "undefined"; -} - -function sortMetadata( - metadata: Metadata, - key: string, - ascending = true -): Metadata | null { - const metaCopy = [...metadata]; - const metaType = getKeyType(metadata, key); - - function toDate(value: string): Date { - return value !== undefined ? new Date(value) : undefined; - } - - switch (metaType) { - case "number": - metaCopy.sort((a: MetaItem, b: MetaItem): number => - compare(a[key] as number, b[key] as number, ascending) - ); - return metaCopy; - case "date": - metaCopy.sort((a, b): number => - compare(toDate(a[key] as string), toDate(b[key] as string), ascending) - ); - return metaCopy; - - case "string": - metaCopy.sort((a: MetaItem, b: MetaItem): number => - compare(a[key] as number, b[key] as number, ascending) - ); - return metaCopy; - - case "undefined": - console.warn(`No values set for metadata key "${key}".`); - return null; - - default: - console.warn(`Cannot sort values with type "${metaType}".`); - return null; - } -} - -function filterMetadata(metadata: Metadata, activeFilters: Filter[]): Metadata { - if (activeFilters.length > 0) { - metadata.forEach((mitem: MetaItem) => { - activeFilters.forEach((filter, fi) => { - const value = mitem[filter.key]; - - // current filter selection - const currentSel = Number( - Array.isArray(value) - ? value.some((v) => v.includes(filter.value)) - : String(value).includes(filter.value) - ); - - // selection for all filters up to current - const prevSel = fi === 0 ? 1 : Number(mitem.filterShow); - - // update 'filterShow' field - mitem.filterShow = Boolean(prevSel * currentSel); - }); - }); - } else { - // all items selected - metadata.forEach((mitem: MetaItem) => { - mitem.filterShow = true; - }); - } - return metadata; -} - const makeThumbnail = (image: Array>): string => { const canvas = document.createElement("canvas"); canvas.width = 128; @@ -161,6 +56,37 @@ const makeThumbnail = (image: Array>): string => { return canvas.toDataURL(); }; +const getLabelsFromKeys = + (keyToLabelMap: { [key: string]: string }) => + (excludedKeys: string[] = []) => + ( + acc: { key: string; label: string }[], + key: string + ): { key: string; label: string }[] => { + // Just an example of how to exclude data from the list if we need + if ( + [ + "fileMetaVersion", + "id", + "thumbnail", + "selected", + "usersWithAnnotations", + "newGroup", + "filterShow", + "fileName", + ...excludedKeys, + ].includes(key) + ) + return acc; + + const label = keyToLabelMap[key] || key; + acc.push({ + label, + key, + }); + return acc; + }; + const getMonthAndYear = (date: string): string => date !== undefined ? new Date(date).toLocaleDateString("en-GB", { @@ -172,8 +98,7 @@ const getMonthAndYear = (date: string): string => export { kCombinations, shuffle, - sortMetadata, - filterMetadata, makeThumbnail, getMonthAndYear, + getLabelsFromKeys, }; diff --git a/src/index.tsx b/src/index.tsx index 104c93f..5737929 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,9 @@ import UserInterface from "./ui"; export type { MetaItem } from "./interfaces"; +export { Filters } from "./filter"; +export type { FilterData, FilterDataItem } from "./filter"; +export { SearchBar, SearchFilterCard } from "./search"; +export { getLabelsFromKeys } from "./helpers"; +export { SortPopover } from "./sort"; export default UserInterface; diff --git a/src/interfaces.ts b/src/interfaces.ts index 5e692b5..27f5a1d 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -26,10 +26,5 @@ type MetaItem = { usersWithAnnotations?: string[]; }; -type Filter = { - key: string; - value: string; -}; - export { UserAccess }; -export type { Profile, Metadata, MetaItem, Filter }; +export type { Profile, Metadata, MetaItem }; diff --git a/src/search/LabelsFilterAccordion.tsx b/src/search/LabelsFilterAccordion.tsx index 3f24a7c..29934f2 100644 --- a/src/search/LabelsFilterAccordion.tsx +++ b/src/search/LabelsFilterAccordion.tsx @@ -33,6 +33,7 @@ const labelList = { }, }; const accordion = { + marginTop: "15px", borderRadius: "9px", position: "static", "& .Mui-expanded": { diff --git a/src/search/SearchBar.tsx b/src/search/SearchBar.tsx index 58914f7..0bad7fd 100644 --- a/src/search/SearchBar.tsx +++ b/src/search/SearchBar.tsx @@ -1,34 +1,28 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { ChangeEvent, useState, useEffect, ReactElement } from "react"; - -import { Card, CardContent, Paper, TextField } from "@mui/material"; +import { + ChangeEvent, + useState, + useEffect, + ReactElement, + useCallback, +} from "react"; +import { Card, Paper, TextField } from "@mui/material"; import Autocomplete, { AutocompleteRenderInputParams, } from "@mui/material/Autocomplete"; -import { BaseIconButton, theme } from "@gliff-ai/style"; -import { metadataNameMap } from "@/MetadataDrawer"; -import { tooltips } from "@/components/Tooltips"; -import { Metadata, MetaItem, Filter } from "@/interfaces"; - -const cardContent = { - backgroundColor: theme.palette.primary.light, - borderRadius: "9px", - marginTop: "15px", - height: "110px", - padding: "inherit", - marginBottom: "15px", -}; +import { IconButton, theme, icons } from "@gliff-ai/style"; +import { Filters, FilterData, FilterDataItem } from "@/filter"; -interface Props { - metadata: Metadata; - metadataKeys: string[]; - callbackSearch: (filter: Filter) => void; -} - -interface MetadataLabel { +interface DataKeyLabel { key: string; label: string; } +interface Props { + data: FilterData; + filters: Filters; + updateData: (func: (data: FilterData) => FilterData) => void; + getLabelsFromKeys: (acc: DataKeyLabel[], key: string) => DataKeyLabel[]; +} // To be able to style the dropdown list const CustomPaper = (props: Record) => ( @@ -39,47 +33,24 @@ const CustomPaper = (props: Record) => ( /> ); -const getLabelsFromKeys = ( - acc: MetadataLabel[], - key: string -): MetadataLabel[] => { - // Just an example of how to exclude metadata from the list if we need - if ( - [ - "fileMetaVersion", - "id", - "thumbnail", - "selected", - "newGroup", - "filterShow", - "usersWithAnnotations", - "fileName", - ].includes(key) - ) - return acc; - - const label = metadataNameMap[key] || key; - acc.push({ - label, - key, - }); - return acc; -}; - function SearchBar({ - metadata, - metadataKeys, - callbackSearch, + data, + filters, + updateData, + getLabelsFromKeys, }: Props): ReactElement { - const [inputKey, setInputKey] = useState(); + const [dataKeys, setDataKeys] = useState([]); + const [inputKey, setInputKey] = useState(); const [inputOptions, setOptions] = useState([]); const [inputValue, setInputValue] = useState(""); - const [metadataLabels, setMetadataLabels] = useState([]); + const [dataKeyLabels, setDataKeyLabels] = useState([]); + + const updateOptions = useCallback((): void => { + if (!inputKey?.key || !dataKeys.includes(inputKey.key)) return; - const updateOptions = (): void => { - if (!inputKey?.key || !metadataKeys.includes(inputKey.key)) return; const options: Set = new Set(); - metadata.forEach((mitem: MetaItem) => { + + data.forEach((mitem: FilterDataItem) => { if (mitem.filterShow && mitem[inputKey.key] !== undefined) { const value = mitem[inputKey.key]; if (Array.isArray(value)) { @@ -89,56 +60,84 @@ function SearchBar({ } } }); + options.add("All values"); setOptions(Array.from(options)); - }; + }, [inputKey, data, dataKeys]); useEffect(() => { - if (inputValue !== "") setInputValue(""); - updateOptions(); + setInputValue(""); }, [inputKey]); useEffect(() => { - if (!metadataKeys || metadataKeys.length === 0) return; - const labels = metadataKeys.reduce( - getLabelsFromKeys, - [] as MetadataLabel[] - ); - setMetadataLabels(labels); - }, [metadataKeys]); + if (data.length > 0) { + setDataKeys(filters.getDataKeys(data[0])); + } + }, [data, filters]); + + useEffect(() => { + updateOptions(); + }, [updateOptions]); + + useEffect(() => { + if (!dataKeys || dataKeys.length === 0) return; + const labels = dataKeys.reduce(getLabelsFromKeys, [] as DataKeyLabel[]); + setDataKeyLabels(labels); + }, [dataKeys, getLabelsFromKeys]); return ( { - callbackSearch({ key: inputKey.key, value: inputValue }); + updateData((prevData) => + filters.applyFilter(prevData, { + key: inputKey.key, + value: inputValue, + }) + ); e.preventDefault(); }} - sx={{ display: "inline" }} + sx={{ + display: "flex", + flexDirection: "column", + borderRadius: "9px", + backgroundColor: theme.palette.primary.light, + height: "110px", + padding: "10px", + marginTop: "15px", + marginBottom: "15px", + }} > - - option.label} - isOptionEqualToValue={(option, value) => option.label === value.label} - onInputChange={(e: ChangeEvent, newInputKey: string) => { - // Match the text with the actual key we want - const metaLabel = metadataLabels.filter( - ({ label }) => label === newInputKey - ); + div > div": { height: "40px" } }} + getOptionLabel={(option: DataKeyLabel) => option.label} + isOptionEqualToValue={(option, value) => option.label === value.label} + onInputChange={(e: ChangeEvent, newInputKey: string) => { + // Match the text with the actual key we want + const metaLabel = dataKeyLabels.filter( + ({ label }) => label === newInputKey + ); - setInputKey(metaLabel?.[0]); - }} - options={metadataLabels} - renderInput={(params: AutocompleteRenderInputParams) => ( - - )} - PaperComponent={CustomPaper} - /> + setInputKey(metaLabel?.[0]); + }} + options={dataKeyLabels} + renderInput={(params: AutocompleteRenderInputParams) => ( + + )} + PaperComponent={CustomPaper} + /> +
div > div": { height: "40px" } }} inputValue={inputValue} freeSolo onInputChange={(e: ChangeEvent, newInputValue: string) => { @@ -146,14 +145,17 @@ function SearchBar({ }} options={inputOptions} renderInput={(params: AutocompleteRenderInputParams) => ( - + )} PaperComponent={CustomPaper} /> - - { if (!inputKey) { @@ -162,9 +164,9 @@ function SearchBar({ }} tooltipPlacement="bottom" /> - +
); } -export { getLabelsFromKeys, SearchBar, MetadataLabel }; +export { SearchBar, DataKeyLabel }; diff --git a/src/search/SearchFilterCard.tsx b/src/search/SearchFilterCard.tsx index 1dfc376..59d1154 100644 --- a/src/search/SearchFilterCard.tsx +++ b/src/search/SearchFilterCard.tsx @@ -1,54 +1,48 @@ import { ReactElement } from "react"; - import { theme, MuiCard, List, ListItem, ListItemText } from "@gliff-ai/style"; -import { Filter } from "@/interfaces"; - -const list = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - color: "brown", -}; -const listItem = { - padding: `${theme.spacing(0)}, ${theme.spacing(0)}`, - marginLeft: theme.spacing(1), - marginBottom: theme.spacing(1), - marginTop: theme.spacing(1), - width: "auto", - border: "1px solid", - borderColor: theme.palette.text.secondary, - borderRadius: "10px", -}; +import { Filters, FilterData } from "@/filter"; interface Props { - activeFilters: Filter[]; - callback: (filter: Filter) => void; + filters: Filters; + updateData: (func: (data: FilterData) => FilterData) => void; } -export function SearchFilterCard({ - activeFilters, - callback, -}: Props): ReactElement { +export function SearchFilterCard({ filters, updateData }: Props): ReactElement { return ( - {activeFilters.map((f) => ( + {filters.activeFilters.map((f) => ( callback(f)} - sx={{ ...listItem }} + onClick={() => { + filters.toggleFilter(f); + updateData(filters.filterData); + }} + sx={{ + padding: `${theme.spacing(0)}, ${theme.spacing(0)}`, + marginTop: "5px", + width: "auto", + border: "1px solid", + borderColor: theme.palette.text.secondary, + borderRadius: "9px", + maxWidth: "255px", + }} button dense > @@ -57,6 +51,10 @@ export function SearchFilterCard({ sx={{ paddingLeft: theme.spacing(2), color: theme.palette.text.secondary, + overflow: "hidden", + display: "-webkit-box", + "-webkit-box-orient": "vertical", + "-webkit-line-clamp": "2", }} /> diff --git a/src/search/index.ts b/src/search/index.ts index 5b846b0..08c32c6 100644 --- a/src/search/index.ts +++ b/src/search/index.ts @@ -1,3 +1,3 @@ -export { SearchBar, getLabelsFromKeys } from "./SearchBar"; -export { LabelsFilterAccordion } from "./LabelsFilterAccordion"; +export { SearchBar } from "./SearchBar"; export { SearchFilterCard } from "./SearchFilterCard"; +export { LabelsFilterAccordion } from "./LabelsFilterAccordion"; diff --git a/src/sort/SortPopover.tsx b/src/sort/SortPopover.tsx index 3c409d5..06f4eba 100644 --- a/src/sort/SortPopover.tsx +++ b/src/sort/SortPopover.tsx @@ -13,28 +13,32 @@ import { Popover, icons, } from "@gliff-ai/style"; -import { getLabelsFromKeys, MetadataLabel } from "@/search/SearchBar"; +import { Filters, FilterData } from "@/filter"; + +type DataKeyLabel = { key: string; label: string }; interface Props { - metadataKeys: string[]; - callbackSort: (key: string, sortOrder: string) => void; - isGrouped: boolean; - toggleIsGrouped: () => void; + data: FilterData; + filters: Filters; + updateData: (func: (data: FilterData) => FilterData) => void; + getLabelsFromKeys: (acc: DataKeyLabel[], key: string) => DataKeyLabel[]; + showGroupBy?: boolean; } export const SortPopover = ({ - metadataKeys, - callbackSort, - isGrouped, - toggleIsGrouped, + data, + filters, + updateData, + getLabelsFromKeys, + showGroupBy, }: Props): ReactElement => { - const [inputKey, setInputKey] = useState({ + const [inputKey, setInputKey] = useState({ key: "", label: "", }); - + const [dataKeyLabels, setDataKeyLabels] = useState([]); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); - const [metadataLabels, setMetadataLabels] = useState([]); + const [isGrouped, setIsGrouped] = useState(filters.isGrouped); // added to trigger re-rendering const handleChange = (func: (value: T) => void) => @@ -45,25 +49,26 @@ export const SortPopover = ({ }; const updateKey = (selectedLabel: string): void => { - const metaLabel = metadataLabels.filter( + const metaLabel = dataKeyLabels.filter( ({ label }) => label === selectedLabel ); setInputKey(metaLabel?.[0]); }; useEffect(() => { - if (!metadataKeys || metadataKeys.length === 0) return; - const labels = metadataKeys - .reduce(getLabelsFromKeys, [] as MetadataLabel[]) - .filter(({ key }) => key !== "imageLabels"); + if (data.length > 0) { + const newKeys = filters.getDataKeys(data[0]); - setMetadataLabels(labels); - }, [metadataKeys]); + if (newKeys && newKeys.length > 0) { + const labels = newKeys.reduce(getLabelsFromKeys, [] as DataKeyLabel[]); + setDataKeyLabels(labels); + } + } + }, [data, filters, getLabelsFromKeys]); return ( - {metadataLabels && - metadataLabels.map(({ key, label }) => ( + {dataKeyLabels && + dataKeyLabels.map(({ key, label }) => ( {label} @@ -106,7 +111,7 @@ export const SortPopover = ({ square style={{ padding: "10px", marginLeft: "15px" }} > - {inputKey.label ? ( + {inputKey.label && ( <> {/* Form for selecting a sort order */} @@ -128,27 +133,43 @@ export const SortPopover = ({ /> - - } - label="Group by value" - /> + {showGroupBy && ( + { + filters.toggleIsGrouped(); + setIsGrouped((value) => !value); + }} + name="group-by" + /> + } + label="Group by value" + /> + )} - ) : ( - <> )}