ENCOA-314 :

- Implemented Async Select
- Changed Stats Page User fetching to use Async Select and only fetch the User data when it needs
- Changed Record Filter to use Async Select
- Changed useTicketListener to only fetch needed data
- Added Sort/Projection to remove unnecessary data processing.
- Removed some unnecessary data processing.
This commit is contained in:
José Marques Lima
2025-01-20 02:52:39 +00:00
parent 205449e1ae
commit ae9a49681e
17 changed files with 1856 additions and 1043 deletions

View File

@@ -9,165 +9,216 @@ import useRecordStore from "@/stores/recordStore";
import { EntityWithRoles } from "@/interfaces/entity";
import { mapBy } from "@/utils";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
import useUsersSelect from "../../hooks/useUsersSelect";
import AsyncSelect from "../Low/AsyncSelect";
type TimeFilter = "months" | "weeks" | "days";
type Filter = TimeFilter | "assignments" | undefined;
interface Props {
user: User;
entities: EntityWithRoles[]
users: User[]
filterState: {
filter: Filter,
setFilter: React.Dispatch<React.SetStateAction<Filter>>
},
assignments?: boolean;
children?: ReactNode
user: User;
entities: EntityWithRoles[];
isAdmin?: boolean;
filterState: {
filter: Filter;
setFilter: React.Dispatch<React.SetStateAction<Filter>>;
};
assignments?: boolean;
children?: ReactNode;
}
const defaultSelectableCorporate = {
value: "",
label: "All",
value: "",
label: "All",
};
const RecordFilter: React.FC<Props> = ({
user,
entities,
users,
filterState,
assignments = true,
children
user,
entities,
filterState,
assignments = true,
isAdmin = false,
children,
}) => {
const { filter, setFilter } = filterState;
const { filter, setFilter } = filterState;
const [entity, setEntity] = useState<string>()
const [entity, setEntity] = useState<string>();
const [, setStatsUserId] = useRecordStore((state) => [
state.selectedUser,
state.setSelectedUser
]);
const [, setStatsUserId] = useRecordStore((state) => [
state.selectedUser,
state.setSelectedUser,
]);
const allowedViewEntities = useAllowedEntities(user, entities, 'view_student_record')
const entitiesToSearch = useMemo(() => {
if(entity) return entity
if (isAdmin) return undefined;
return mapBy(entities, "id");
}, [entities, entity, isAdmin]);
const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [users, entity])
const { users, isLoading, onScrollLoadMoreOptions, loadOptions } =
useUsersSelect({
size: 50,
orderBy: "name",
direction: "asc",
entities: entitiesToSearch,
});
useEffect(() => setStatsUserId(user.id), [setStatsUserId, user.id])
const allowedViewEntities = useAllowedEntities(
user,
entities,
"view_student_record"
);
const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
setFilter((prev) => (prev === value ? undefined : value));
};
useEffect(() => setStatsUserId(user.id), [setStatsUserId, user.id]);
return (
<div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center">
<div className="xl:w-3/4 flex gap-2">
{checkAccess(user, ["developer", "admin", "mastercorporate"]) && !children && (
<>
<div className="flex flex-col gap-2 w-full">
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
setFilter((prev) => (prev === value ? undefined : value));
};
<Select
options={allowedViewEntities.map((e) => ({ value: e.id, label: e.label }))}
onChange={(value) => setEntity(value?.value || undefined)}
isClearable
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}} />
</div>
<div className="flex flex-col gap-2 w-full">
<label className="font-normal text-base text-mti-gray-dim">User</label>
return (
<div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center">
<div className="xl:w-3/4 flex gap-2">
{checkAccess(user, ["developer", "admin", "mastercorporate"]) &&
!children && (
<>
<div className="flex flex-col gap-2 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Entity
</label>
<Select
options={entityUsers.map((x) => ({
value: x.id,
label: `${x.name} - ${x.email}`,
}))}
defaultValue={{ value: user.id, label: `${user.name} - ${user.email}` }}
onChange={(value) => setStatsUserId(value?.value!)}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
</>
)}
{(user.type === "corporate" || user.type === "teacher") && !children && (
<div className="flex flex-col gap-2">
<label className="font-normal text-base text-mti-gray-dim">User</label>
<Select
options={allowedViewEntities.map((e) => ({
value: e.id,
label: e.label,
}))}
onChange={(value) => setEntity(value?.value || undefined)}
isClearable
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
<div className="flex flex-col gap-2 w-full">
<label className="font-normal text-base text-mti-gray-dim">
User
</label>
<Select
options={users
.map((x) => ({
value: x.id,
label: `${x.name} - ${x.email}`,
}))}
defaultValue={{ value: user.id, label: `${user.name} - ${user.email}` }}
onChange={(value) => setStatsUserId(value?.value!)}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
)}
{children}
</div>
<div className="flex gap-4 w-full justify-center xl:justify-end">
{assignments && (
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "assignments" && "!bg-mti-purple-light !text-white",
)}
onClick={() => toggleFilter("assignments")}>
Assignments
</button>
)}
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "months" && "!bg-mti-purple-light !text-white",
)}
onClick={() => toggleFilter("months")}>
Last month
</button>
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "weeks" && "!bg-mti-purple-light !text-white",
)}
onClick={() => toggleFilter("weeks")}>
Last week
</button>
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "days" && "!bg-mti-purple-light !text-white",
)}
onClick={() => toggleFilter("days")}>
Last day
</button>
</div>
</div>
);
}
<AsyncSelect
isLoading={isLoading}
loadOptions={loadOptions}
onMenuScrollToBottom={onScrollLoadMoreOptions}
options={users}
defaultValue={{
value: user.id,
label: `${user.name} - ${user.email}`,
}}
onChange={(value) => setStatsUserId(value?.value!)}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
</>
)}
{(user.type === "corporate" || user.type === "teacher") &&
!children && (
<div className="flex flex-col gap-2">
<label className="font-normal text-base text-mti-gray-dim">
User
</label>
<AsyncSelect
isLoading={isLoading}
loadOptions={loadOptions}
onMenuScrollToBottom={onScrollLoadMoreOptions}
options={users}
defaultValue={{
value: user.id,
label: `${user.name} - ${user.email}`,
}}
onChange={(value) => setStatsUserId(value?.value!)}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
)}
{children}
</div>
<div className="flex gap-4 w-full justify-center xl:justify-end">
{assignments && (
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "assignments" && "!bg-mti-purple-light !text-white"
)}
onClick={() => toggleFilter("assignments")}
>
Assignments
</button>
)}
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "months" && "!bg-mti-purple-light !text-white"
)}
onClick={() => toggleFilter("months")}
>
Last month
</button>
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "weeks" && "!bg-mti-purple-light !text-white"
)}
onClick={() => toggleFilter("weeks")}
>
Last week
</button>
<button
className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out",
filter === "days" && "!bg-mti-purple-light !text-white"
)}
onClick={() => toggleFilter("days")}
>
Last day
</button>
</div>
</div>
);
};
export default RecordFilter;