React Component for Student Filtering Interface

 avatar
Yash
javascript
2 months ago
27 kB
6
Indexable
import {
    Box,
    Button,
    Divider,
    Flex,
    Input,
    Menu,
    MenuButton,
    MenuItem,
    MenuList,
    Checkbox,
    Text,
    Select,
    FormLabel,
    FormControl,
} from '@chakra-ui/react';
import Rselect from 'react-select';
import makeAnimated from 'react-select/animated';
import { filter, find, forEach, map } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { STATUS } from "../../constants";
import { useScholarshipTestStore } from '../../store/ScholarshipTestStore';
import { useAuthStore } from '../../store/useAuth';
import { useStaffStore } from '../../store/StaffStore';
import { PERMISSIONS } from '../../app/data/permissions';
import { useGetAllLeadStage } from '../../services/lead-source.service';
import { CUSTOM_FILTER_TYPES, NUMBER_FILTER_TYPES, STRING_FILTER_TYPES } from '../AllLeads/filters';



export const StudentsFilterContainer = ({ filters, filterByFields, handleApply, handleClear, allLeadSource, allLeadStage, getAllFilterLeadsStatus, allCustomFIelds }) => {
    console.log({ filters })
    const [curFilters, setCurFilters] = useState([]);
    console.log({ curFilters })
    useEffect(() => {
        if (filters) {
            setCurFilters(filters)
        }
    }, [filters])

    const handleFilterChange = useCallback(
        (changeData) => {
            setCurFilters((prevFilters) => {
                let newFilters = [...prevFilters];
                forEach(changeData, (d) => {
                    const { key: filterKey, data } = d;
                    if (!data) {
                        newFilters = filter(newFilters, (f) => f.filterKey !== filterKey);
                    }
                    else if (filterKey === "customField") {
                        newFilters = filter(newFilters, (f) => f?.customFieldKey !== data.lastDeletedCustomField)
                        newFilters = filter(newFilters, (f) => f?.customFieldKey !== data.customFieldKey);
                        newFilters.push(data);
                    } else if (find(newFilters, (f) => f.filterKey === filterKey)) {
                        newFilters = map(newFilters, (f) =>
                            f.filterKey === filterKey ? data : f,
                        );
                    } else {
                        newFilters.push(data);
                    }
                });
                return newFilters;
            });
        },
        []
    );

    const onApply = () => {
        handleApply([...curFilters]);
    };

    const onClear = () => {
        handleClear([]);
        setCurFilters([]);
    };

    return (
        <Box>
            <Box
                mt={2}
                h="46vh"
                overflowY="scroll"
                css={{
                    '&::-webkit-scrollbar': {
                        width: '7px',
                        background: 'transparent',
                    },
                    '&::-webkit-scrollbar-thumb': {
                        background: '#CBD5E0',
                        borderRadius: '10px',
                    },
                }}
            >
                <Box px={4} mt={2}>
                    {map(filterByFields, (f) => (
                        <FilterTypesContainer
                            filterApplied={find(curFilters, (ftr) => ftr?.filterKey === f.key)}
                            handleFilterChange={handleFilterChange}
                            key={f.key}
                            filterData={f}
                            allLeadSource={allLeadSource}
                            allLeadStage={allLeadStage}
                            allCustomFIelds={allCustomFIelds}
                        />
                    ))}
                </Box>
            </Box>
            <Box
                py={4}
                px={4}
                boxShadow="0px -9px 25px -14px #41424224"
                borderTop="1px solid"
                borderColor="gray.100"
            >
                <Flex justify="end">
                    <Box mr={2}>
                        <Button onClick={onClear} colorScheme="gray" size="sm" variant="outline">
                            Clear
                        </Button>
                    </Box>
                    <Box>
                        <Button onClick={onApply} colorScheme="blue" size="sm" isLoading={getAllFilterLeadsStatus === STATUS.FETCHING}>
                            Apply filter
                        </Button>
                    </Box>
                </Flex>
            </Box>
        </Box>
    );
};


