From 7d2d85c374349c63ad03c4224dd51f79093738a8 Mon Sep 17 00:00:00 2001 From: Stefan Kairinos Date: Tue, 3 Sep 2024 09:29:14 +0300 Subject: [PATCH] fix: portal frontend 45 (#55) * read teacher * nested user object * fix model inheritances * fix types * fix: api auto complete * prefer cache value * fix: filter users by type * allow overriding read endpoint types * avoid reloads --- src/api/endpoints/authFactor.ts | 17 +++-- src/api/endpoints/klass.ts | 77 +++++++++++++------- src/api/endpoints/school.ts | 17 +++-- src/api/endpoints/user.ts | 31 ++++---- src/api/models.ts | 34 +++++---- src/components/TablePagination.tsx | 10 ++- src/components/form/ApiAutocompleteField.tsx | 48 +++++++----- 7 files changed, 142 insertions(+), 92 deletions(-) diff --git a/src/api/endpoints/authFactor.ts b/src/api/endpoints/authFactor.ts index 4712baa..34d63e4 100644 --- a/src/api/endpoints/authFactor.ts +++ b/src/api/endpoints/authFactor.ts @@ -3,8 +3,8 @@ import { type EndpointBuilder } from "@reduxjs/toolkit/query/react" import { buildUrl, tagData, - type ListArg, - type ListResult, + type ListArg as _ListArg, + type ListResult as _ListResult, } from "../../utils/api" import type { AuthFactor } from "../models" import { type TagTypes } from "../tagTypes" @@ -12,14 +12,15 @@ import urls from "../urls" export const AUTH_FACTOR_TAG: TagTypes = "AuthFactor" -export type ListAuthFactorsResult = ListResult -export type ListAuthFactorsArg = ListArg +export type ListAuthFactorsResult = _ListResult +export type ListAuthFactorsArg = _ListArg -export default function getReadAuthFactorEndpoints( - build: EndpointBuilder, -) { +export default function getReadAuthFactorEndpoints< + ListResult extends _ListResult = ListAuthFactorsResult, + ListArg extends _ListArg = ListAuthFactorsArg, +>(build: EndpointBuilder) { return { - listAuthFactors: build.query({ + listAuthFactors: build.query({ query: search => ({ url: buildUrl(urls.authFactor.list, { search }), method: "GET", diff --git a/src/api/endpoints/klass.ts b/src/api/endpoints/klass.ts index 0f37c94..bbe7271 100644 --- a/src/api/endpoints/klass.ts +++ b/src/api/endpoints/klass.ts @@ -3,49 +3,76 @@ import { type EndpointBuilder } from "@reduxjs/toolkit/query/react" import { buildUrl, tagData, - type ListArg, - type ListResult, - type RetrieveArg, - type RetrieveResult, + type ListArg as _ListArg, + type ListResult as _ListResult, + type RetrieveArg as _RetrieveArg, + type RetrieveResult as _RetrieveResult, } from "../../utils/api" -import type { Class, Teacher } from "../models" +import type { + Class, + Teacher, + SchoolTeacher, + SchoolTeacherUser, +} from "../models" import { type TagTypes } from "../tagTypes" import urls from "../urls" export const CLASS_TAG: TagTypes = "Class" -export type RetrieveClassResult = RetrieveResult< +export type RetrieveClassResult = _RetrieveResult< Class, - | "name" - | "read_classmates_data" - | "receive_requests_until" - | "school" - | "teacher" -> -export type RetrieveClassArg = RetrieveArg + "name" | "read_classmates_data" | "receive_requests_until" | "school" +> & { + teacher: SchoolTeacher & { + user: Pick< + SchoolTeacherUser, + | "id" + | "first_name" + | "last_name" + | "email" + | "is_active" + | "date_joined" + | "requesting_to_join_class" + > + } +} +export type RetrieveClassArg = _RetrieveArg -export type ListClassesResult = ListResult< +export type ListClassesResult = _ListResult< Class, - | "name" - | "read_classmates_data" - | "receive_requests_until" - | "school" - | "teacher" + "name" | "read_classmates_data" | "receive_requests_until" | "school", + { + teacher: SchoolTeacher & { + user: Pick< + SchoolTeacherUser, + | "id" + | "first_name" + | "last_name" + | "email" + | "is_active" + | "date_joined" + | "requesting_to_join_class" + > + } + } > -export type ListClassesArg = ListArg<{ teacher: Teacher["id"] }> +export type ListClassesArg = _ListArg<{ teacher: Teacher["id"] }> -export default function getReadClassEndpoints( - build: EndpointBuilder, -) { +export default function getReadClassEndpoints< + RetrieveResult extends _RetrieveResult = RetrieveClassResult, + RetrieveArg extends _RetrieveArg = RetrieveClassArg, + ListResult extends _ListResult = ListClassesResult, + ListArg extends _ListArg = ListClassesArg, +>(build: EndpointBuilder) { return { - retrieveClass: build.query({ + retrieveClass: build.query({ query: id => ({ url: buildUrl(urls.class.detail, { url: { id } }), method: "GET", }), providesTags: tagData(CLASS_TAG), }), - listClasses: build.query({ + listClasses: build.query({ query: search => ({ url: buildUrl(urls.class.list, { search }), method: "GET", diff --git a/src/api/endpoints/school.ts b/src/api/endpoints/school.ts index 75f0dd4..e84a854 100644 --- a/src/api/endpoints/school.ts +++ b/src/api/endpoints/school.ts @@ -3,8 +3,8 @@ import { type EndpointBuilder } from "@reduxjs/toolkit/query/react" import { buildUrl, tagData, - type RetrieveArg, - type RetrieveResult, + type RetrieveArg as _RetrieveArg, + type RetrieveResult as _RetrieveResult, } from "../../utils/api" import type { School } from "../models" import { type TagTypes } from "../tagTypes" @@ -12,17 +12,18 @@ import urls from "../urls" export const SCHOOL_TAG: TagTypes = "School" -export type RetrieveSchoolResult = RetrieveResult< +export type RetrieveSchoolResult = _RetrieveResult< School, "name" | "country" | "uk_county" > -export type RetrieveSchoolArg = RetrieveArg +export type RetrieveSchoolArg = _RetrieveArg -export default function getReadSchoolEndpoints( - build: EndpointBuilder, -) { +export default function getReadSchoolEndpoints< + RetrieveResult extends _RetrieveResult = RetrieveSchoolResult, + RetrieveArg extends _RetrieveArg = RetrieveSchoolArg, +>(build: EndpointBuilder) { return { - retrieveSchool: build.query({ + retrieveSchool: build.query({ query: id => ({ url: buildUrl(urls.school.detail, { url: { id } }), method: "GET", diff --git a/src/api/endpoints/user.ts b/src/api/endpoints/user.ts index 703b4cc..5b6485d 100644 --- a/src/api/endpoints/user.ts +++ b/src/api/endpoints/user.ts @@ -3,10 +3,10 @@ import { type EndpointBuilder } from "@reduxjs/toolkit/query/react" import { buildUrl, tagData, - type ListArg, - type ListResult, - type RetrieveArg, - type RetrieveResult, + type ListArg as _ListArg, + type ListResult as _ListResult, + type RetrieveArg as _RetrieveArg, + type RetrieveResult as _RetrieveResult, } from "../../utils/api" import type { Class, User } from "../models" import { type TagTypes } from "../tagTypes" @@ -14,7 +14,7 @@ import urls from "../urls" export const USER_TAG: TagTypes = "User" -export type RetrieveUserResult = RetrieveResult< +export type RetrieveUserResult = _RetrieveResult< User, | "first_name" | "last_name" @@ -25,9 +25,9 @@ export type RetrieveUserResult = RetrieveResult< | "student" | "teacher" > -export type RetrieveUserArg = RetrieveArg +export type RetrieveUserArg = _RetrieveArg -export type ListUsersResult = ListResult< +export type ListUsersResult = _ListResult< User, | "first_name" | "last_name" @@ -38,25 +38,28 @@ export type ListUsersResult = ListResult< | "student" | "teacher" > -export type ListUsersArg = ListArg<{ +export type ListUsersArg = _ListArg<{ students_in_class: Class["id"] - only_teachers: boolean _id: User["id"] | User["id"][] name: string + type: "teacher" | "student" | "independent" | "indy" }> -export default function getReadUserEndpoints( - build: EndpointBuilder, -) { +export default function getReadUserEndpoints< + RetrieveResult extends _RetrieveResult = RetrieveUserResult, + RetrieveArg extends _RetrieveArg = RetrieveUserArg, + ListResult extends _ListResult = ListUsersResult, + ListArg extends _ListArg = ListUsersArg, +>(build: EndpointBuilder) { return { - retrieveUser: build.query({ + retrieveUser: build.query({ query: id => ({ url: buildUrl(urls.user.detail, { url: { id } }), method: "GET", }), providesTags: tagData(USER_TAG), }), - listUsers: build.query({ + listUsers: build.query({ query: search => ({ url: buildUrl(urls.user.list, { search }), method: "GET", diff --git a/src/api/models.ts b/src/api/models.ts index e103588..7737484 100644 --- a/src/api/models.ts +++ b/src/api/models.ts @@ -23,36 +23,40 @@ export type User = Model< > export type TeacherUser = Fields & { + email: string + last_name: string teacher: Teacher student: undefined } -export type SchoolTeacherUser = Fields & { +export type SchoolTeacherUser = TeacherUser & { teacher: SchoolTeacher - student: undefined } -export type AdminSchoolTeacherUser = Fields & { - teacher: AdminSchoolTeacher - student: undefined -} +export type AdminSchoolTeacherUser = + SchoolTeacherUser & { + teacher: AdminSchoolTeacher + } -export type NonAdminSchoolTeacherUser = Fields & { - teacher: NonAdminSchoolTeacher - student: undefined -} +export type NonAdminSchoolTeacherUser = + SchoolTeacherUser & { + teacher: NonAdminSchoolTeacher + } -export type NonSchoolTeacherUser = Fields & { +export type NonSchoolTeacherUser = TeacherUser & { teacher: NonSchoolTeacher - student: undefined } export type StudentUser = Fields & { + email: undefined + last_name: undefined teacher: undefined student: Student } export type IndependentUser = Fields & { + email: string + last_name: string teacher: undefined student: undefined } @@ -74,13 +78,11 @@ export type SchoolTeacher = Fields & { school: number } -export type AdminSchoolTeacher = Fields & { - school: number +export type AdminSchoolTeacher = SchoolTeacher & { is_admin: true } -export type NonAdminSchoolTeacher = Fields & { - school: number +export type NonAdminSchoolTeacher = SchoolTeacher & { is_admin: false } diff --git a/src/components/TablePagination.tsx b/src/components/TablePagination.tsx index 51a5d59..c0c040d 100644 --- a/src/components/TablePagination.tsx +++ b/src/components/TablePagination.tsx @@ -73,9 +73,13 @@ const TablePagination = < limit: initialLimit, }) - useEffect(() => { - trigger({ limit, offset, ...filters } as QueryArg, preferCacheValue) - }, [trigger, limit, offset, filters, preferCacheValue]) + useEffect( + () => { + trigger({ limit, offset, ...filters } as QueryArg, preferCacheValue) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [trigger, limit, offset, ...Object.values(filters || {}), preferCacheValue], + ) const { count, max_limit } = result.data || {} diff --git a/src/components/form/ApiAutocompleteField.tsx b/src/components/form/ApiAutocompleteField.tsx index 0e3ebcd..e5112a7 100644 --- a/src/components/form/ApiAutocompleteField.tsx +++ b/src/components/form/ApiAutocompleteField.tsx @@ -85,27 +85,39 @@ const ApiAutocompleteField = < }>({ options: {}, hasMore: true }) // Call api - useEffect(() => { - const arg = { limit, offset, ...filterOptions } as QueryArg - // @ts-expect-error - if (search) arg[searchKey] = search + useEffect( + () => { + const arg = { limit, offset, ...filterOptions } as QueryArg + // @ts-expect-error + if (search) arg[searchKey] = search - trigger(arg) - .unwrap() - .then(({ data, offset, limit, count }) => { - setState(({ options: previousOptions }) => { - const options = { ...previousOptions } - data.forEach(result => { - options[getOptionKey(result)] = result + trigger(arg, true) + .unwrap() + .then(({ data, offset, limit, count }) => { + setState(({ options: previousOptions }) => { + const options = { ...previousOptions } + data.forEach(result => { + options[getOptionKey(result)] = result + }) + return { options, hasMore: offset + limit < count } }) - return { options, hasMore: offset + limit < count } }) - }) - .catch(error => { - if (error) console.error(error) - // TODO: gracefully handle error - }) - }, [trigger, limit, offset, filterOptions, getOptionKey, searchKey, search]) + .catch(error => { + if (error) console.error(error) + // TODO: gracefully handle error + }) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + trigger, + limit, + offset, + searchKey, + search, + // eslint-disable-next-line react-hooks/exhaustive-deps + ...Object.values(filterOptions || {}), + ], + ) // Get options keys let optionKeys: ModelId[] = Object.keys(options)