Skip to content

Commit

Permalink
fix: portal frontend 45 (#55)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
SKairinos authored Sep 3, 2024
1 parent 6ca074a commit 7d2d85c
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 92 deletions.
17 changes: 9 additions & 8 deletions src/api/endpoints/authFactor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ 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"
import urls from "../urls"

export const AUTH_FACTOR_TAG: TagTypes = "AuthFactor"

export type ListAuthFactorsResult = ListResult<AuthFactor, "type">
export type ListAuthFactorsArg = ListArg
export type ListAuthFactorsResult = _ListResult<AuthFactor, "type">
export type ListAuthFactorsArg = _ListArg

export default function getReadAuthFactorEndpoints(
build: EndpointBuilder<any, any, any>,
) {
export default function getReadAuthFactorEndpoints<
ListResult extends _ListResult<AuthFactor> = ListAuthFactorsResult,
ListArg extends _ListArg<AuthFactor> = ListAuthFactorsArg,
>(build: EndpointBuilder<any, any, any>) {
return {
listAuthFactors: build.query<ListAuthFactorsResult, ListAuthFactorsArg>({
listAuthFactors: build.query<ListResult, ListArg>({
query: search => ({
url: buildUrl(urls.authFactor.list, { search }),
method: "GET",
Expand Down
77 changes: 52 additions & 25 deletions src/api/endpoints/klass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class>
"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<Class>

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<any, any, any>,
) {
export default function getReadClassEndpoints<
RetrieveResult extends _RetrieveResult<Class> = RetrieveClassResult,
RetrieveArg extends _RetrieveArg<Class> = RetrieveClassArg,
ListResult extends _ListResult<Class> = ListClassesResult,
ListArg extends _ListArg<Class> = ListClassesArg,
>(build: EndpointBuilder<any, any, any>) {
return {
retrieveClass: build.query<RetrieveClassResult, RetrieveClassArg>({
retrieveClass: build.query<RetrieveResult, RetrieveArg>({
query: id => ({
url: buildUrl(urls.class.detail, { url: { id } }),
method: "GET",
}),
providesTags: tagData(CLASS_TAG),
}),
listClasses: build.query<ListClassesResult, ListClassesArg>({
listClasses: build.query<ListResult, ListArg>({
query: search => ({
url: buildUrl(urls.class.list, { search }),
method: "GET",
Expand Down
17 changes: 9 additions & 8 deletions src/api/endpoints/school.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ 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"
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<School>
export type RetrieveSchoolArg = _RetrieveArg<School>

export default function getReadSchoolEndpoints(
build: EndpointBuilder<any, any, any>,
) {
export default function getReadSchoolEndpoints<
RetrieveResult extends _RetrieveResult<School> = RetrieveSchoolResult,
RetrieveArg extends _RetrieveArg<School> = RetrieveSchoolArg,
>(build: EndpointBuilder<any, any, any>) {
return {
retrieveSchool: build.query<RetrieveSchoolResult, RetrieveSchoolArg>({
retrieveSchool: build.query<RetrieveResult, RetrieveArg>({
query: id => ({
url: buildUrl(urls.school.detail, { url: { id } }),
method: "GET",
Expand Down
31 changes: 17 additions & 14 deletions src/api/endpoints/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ 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"
import urls from "../urls"

export const USER_TAG: TagTypes = "User"

export type RetrieveUserResult = RetrieveResult<
export type RetrieveUserResult = _RetrieveResult<
User,
| "first_name"
| "last_name"
Expand All @@ -25,9 +25,9 @@ export type RetrieveUserResult = RetrieveResult<
| "student"
| "teacher"
>
export type RetrieveUserArg = RetrieveArg<User>
export type RetrieveUserArg = _RetrieveArg<User>

export type ListUsersResult = ListResult<
export type ListUsersResult = _ListResult<
User,
| "first_name"
| "last_name"
Expand All @@ -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<any, any, any>,
) {
export default function getReadUserEndpoints<
RetrieveResult extends _RetrieveResult<User> = RetrieveUserResult,
RetrieveArg extends _RetrieveArg<User> = RetrieveUserArg,
ListResult extends _ListResult<User> = ListUsersResult,
ListArg extends _ListArg<User> = ListUsersArg,
>(build: EndpointBuilder<any, any, any>) {
return {
retrieveUser: build.query<RetrieveUserResult, RetrieveUserArg>({
retrieveUser: build.query<RetrieveResult, RetrieveArg>({
query: id => ({
url: buildUrl(urls.user.detail, { url: { id } }),
method: "GET",
}),
providesTags: tagData(USER_TAG),
}),
listUsers: build.query<ListUsersResult, ListUsersArg>({
listUsers: build.query<ListResult, ListArg>({
query: search => ({
url: buildUrl(urls.user.list, { search }),
method: "GET",
Expand Down
34 changes: 18 additions & 16 deletions src/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,40 @@ export type User = Model<
>

export type TeacherUser<Fields = User> = Fields & {
email: string
last_name: string
teacher: Teacher
student: undefined
}

export type SchoolTeacherUser<Fields = User> = Fields & {
export type SchoolTeacherUser<Fields = User> = TeacherUser<Fields> & {
teacher: SchoolTeacher
student: undefined
}

export type AdminSchoolTeacherUser<Fields = User> = Fields & {
teacher: AdminSchoolTeacher
student: undefined
}
export type AdminSchoolTeacherUser<Fields = User> =
SchoolTeacherUser<Fields> & {
teacher: AdminSchoolTeacher
}

export type NonAdminSchoolTeacherUser<Fields = User> = Fields & {
teacher: NonAdminSchoolTeacher
student: undefined
}
export type NonAdminSchoolTeacherUser<Fields = User> =
SchoolTeacherUser<Fields> & {
teacher: NonAdminSchoolTeacher
}

export type NonSchoolTeacherUser<Fields = User> = Fields & {
export type NonSchoolTeacherUser<Fields = User> = TeacherUser<Fields> & {
teacher: NonSchoolTeacher
student: undefined
}

export type StudentUser<Fields = User> = Fields & {
email: undefined
last_name: undefined
teacher: undefined
student: Student
}

export type IndependentUser<Fields = User> = Fields & {
email: string
last_name: string
teacher: undefined
student: undefined
}
Expand All @@ -74,13 +78,11 @@ export type SchoolTeacher<Fields = Teacher> = Fields & {
school: number
}

export type AdminSchoolTeacher<Fields = Teacher> = Fields & {
school: number
export type AdminSchoolTeacher<Fields = Teacher> = SchoolTeacher<Fields> & {
is_admin: true
}

export type NonAdminSchoolTeacher<Fields = Teacher> = Fields & {
school: number
export type NonAdminSchoolTeacher<Fields = Teacher> = SchoolTeacher<Fields> & {
is_admin: false
}

Expand Down
10 changes: 7 additions & 3 deletions src/components/TablePagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {}

Expand Down
48 changes: 30 additions & 18 deletions src/components/form/ApiAutocompleteField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 7d2d85c

Please sign in to comment.