const FilterTypesContainer = ({ filterData, handleFilterChange, filterApplied, allLeadSource, allLeadStage, allCustomFIelds }) => {

    const [isChecked, setIsChecked] = useState()
    const { activeBranch } = useAuthStore();

    useEffect(() => {
        if (filterApplied && (filterApplied.value || filterApplied.startDate || filterApplied.endDate || filterApplied.customFieldKeys)) {
            setIsChecked(true)
        }
        else if (!filterApplied) {
            setIsChecked(false)
        }
    }, [filterApplied])

    const { allStaffDetails, getAllStaff } = useStaffStore()

    useEffect(() => {
        getAllStaff({ branch: activeBranch, populate: 'roleId' })
    }, [getAllStaff, activeBranch])

    const staffList = useMemo(() => {
        if (allStaffDetails) {
            const staffs = filter(allStaffDetails, staff => {
                const permissions = staff.roleId?.permissions;
                if (permissions) {
                    return permissions.includes(PERMISSIONS.LEAD_MANAGER.value) || permissions.includes(PERMISSIONS.LEAD_OWNER.value)
                }
            })
            return map(staffs, staff => staff.roleId.permissions.includes(PERMISSIONS.LEAD_MANAGER.value) ? { ...staff, isLeadManger: true } : staff)
        }
    }, [allStaffDetails])

    const updateFilterData = useCallback((data) => {
        const filterDataObj = {
            filterKey: filterData.key,
            ...data,
        };
        handleFilterChange([{ key: filterData.key, data: filterDataObj }]);
    }, [filterData.key, handleFilterChange]);


    const renderFilterInput = () => {

        const options = filterData.key === "leadStage" ? allLeadStage
            : filterData.key === "leadSource" ? allLeadSource
                : filterData.key === "staffId" ? staffList
                    : filterData.key === "customField" ? allCustomFIelds
                        : [];

        switch (filterData.type) {
            case 'number':
                return <NumberFilter filterApplied={filterApplied} updateFilterData={updateFilterData} />;
            case 'string':
                return <StringFilter filterApplied={filterApplied} updateFilterData={updateFilterData} />;
            case 'select':
                return <SelectFilter filterApplied={filterApplied} updateFilterData={updateFilterData} options={options} />;
            case 'date':
                return <DateFilter filterApplied={filterApplied} updateFilterData={updateFilterData} />;
            case 'selectClass':
                return <SelectClassFilter filterApplied={filterApplied} updateFilterData={updateFilterData} options={options} />;
            case 'selectLeadConvert':
                return <SelectLeadConvertFilter filterApplied={filterApplied} updateFilterData={updateFilterData} />
            case 'selectLeadOwner':
                return <SelectLeadOwner filterApplied={filterApplied} updateFilterData={updateFilterData} options={options} />
            case 'selectLeadStage':
                return <SelectLeadStage filterApplied={filterApplied} updateFilterData={updateFilterData} />
            case 'selectLeadCustomField':
                return <SelectLeadCustomFields filterApplied={filterApplied} updateFilterData={updateFilterData} options={options} />

            default:
                return null;
        }
    };

    return (
        <Box mb={4}>
            <Checkbox
                isChecked={isChecked}
                onChange={(e) => {
                    setIsChecked(e.target.checked);
                    if (!e.target.checked) {
                        updateFilterData(null);
                    }
                }}
            >
                {filterData.title}
            </Checkbox>
            {isChecked && renderFilterInput()}
        </Box>
    );
};

const NumberFilter = ({ filterApplied, updateFilterData }) => {

    const [type, setType] = useState(filterApplied?.type ?? 'equal');
    const [value, setValue] = useState(filterApplied?.value ?? '');

    const handleValueChange = (v) => {
        setValue(v);
    };

    useEffect(() => {
        updateFilterData({ type, value });
    }, [value, type, updateFilterData]);

    return (
        <Flex flexDir="column" maxW="245px">
            <Menu size="sm">
                <MenuButton as={Button} size="sm" mt={2} variant="outline" rightIcon={<ChevronDownIcon />}>
                    {NUMBER_FILTER_TYPES[type]}
                </MenuButton>
                <MenuList size="sm">
                    {map(NUMBER_FILTER_TYPES, (title, key) => (
                        <MenuItem onClick={() => setType(key)} key={key}>
                            {title}
                        </MenuItem>
                    ))}
                </MenuList>
            </Menu>
            <Input
                value={value}
                onChange={(e) => handleValueChange(e.target.value)}
                type="number"
                size="sm"
                placeholder="Enter value"
                mt={2}
                borderColor="gray.300"
                _focus={{ borderColor: 'blue.400', boxShadow: '0 0 0 1px rgba(66, 153, 225, 0.6)' }}
            />
        </Flex>
    );
};

