Skip to content

Commit 6b115c6

Browse files
authored
Forms Hub filter and search implemented (#175)
* complete Hi-Fi work flow for random client survey form with successful submission * filter and search implemented into cm forms hub
1 parent f7f35bb commit 6b115c6

File tree

4 files changed

+575
-31
lines changed

4 files changed

+575
-31
lines changed

client/src/components/clientlist/ClientList.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const ClientList = ({ admin }: ClientListProps) => {
8484
const [loading, setLoading] = useState(true);
8585

8686

87+
8788
const [showUnfinishedAlert, setShowUnfinishedAlert] = useState(false)
8889

8990
const [showSearch, setShowSearch] = useState(false);
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { useState } from "react";
2+
import {
3+
Button,
4+
HStack,
5+
Icon,
6+
Input,
7+
Popover,
8+
PopoverArrow,
9+
PopoverBody,
10+
PopoverContent,
11+
PopoverTrigger,
12+
Select,
13+
Text,
14+
VStack,
15+
useToast,
16+
} from "@chakra-ui/react";
17+
import { MdOutlineAdd, MdOutlineDelete, MdOutlineFilterAlt } from "react-icons/md";
18+
19+
interface FormFilter {
20+
id: number;
21+
field: string;
22+
operator: string;
23+
value: string;
24+
selector?: string;
25+
}
26+
27+
interface FormsListFilterProps {
28+
filterRows: FormFilter[];
29+
setFilterRows: React.Dispatch<React.SetStateAction<FormFilter[]>>;
30+
}
31+
32+
export const FormsListFilter = ({ filterRows, setFilterRows }: FormsListFilterProps) => {
33+
const toast = useToast();
34+
const [nextId, setNextId] = useState(2);
35+
36+
const columns = [
37+
{ name: "Date", value: "date", type: "date" },
38+
{ name: "Name", value: "name", type: "string" },
39+
{ name: "Form Title", value: "title", type: "string" },
40+
];
41+
42+
const addNewRow = () => {
43+
const last = filterRows[filterRows.length - 1];
44+
if (!last?.field || !last?.operator || !last?.value) {
45+
toast({
46+
title: "Incomplete filter",
47+
description: "Fill in all fields before adding a new one.",
48+
status: "warning",
49+
duration: 3000,
50+
isClosable: true,
51+
});
52+
return;
53+
}
54+
55+
setFilterRows([
56+
...filterRows,
57+
{ id: nextId, field: "", operator: "", value: "", selector: "AND" },
58+
]);
59+
setNextId(prev => prev + 1);
60+
};
61+
62+
const removeRow = (id: number) => {
63+
const updated = filterRows.filter(row => row.id !== id);
64+
if (updated.length === 0) {
65+
setFilterRows([{ id: 1, field: "", operator: "", value: "" }]);
66+
} else {
67+
setFilterRows(updated.map((row, idx) => ({ ...row, selector: idx === 0 ? undefined : row.selector })));
68+
}
69+
};
70+
71+
const updateValue = (id: number, key: keyof FormFilter, value: string) => {
72+
setFilterRows(prev =>
73+
prev.map(row => (row.id === id ? { ...row, [key]: value } : row))
74+
);
75+
};
76+
77+
return (
78+
<Popover placement="bottom-start">
79+
<PopoverTrigger>
80+
<Button background={"white"}>
81+
<HStack>
82+
<Icon as={MdOutlineFilterAlt} />
83+
<Text>Filter</Text>
84+
</HStack>
85+
</Button>
86+
</PopoverTrigger>
87+
<PopoverContent width="auto" p={2}>
88+
<PopoverArrow />
89+
<PopoverBody>
90+
<VStack spacing={4} align="stretch" width={"100%"}>
91+
<Text fontSize="14px" fontWeight="300">In this view, show records</Text>
92+
{filterRows.map((row, index) => {
93+
const type = columns.find(c => c.value === row.field)?.type || "string";
94+
const ops =
95+
type === "string"
96+
? [
97+
{ value: "contains", label: "contains" },
98+
{ value: "=", label: "equals" },
99+
]
100+
: [
101+
{ value: "=", label: "equals" },
102+
{ value: "!=", label: "not equals" },
103+
];
104+
105+
return (
106+
<HStack key={row.id} spacing={0} align="center">
107+
{index === 0 ? (
108+
<Text fontWeight="medium" fontSize="md" marginRight={"47px"}>Where</Text>
109+
) : (
110+
<Select
111+
value={row.selector || "AND"}
112+
onChange={e => updateValue(row.id, "selector", e.target.value)}
113+
width={"80px"}
114+
marginRight={"14px"}
115+
>
116+
<option value="AND">and</option>
117+
<option value="OR">or</option>
118+
</Select>
119+
)}
120+
121+
<Select
122+
placeholder="Select Field"
123+
width="150px"
124+
value={row.field}
125+
onChange={e => updateValue(row.id, "field", e.target.value)}
126+
borderRightRadius="0"
127+
>
128+
{columns.map(col => (
129+
<option key={col.value} value={col.value}>{col.name}</option>
130+
))}
131+
</Select>
132+
133+
<Select
134+
placeholder="Select Operator"
135+
width="150px"
136+
value={row.operator}
137+
onChange={e => updateValue(row.id, "operator", e.target.value)}
138+
borderRadius="0"
139+
>
140+
{ops.map(op => (
141+
<option key={op.value} value={op.value}>{op.label}</option>
142+
))}
143+
</Select>
144+
145+
{type === "date" ? (
146+
<Input
147+
type="date"
148+
width="150px"
149+
value={row.value}
150+
onChange={e => updateValue(row.id, "value", e.target.value)}
151+
borderRadius="0"
152+
/>
153+
) : (
154+
<Input
155+
placeholder="Enter value"
156+
width="150px"
157+
value={row.value}
158+
onChange={e => updateValue(row.id, "value", e.target.value)}
159+
borderRadius="0"
160+
/>
161+
)}
162+
163+
<Button onClick={() => removeRow(row.id)} variant="outline" borderLeftRadius="0">
164+
<Icon as={MdOutlineDelete} />
165+
</Button>
166+
</HStack>
167+
);
168+
})}
169+
<Button onClick={addNewRow} leftIcon={<MdOutlineAdd />} size="sm" variant="ghost">
170+
Add Filter
171+
</Button>
172+
</VStack>
173+
</PopoverBody>
174+
</PopoverContent>
175+
</Popover>
176+
);
177+
};

0 commit comments

Comments
 (0)