mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
support filtering by document ID (#668)
This commit is contained in:
@@ -29,16 +29,17 @@ export default function ColumnSelect({
|
||||
...props
|
||||
}: IColumnSelectProps & Omit<MultiSelectProps<string>, "options">) {
|
||||
const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope);
|
||||
const options = (
|
||||
filterColumns
|
||||
const options =
|
||||
props.options ||
|
||||
(filterColumns
|
||||
? tableColumnsOrdered.filter(filterColumns)
|
||||
: tableColumnsOrdered
|
||||
).map(({ key, name, type, index }) => ({
|
||||
value: key,
|
||||
label: name,
|
||||
type,
|
||||
index,
|
||||
}));
|
||||
).map(({ key, name, type, index }) => ({
|
||||
value: key,
|
||||
label: name,
|
||||
type,
|
||||
index,
|
||||
}));
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import ColumnSelect from "@src/components/Table/ColumnSelect";
|
||||
import FieldSkeleton from "@src/components/SideDrawer/FieldSkeleton";
|
||||
import IdFilterInput from "./IdFilterInput";
|
||||
|
||||
import type { useFilterInputs } from "./useFilterInputs";
|
||||
import { getFieldType, getFieldProp } from "@src/components/fields";
|
||||
@@ -97,15 +98,21 @@ export default function FilterInputs({
|
||||
|
||||
<Suspense fallback={<FieldSkeleton />}>
|
||||
{columnType &&
|
||||
createElement(getFieldProp("SideDrawerField", columnType), {
|
||||
column: selectedColumn,
|
||||
_rowy_ref: {},
|
||||
value: query.value,
|
||||
onChange: (value: any) => {
|
||||
setQuery((query) => ({ ...query, value }));
|
||||
},
|
||||
disabled,
|
||||
})}
|
||||
createElement(
|
||||
query.key === "_rowy_ref.id"
|
||||
? IdFilterInput
|
||||
: getFieldProp("filter.customInput" as any, columnType) ||
|
||||
getFieldProp("SideDrawerField", columnType),
|
||||
{
|
||||
column: selectedColumn,
|
||||
_rowy_ref: {},
|
||||
value: query.value,
|
||||
onChange: (value: any) => {
|
||||
setQuery((query) => ({ ...query, value }));
|
||||
},
|
||||
disabled,
|
||||
}
|
||||
)}
|
||||
</Suspense>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -65,6 +65,7 @@ export default function FiltersPopover({
|
||||
</ButtonWithStatus>
|
||||
|
||||
{appliedFilters.map((filter) => {
|
||||
const fieldName = filter.key === "_rowy_ref.id" ? "ID" : filter.key;
|
||||
const operator = (availableFilters?.operators ?? []).find(
|
||||
(f) => f.value === filter.operator
|
||||
);
|
||||
@@ -78,8 +79,8 @@ export default function FiltersPopover({
|
||||
<Chip
|
||||
key={filter.key}
|
||||
label={
|
||||
<Typography variant="inherit">
|
||||
{filter.key}{" "}
|
||||
<Typography variant="inherit" component="span">
|
||||
{fieldName}{" "}
|
||||
<Typography
|
||||
variant="inherit"
|
||||
display="inline"
|
||||
|
||||
26
src/components/TableToolbar/Filters/IdFilterInput.tsx
Normal file
26
src/components/TableToolbar/Filters/IdFilterInput.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { TextField } from "@mui/material";
|
||||
|
||||
export interface IIdFilterInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function IdFilterInput({
|
||||
value,
|
||||
onChange,
|
||||
}: IIdFilterInputProps) {
|
||||
return (
|
||||
<TextField
|
||||
aria-label="Filter by this ID"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={value}
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiFilledInput-input": {
|
||||
typography: "caption",
|
||||
fontFamily: "mono",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import { find } from "lodash-es";
|
||||
|
||||
import { getFieldType, getFieldProp } from "@src/components/fields";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import type { ColumnConfig, TableFilter } from "@src/types/table";
|
||||
import type { IFieldConfig } from "@src/components/fields/types";
|
||||
|
||||
@@ -22,6 +23,15 @@ export const useFilterInputs = (
|
||||
index: c.index,
|
||||
}));
|
||||
|
||||
// Always allow IDs to be filterable
|
||||
filterColumns.push({
|
||||
value: "_rowy_ref.id",
|
||||
label: "Document ID",
|
||||
type: FieldType.id,
|
||||
key: "_rowy_ref.id",
|
||||
index: filterColumns.length,
|
||||
});
|
||||
|
||||
// State for filter inputs
|
||||
const [query, setQuery] = useState<TableFilter | typeof INITIAL_QUERY>(
|
||||
defaultQuery || INITIAL_QUERY
|
||||
@@ -30,6 +40,11 @@ export const useFilterInputs = (
|
||||
|
||||
// When the user sets a new column, automatically set the operator and value
|
||||
const handleChangeColumn = (value: string | null) => {
|
||||
if (value === "_rowy_ref.id") {
|
||||
setQuery({ key: "_rowy_ref.id", operator: "id-equal", value: "" });
|
||||
return;
|
||||
}
|
||||
|
||||
const column = find(filterColumns, ["key", value]);
|
||||
|
||||
if (column) {
|
||||
@@ -47,9 +62,12 @@ export const useFilterInputs = (
|
||||
// Get the column config
|
||||
const selectedColumn = find(filterColumns, ["key", query?.key]);
|
||||
// Get available filters from selected column type
|
||||
const availableFilters: IFieldConfig["filter"] = selectedColumn
|
||||
? getFieldProp("filter", getFieldType(selectedColumn))
|
||||
: undefined;
|
||||
const availableFilters: IFieldConfig["filter"] =
|
||||
query?.key === "_rowy_ref.id"
|
||||
? { operators: [{ value: "id-equal", label: "is" }] }
|
||||
: selectedColumn
|
||||
? getFieldProp("filter", getFieldType(selectedColumn))
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
filterColumns,
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface IFieldConfig {
|
||||
settingsValidator?: (config: Record<string, any>) => Record<string, string>;
|
||||
filter?: {
|
||||
operators: IFilterOperator[];
|
||||
customInput?: React.ComponentType<IFiltersProps>;
|
||||
customInput?: React.ComponentType<{ onChange: (value: any) => void }>;
|
||||
defaultValue?: any;
|
||||
valueFormatter?: (value: any) => string;
|
||||
};
|
||||
@@ -101,12 +101,6 @@ export interface ISettingsProps {
|
||||
errors: Record<string, any>;
|
||||
}
|
||||
|
||||
// TODO: WRITE TYPES
|
||||
export interface IFiltersProps {
|
||||
onChange: (key: string) => (value: any) => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IFilterOperator {
|
||||
value: TableFilter["operator"];
|
||||
label: string;
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
Query,
|
||||
QueryConstraint,
|
||||
WhereFilterOp,
|
||||
documentId,
|
||||
} from "firebase/firestore";
|
||||
import { useErrorHandler } from "react-error-boundary";
|
||||
|
||||
@@ -358,6 +359,9 @@ export const tableFiltersToFirestoreFilters = (filters: TableFilter[]) => {
|
||||
firestoreFilters.push(where(filter.key, ">=", startDate));
|
||||
firestoreFilters.push(where(filter.key, "<=", endDate));
|
||||
continue;
|
||||
} else if (filter.operator === "id-equal") {
|
||||
firestoreFilters.push(where(documentId(), "==", filter.value));
|
||||
continue;
|
||||
}
|
||||
|
||||
firestoreFilters.push(
|
||||
|
||||
3
src/types/table.d.ts
vendored
3
src/types/table.d.ts
vendored
@@ -152,7 +152,8 @@ export type TableFilter = {
|
||||
| "date-after"
|
||||
| "date-before-equal"
|
||||
| "date-after-equal"
|
||||
| "time-minute-equal";
|
||||
| "time-minute-equal"
|
||||
| "id-equal";
|
||||
value: any;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user