const StringFilter = ({ filterApplied, updateFilterData }) => {

    const [type, setType] = useState(filterApplied?.type ?? 'is');
    const [value, setValue] = useState(filterApplied?.value ?? '');


    useEffect(() => {
        updateFilterData({ type, value });
    }, [value, type, updateFilterData]);

    return (
        <Flex align="center" mt={2}>
            <Menu size="sm" >
                <MenuButton as={Button} size="sm" variant="outline" rightIcon={<ChevronDownIcon />}>
                    {STRING_FILTER_TYPES[type]}
                </MenuButton>
                <MenuList size="sm">
                    {map(STRING_FILTER_TYPES, (title, key) => (
                        <MenuItem onClick={() => setType(key)} key={key}>
                            {title}
                        </MenuItem>
                    ))}
                </MenuList>
            </Menu>
            <Input
                ml={1}
                value={value}
                onChange={(e) => setValue(e.target.value)}
                type="text"
                size="sm"
                placeholder="Enter string"
                borderColor="gray.300"
                _focus={{ borderColor: 'blue.400', boxShadow: '0 0 0 1px rgba(66, 153, 225, 0.6)' }}
            />
        </Flex>
    );
};

const DateFilter = ({ filterApplied, updateFilterData }) => {

    const [startDate, setStartDate] = useState(filterApplied?.startDate ?? '');
    const [endDate, setEndDate] = useState(filterApplied?.endDate ?? '');

    useEffect(() => {
        updateFilterData({ startDate, endDate, type: 'date' });
    }, [startDate, endDate, updateFilterData]);

    return (
        <Flex align="center" mt={2}>
            <FormControl>
                <FormLabel>From</FormLabel>
                <Input
                    value={startDate}
                    onChange={(e) => setStartDate(e.target.value)}
                    type="date"
                    size="sm"
                    placeholder="Select date"
                    borderColor="gray.300"
                    _focus={{ borderColor: 'blue.400', boxShadow: '0 0 0 1px rgba(66, 153, 225, 0.6)' }}
                />
            </FormControl>
            <FormControl>
                <FormLabel>To</FormLabel>
                <Input
                    value={endDate}
                    onChange={(e) => setEndDate(e.target.value)}
                    type="date"
                    size="sm" ml={1}
                    placeholder="Select date"
                    borderColor="gray.300"
                    _focus={{ borderColor: 'blue.400', boxShadow: '0 0 0 1px rgba(66, 153, 225, 0.6)' }}
                />
            </FormControl>
        </Flex>
    );
};

const LeadStringFilter = ({ filterApplied, updateFilterData, customFieldKey, data, lastDeletedCustomField }) => {
    const [value, setValue] = useState('');
    const [type, setType] = useState('equal');

    useEffect(() => {
        updateFilterData({ customFieldKey, lastDeletedCustomField, value, type, fieldType: data.fieldType });
    }, [value, customFieldKey, lastDeletedCustomField, updateFilterData, type, data.fieldType]);

    useEffect(() => {
        if (filterApplied && filterApplied.customFieldKey === customFieldKey) {
            setValue(filterApplied.value || '');
            setType(filterApplied.type || 'equal');
        }
    }, [filterApplied, customFieldKey]);

    return (
        <Flex mt={2} direction="column">
            <FormLabel htmlFor={customFieldKey} mb={1} fontSize="sm">
                {data?.inputState?.key || 'Custom Field'}
            </FormLabel>

            {data?.fieldType === 'select' ? (
                <Select
                    id={customFieldKey}
                    placeholder="Select"
                    size="sm"
                    value={value}
                    onChange={(e) => setValue(e.target.value)}
                >
                    {map(data.options, (o) => (
                        <option key={o.name} value={o.name}>
                            {o.name}
                        </option>
                    ))}
                </Select>
            ) : (
                <Flex align="center" mt={2} w="87%">
                    {data?.fieldType === 'number' && (
                        <Menu size="sm">
                            <MenuButton as={Button} size="sm" variant="outline" rightIcon={<ChevronDownIcon />}>
                                {CUSTOM_FILTER_TYPES[type]}
                            </MenuButton>
                            <MenuList size="sm">
                                {map(CUSTOM_FILTER_TYPES, (title, key) => (
                                    <MenuItem onClick={() => setType(key)} key={key}>
                                        {title}
                                    </MenuItem>
                                ))}
                            </MenuList>
                        </Menu>
                    )}
                    <Input
                        id={customFieldKey}
                        ml={1}
                        value={value}
                        onChange={(e) => setValue(e.target.value)}
                        type={data?.fieldType === 'date' ? 'date' : 'text'}
                        size="sm"
                        placeholder="Enter Value"
                        borderColor="gray.300"
                        _focus={{ borderColor: 'blue.400', boxShadow: '0 0 0 1px rgba(66, 153, 225, 0.6)' }}
                    />
                </Flex>
            )}
        </Flex>
    );
};

const SelectFilter = ({ filterApplied, updateFilterData, options }) => {
    const [value, setValue] = useState(filterApplied?.value || []);

    const animatedComponents = makeAnimated();


    const handleValueChange = (selectedOptions) => {
        const newValue = selectedOptions ? selectedOptions.map(option => option.value) : [];
        setValue(newValue);
    };

    useEffect(() => {
        if (filterApplied?.value) {
            setValue(filterApplied.value);
        }
    }, [filterApplied]);

    useEffect(() => {
        updateFilterData({ value });
    }, [value]);

    const formattedOptions = options.map((opt) => ({
        label: opt.name,
        value: opt.name,
    }));

    const selectedOptions = formattedOptions.filter((option) =>
        value.includes(option.value)
    );
    return (
        <Flex maxW="245px">
            <Rselect
                options={formattedOptions}
                value={selectedOptions}
                onChange={handleValueChange}
                isMulti
                isClearable
                components={animatedComponents}
                closeMenuOnSelect={false}
                placeholder="Select"
                styles={{
                    control: (provided) => ({
                        ...provided,
                        width: '245px',
                        marginTop: "10px"
                    }),
                }}
            />
        </Flex>
    );
};

const SelectClassFilter = ({ filterApplied, updateFilterData }) => {
    const { getAllClasses, allClasses } = useScholarshipTestStore();

    const animatedComponents = makeAnimated();

    const [selectedValues, setSelectedValues] = useState([]);

    useEffect(() => {
        getAllClasses();
    }, [getAllClasses]);

    useEffect(() => {
        if (filterApplied && filterApplied.value) {
            const appliedValues = Array.isArray(filterApplied.value)
                ? filterApplied.value
                : [filterApplied.value];
            setSelectedValues(
                appliedValues.map((val) => {
                    const matchedClass = allClasses.find((opt) => opt.name === val);
                    return matchedClass ? { value: matchedClass.name, label: matchedClass.name } : null;
                }).filter(Boolean)
            );
        }
    }, [filterApplied, allClasses]);

    const handleValueChange = (selectedOptions) => {
        const names = selectedOptions.map((option) => option.value);
        setSelectedValues(selectedOptions);
        updateFilterData({ value: names });
    };

    const options = allClasses.map((opt) => ({
        value: opt.name,
        label: opt.name,
    }));

    return (
        <Flex maxW="245px">
            <Rselect
                options={options}
                isMulti
                value={selectedValues}
                components={animatedComponents}
                onChange={handleValueChange}
                placeholder="Select classes..."
                isClearable
                closeMenuOnSelect={false}
                styles={{
                    control: (provided) => ({
                        ...provided,
                        width: '245px',
                        marginTop: "10px"
                    }),
                }}
            />
        </Flex>
    );
};



const SelectLeadOwner = ({ filterApplied, updateFilterData, options }) => {
    const [value, setValue] = useState(filterApplied?.value || []);

    const animatedComponents = makeAnimated();

    const handleValueChange = (selectedOptions) => {
        const newValue = selectedOptions
            ? selectedOptions.map((option) => option.value)
            : [];
        setValue(newValue);
    };

    useEffect(() => {
        if (filterApplied?.value) {
            setValue(filterApplied.value);
        }
    }, [filterApplied]);

    useEffect(() => {
        updateFilterData({ value });
    }, [value, updateFilterData]);

    const formattedOptions = [
        ...options.map((opt) => ({
            label: opt.name,
            value: opt._id,
        })),
        {
            label: "Not Assigned",
            value: "not-assigned"
        }
    ];

    const selectedOptions = formattedOptions.filter((option) =>
        value.includes(option.value)
    );

    return (
        <Flex maxW="245px">
            <Rselect
                options={formattedOptions}
                value={selectedOptions}
                onChange={handleValueChange}
                isMulti
                isClearable
                components={animatedComponents}
                closeMenuOnSelect={false}
                placeholder="Select Lead Owner"
                styles={{
                    control: (provided) => ({
                        ...provided,
                        width: "245px",
                        marginTop: "10px",
                    }),
                }}
            />
        </Flex>
    );
};


const SelectLeadStage = ({ filterApplied, updateFilterData }) => {
    const { data: allLeadStages = [] } = useGetAllLeadStage({});
    const [selectedStage, setSelectedStage] = useState("");
    const [selectedSubStage, setSelectedSubStage] = useState("");

    const handleStageChange = (stageId) => {
        setSelectedStage(stageId);
        setSelectedSubStage("");
    };

    const handleSubStageChange = (subStageId) => {
        setSelectedSubStage(subStageId);
    };

    useEffect(() => {
        if (filterApplied && filterApplied.value) {
            setSelectedStage(filterApplied.value)
        }
        if (filterApplied && filterApplied.leadSubStage) {
            setSelectedSubStage(filterApplied.leadSubStage)
        }

    }, [filterApplied])

    useEffect(() => {
        const filterData = { value: selectedStage };
        if (selectedSubStage) {
            filterData.leadSubStage = selectedSubStage;
        }
        updateFilterData(filterData);
    }, [selectedStage, selectedSubStage, updateFilterData]);

    return (
        <Flex maxW="245px" flexDirection="column">

            <Select
                size="sm"
                mt={4}
                value={selectedStage}
                onChange={(e) => handleStageChange(e.target.value)}
                placeholder="select lead stage"
            >
                {map(allLeadStages, (opt) => (
                    <option key={opt._id} value={opt.name}>
                        {opt.name}
                    </option>
                ))}
            </Select>

            {selectedStage && allLeadStages.length > 0 && (
                <Select
                    size="sm"
                    mt={4}
                    value={selectedSubStage}
                    onChange={(e) => handleSubStageChange(e.target.value)}
                    placeholder="select sub stage"
                >
                    {map(
                        allLeadStages.find((stage) => stage.name === selectedStage)?.leadSubStage || [],
                        (opt) => (
                            <option key={opt} value={opt}>
                                {opt}
                            </option>
                        )
                    )}
                </Select>
            )}
        </Flex>
    );
};

const SelectLeadConvertFilter = ({ updateFilterData }) => {

    const { getAllClasses, allClasses } = useScholarshipTestStore();

    useEffect(() => {
        getAllClasses();
    }, [getAllClasses]);

    const [value, setValue] = useState('');
    const handleValueChange = (v) => {
        let newValue = v;

        if (v === 'true' || v === 'false') newValue = v === 'true' ? true : false;
        setValue(newValue);
    };
    useEffect(() => {
        updateFilterData({ value });
    }, [value, updateFilterData]);

    return (
        <Flex maxW="245px">
            <Select
                size="sm"
                mt={4}
                value={value}
                onChange={(e) => handleValueChange(e.target.value)}
            >
                <option>Select</option>
                <option value={true}>Converted</option>
                <option value={false}>Not Converted</option>

            </Select>
        </Flex>
    );

};


const SelectLeadCustomFields = ({ filterApplied, updateFilterData, options }) => {
    const [selectedCustomFields, setSelectedCustomFields] = useState([]);
    const [lastDeletedCustomField, setLastDeletedCustomField] = useState(null);

    const filterAllowedOnFieldsType = ['text', 'number', 'date', 'select'];
    const filteredData = filter(options, (o) => filterAllowedOnFieldsType.includes(o.fieldType));

    const selectedCustomFieldData = useMemo(() => {
        return selectedCustomFields.map((selectedCustomField) =>
            find(options, (o) => o.key === selectedCustomField)
        );
    }, [selectedCustomFields, options]);

    const handleValueChange = (selectedValues) => {
        const newSelectedValues = selectedValues.map(val => val.value);
        const removedValues = selectedCustomFields.filter(val => !newSelectedValues.includes(val));

        if (removedValues.length > 0) {
            const lastRemovedValue = removedValues[removedValues.length - 1];
            setLastDeletedCustomField(lastRemovedValue);
        }

        setSelectedCustomFields(newSelectedValues);
    };

    useEffect(() => {
        if (filterApplied && filterApplied.customFieldKeys) {
            setSelectedCustomFields(filterApplied.customFieldKeys);
        }
    }, [filterApplied]);

    return (
        <>
            <Rselect
                options={filteredData.map((opt) => ({ value: opt.key, label: opt.inputState.key }))}
                value={selectedCustomFields.map(key => ({ value: key, label: key }))}
                onChange={handleValueChange}
                isMulti
                closeMenuOnSelect={false}
            />

            {selectedCustomFields.map((customFieldKey) => {
                const customFieldData = selectedCustomFieldData.find((data) => data.key === customFieldKey);

                return (
                    <LeadStringFilter
                        key={customFieldKey}
                        filterApplied={filterApplied}
                        updateFilterData={updateFilterData}
                        customFieldKey={customFieldKey}
                        data={customFieldData}
                        lastDeletedCustomField={lastDeletedCustomField}
                    />
                );
            })}
        </>
    );
};

Editor is loading...
Leave a Comment