Skip to content

Commit

Permalink
Upgrade react admin to v5 (#507)
Browse files Browse the repository at this point in the history
* upgrade React Admin to v5

* fix post-upgrade type errors, prepare client-side search and filter, create Accept constant

* post-upgrade: account for new rowClick defaults

* refactor My(Show/Edit)Actions

* fix crash on PostCreate
  • Loading branch information
rtrembecky authored Nov 26, 2024
1 parent e5f8001 commit b0c972d
Show file tree
Hide file tree
Showing 31 changed files with 200 additions and 624 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"mathjax-react": "^2.0.1",
"next": "15.0.3",
"react": "19.0.0-rc-66855b96-20241106",
"react-admin": "^4.16.20",
"react-admin": "^5.4.0",
"react-cookie": "^4.1.1",
"react-dom": "19.0.0-rc-66855b96-20241106",
"react-dropzone": "^14.3.5",
Expand Down
7 changes: 4 additions & 3 deletions src/components/Admin/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Home, Logout} from '@mui/icons-material/'
import {Button, Stack, Typography} from '@mui/material'
import {useRouter} from 'next/router'
import {AppBar, Layout, LayoutProps, useLogout} from 'react-admin'
import {FC, PropsWithChildren} from 'react'
import {AppBar, Layout, useLogout} from 'react-admin'

const AppMenuBar = () => {
const router = useRouter()
Expand Down Expand Up @@ -32,10 +33,10 @@ const AppMenuBar = () => {
)
}

export const AdminLayout = (props: LayoutProps) => {
export const AdminLayout: FC<PropsWithChildren> = ({children}) => {
return (
<>
<Layout {...props} appBar={AppMenuBar} />
<Layout appBar={AppMenuBar}>{children}</Layout>
<Stack
style={{
position: 'fixed',
Expand Down
15 changes: 8 additions & 7 deletions src/components/Admin/custom/MyEditActions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {FC} from 'react'
import {ListButton, ShowButton, TopToolbar, useRecordContext, useResourceContext} from 'react-admin'
import {ListButton, ShowButton, TopToolbar, useCreatePath, useRecordContext, useResourceContext} from 'react-admin'
// eslint-disable-next-line node/no-extraneous-import
import {useLocation} from 'react-router-dom'

Expand All @@ -8,19 +8,20 @@ export const MyEditActions: FC = () => {

const resource = useResourceContext()
const record = useRecordContext()
const createPath = useCreatePath()

// needed, undefined on first load
if (!record) return null

const currentPathWithoutTab = `/${resource}/${record.id}` // '/cms/post/123'
let to = `${currentPathWithoutTab}/show` // '/cms/post/123/show'
const currentPathWithoutTab = createPath({type: 'edit', resource, id: record.id}) // '/cms/post/123'
const tabPart = pathname.slice(currentPathWithoutTab.length) // bud '' alebo '/1'
if (tabPart) to = `${to}${tabPart}` // '/cms/post/123/show' alebo '/cms/post/123/show/1'
const path = createPath({type: 'show', resource, id: record.id}) // '/cms/post/123/show'
const to = `${path}${tabPart}` // '/cms/post/123/show' alebo '/cms/post/123/show/1'

return (
<TopToolbar>
{/* the `to` prop was omitted from ShowButton in recent RA version, but it's still working
and RA doesn't provide better way to do this
TODO: try again after RA upgrade */}
{/* the `to` prop is omitted from ShowButtonProps, but it's still being spread to underlying button.
we want to link to the specific show tab, not just resource show */}
{/* @ts-ignore */}

Check warning on line 25 in src/components/Admin/custom/MyEditActions.tsx

View workflow job for this annotation

GitHub Actions / branch-test

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
<ShowButton to={to} />
<ListButton label="Back to list" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Admin/custom/MyFileField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const MyFileField: FC = () => {

return (
<RecordContextProvider value={myRecord}>
<FileField source="src" title={myRecord.title} />
<FileField source="src" title={myRecord?.title} />
</RecordContextProvider>
)
}
10 changes: 6 additions & 4 deletions src/components/Admin/custom/MyShowActions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {FC} from 'react'
import {EditButton, ListButton, TopToolbar, useRecordContext, useResourceContext} from 'react-admin'
import {EditButton, ListButton, TopToolbar, useCreatePath, useRecordContext, useResourceContext} from 'react-admin'
// eslint-disable-next-line node/no-extraneous-import
import {useLocation} from 'react-router-dom'

Expand All @@ -8,13 +8,15 @@ export const MyShowActions: FC = () => {

const resource = useResourceContext()
const record = useRecordContext()
const createPath = useCreatePath()

// needed, undefined on first load
if (!record) return null

const currentPathWithoutTab = `/${resource}/${record.id}/show` // '/cms/post/123/show'
let to = `/${resource}/${record.id}` // '/cms/post/123'
const currentPathWithoutTab = createPath({type: 'show', resource, id: record.id}) // '/cms/post/123/show'
const tabPart = pathname.slice(currentPathWithoutTab.length) // bud '' alebo '/1'
if (tabPart) to = `${to}${tabPart}` // '/cms/post/123' alebo '/cms/post/123/1'
const path = createPath({type: 'edit', resource, id: record.id}) // '/cms/post/123'
const to = `${path}${tabPart}` // '/cms/post/123' alebo '/cms/post/123/1'

return (
<TopToolbar>
Expand Down
54 changes: 34 additions & 20 deletions src/components/Admin/dataProvider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import axios from 'axios'
import {stringify} from 'querystring'
import {DataProvider, RaRecord} from 'react-admin'

// potencialne TODO: ak BE bude mat pagination, filter alebo sort, upravime a pouzijeme tento kod.
// zatial je pagination aj sort rieseny client-side a len pre getList, filter/search nemame.

// TODO: BE chysta search, filter, pagination a sort. ked to bude ready,
// postupne odkomentujeme tento kod a zmazeme client-side handling nizsie
// import {FilterPayload, PaginationPayload, SortPayload} from 'react-admin'

// const getPaginationQuery = ({page, perPage}: PaginationPayload) => ({
// page,
// page_size: perPage,
// offset: page,
// limit: perPage,
// })
// const getFilterQuery = ({q, ...otherSearchParams}: FilterPayload) => ({
// ...otherSearchParams,
Expand Down Expand Up @@ -40,30 +38,46 @@ export const dataProvider: DataProvider = {
// ...getOrderingQuery(params.sort),
}
const stringifiedQuery = stringify(query)
const {data} = await axios.get(`${apiUrl}/${resource}${stringifiedQuery ? `/?${stringifiedQuery}` : ''}`)
const {data} = await axios.get<any[]>(`${apiUrl}/${resource}${stringifiedQuery ? `/?${stringifiedQuery}` : ''}`)

Check warning on line 41 in src/components/Admin/dataProvider.ts

View workflow job for this annotation

GitHub Actions / branch-test

Unexpected any. Specify a different type

// client-side filter
const filter = params.filter.q
let filteredData = data
if (filter) {
// v podstate vygenerovane Copilotom :D
// vyhladava to filter string vo vsetkych fieldoch kazdeho recordu
filteredData = data.filter((record: RaRecord) => {
return Object.keys(record).some((key) => {
const value = record[key]
return value && value.toString().toLowerCase().includes(filter.toLowerCase())
if (params.filter) {
const {q: search, ...rest} = params.filter
if (search) {
// vyhladava to filter string vo vsetkych fieldoch kazdeho recordu
// - bohuzial tie fieldy su casto len IDcka inych modelov, tak nic moc :D
filteredData = data.filter((record: RaRecord) => {
const matches = Object.values(record).some((value) => {
return value && JSON.stringify(value).toLowerCase().includes(search.toLowerCase())
})
return matches
})
}

if (rest) {
filteredData = filteredData.filter((record: RaRecord) => {
return Object.entries(rest).every(([key, value]) => {
if (!value) return true
return record[key] === value
})
})
})
}
}

// client-side sort
const {field, order} = params.sort
if (params.sort) {
const {field, order} = params.sort

filteredData.sort(dynamicSort(field, order))
filteredData.sort(dynamicSort(field, order))
}

// client-side pagination
const {page, perPage} = params.pagination
const pagedData = filteredData.slice((page - 1) * perPage, page * perPage)
let pagedData = filteredData
if (params.pagination) {
const {page, perPage} = params.pagination
pagedData = filteredData.slice((page - 1) * perPage, page * perPage)
}

return {
data: pagedData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {TruncatedTextField} from '@/components/Admin/custom/TruncatedTextField'

export const FlatpageList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<NumberField source="id" />
<TextField source="url" />
<TextField source="title" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Admin/resources/cms/post/PostCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PostCreate: FC = () => {
<TextInput source="caption" fullWidth validate={required()} />
<TextInput source="short_text" fullWidth validate={maxLength(200, 'Text musí mať najviac 200 znakov.')} />
<TextInput source="details" multiline fullWidth />
<MyDateTimeInput source="added_at" fullWidth disabled defaultValue={new Date().toISOString()} />
{/* <MyDateTimeInput source="added_at" fullWidth disabled /> */}
<MyDateTimeInput source="visible_after" fullWidth validate={required()} />
<MyDateTimeInput source="visible_until" fullWidth validate={required()} />
<SitesCheckboxInput source="sites" validate={required()} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Admin/resources/cms/post/PostList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {TruncatedTextField} from '@/components/Admin/custom/TruncatedTextField'

export const PostList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<TextField source="caption" />
<TruncatedTextField source="short_text" maxTextWidth={50} />
<TruncatedTextField source="details" maxTextWidth={50} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Admin/resources/cms/post/PostShow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const PostShow: FC = () => (
<Tab label="links">
<SimpleShowLayout>
<ArrayField source="links">
<Datagrid>
<Datagrid rowClick={false}>
<TextField source="caption" />
<TextField source="url" />
</Datagrid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {TruncatedTextField} from '@/components/Admin/custom/TruncatedTextField'

export const CompetitionList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<TextField source="name" />
<TextField source="slug" />
<TextField source="start_year" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const UpcomingOrCurrentEvent: FC = () => {
const record = useRecordContext()
const redirect = useRedirect()

if (!record) return null

return (
<Labeled
label="Prebiehajúca alebo najbližšia akcia"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {EventRegistration} from '@/types/api/competition'

export const EventRegistrationList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<FunctionField
source="profile.last_name"
label="Meno a priezvisko"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {SeasonCodeField} from '@/components/Admin/custom/SeasonCodeField'

export const EventList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<ReferenceField source="competition" reference="competition/competition" link={false} />
<NumberField source="year" />
<SeasonCodeField source="season_code" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const EventShow: FC = () => (
<Tab label="publications">
<SimpleShowLayout>
<ArrayField source="publication_set">
<Datagrid>
<Datagrid rowClick={false}>
<TextField source="name" />
<TextField source="file" />
<ReferenceField source="publication_type" reference="competition/publication-type" link="show">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {LatexPreview} from '@/components/Admin/custom/LatexPreview'
import {MyCreate} from '@/components/Admin/custom/MyCreate'
import {MyFileField} from '@/components/Admin/custom/MyFileField'
import {MyImageField} from '@/components/Admin/custom/MyImageField'
import {Accept} from '@/utils/dropzone-accept'

import {createProblemFormData} from './createProblemFormData'

Expand All @@ -23,10 +24,10 @@ export const ProblemCreate: FC = () => (
<TextInput source="text" multiline fullWidth validate={required()} />
<LatexPreview source="text" />
<TextInput source="order" fullWidth validate={required()} />
<ImageInput source="image" accept="image/*">
<ImageInput source="image" accept={Accept.Image}>
<MyImageField />
</ImageInput>
<FileInput source="solution_pdf" accept="application/pdf">
<FileInput source="solution_pdf" accept={Accept.Pdf}>
<MyFileField />
</FileInput>
</FormTab>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {LatexPreview} from '@/components/Admin/custom/LatexPreview'
import {MyEdit} from '@/components/Admin/custom/MyEdit'
import {MyFileField} from '@/components/Admin/custom/MyFileField'
import {MyImageField} from '@/components/Admin/custom/MyImageField'
import {Accept} from '@/utils/dropzone-accept'

import {createProblemFormData} from './createProblemFormData'

Expand All @@ -23,10 +24,10 @@ export const ProblemEdit: FC = () => (
<TextInput source="text" multiline fullWidth validate={required()} />
<LatexPreview source="text" />
<TextInput source="order" fullWidth validate={required()} />
<ImageInput source="image" accept="image/*">
<ImageInput source="image" accept={Accept.Image}>
<MyImageField />
</ImageInput>
<FileInput source="solution_pdf" accept="application/pdf">
<FileInput source="solution_pdf" accept={Accept.Pdf}>
<MyFileField />
</FileInput>
</FormTab>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {TruncatedTextField} from '@/components/Admin/custom/TruncatedTextField'

export const ProblemList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<ReferenceField source="series" reference="competition/series" link={false} />
<NumberField source="order" />
<TruncatedTextField source="text" maxTextWidth={50} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {SeasonCodeField} from '@/components/Admin/custom/SeasonCodeField'

export const SemesterList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<ReferenceField source="competition" reference="competition/competition" link={false} />
<NumberField source="year" />
<SeasonCodeField source="season_code" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const SemesterShow: FC = () => (
<DateTimeField source="deadline" />
<TextField source="order" />
<ArrayField source="problems">
<Datagrid>
<Datagrid rowClick={false}>
<TruncatedTextField source="text" maxTextWidth={50} />
</Datagrid>
</ArrayField>
Expand All @@ -51,10 +51,10 @@ export const SemesterShow: FC = () => (
<Tab label="publications">
<SimpleShowLayout>
<ArrayField source="publication_set">
<Datagrid>
<Datagrid rowClick={false}>
<TextField source="name" />
<TextField source="file" />
<ReferenceField source="publication_type" reference="competition/publication-type" link="show">
<ReferenceField source="publication_type" reference="competition/publication-type">
<TextField source="name" />
</ReferenceField>
<NumberField source="event" />
Expand All @@ -66,7 +66,7 @@ export const SemesterShow: FC = () => (
<Tab label="late tags">
<SimpleShowLayout>
<ReferenceArrayField source="late_tags" reference="competition/late-tag">
<Datagrid>
<Datagrid rowClick={false}>
<TextField source="name" />
<TextField source="slug" />
<TextField source="upper_bound" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {DateTimeField} from '@/components/Admin/custom/DateTimeField'

export const SeriesList: FC = () => (
<List>
<Datagrid rowClick="show">
<Datagrid>
<ReferenceField source="semester" reference="competition/semester" link={false} />
<DateTimeField source="deadline" />
<TextField source="order" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {AutocompleteInput, BooleanInput, FileInput, ReferenceInput, required, Si

import {MyCreate} from '@/components/Admin/custom/MyCreate'
import {MyFileField} from '@/components/Admin/custom/MyFileField'
import {Accept} from '@/utils/dropzone-accept'

import {createSolutionFormData} from './createSolutionFormData'

Expand All @@ -20,7 +21,7 @@ export const SolutionCreate: FC = () => (
<ReferenceInput source="semester_registration" reference="competition/event-registration">
<AutocompleteInput optionText="verbose_name" fullWidth validate={required()} />
</ReferenceInput>
<FileInput source="solution" accept="application/pdf">
<FileInput source="solution" accept={Accept.Pdf}>
<MyFileField />
</FileInput>
<ReferenceInput source="late_tag" reference="competition/late-tag" label="Je riešenie po termíne?">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {AutocompleteInput, BooleanInput, FileInput, ReferenceInput, required, Si

import {MyEdit} from '@/components/Admin/custom/MyEdit'
import {MyFileField} from '@/components/Admin/custom/MyFileField'
import {Accept} from '@/utils/dropzone-accept'

import {createSolutionFormData} from './createSolutionFormData'

Expand All @@ -20,7 +21,7 @@ export const SolutionEdit: FC = () => (
<ReferenceInput source="semester_registration" reference="competition/event-registration">
<AutocompleteInput optionText="verbose_name" fullWidth validate={required()} />
</ReferenceInput>
<FileInput source="solution" accept="application/pdf">
<FileInput source="solution" accept={Accept.Pdf}>
<MyFileField />
</FileInput>
<ReferenceInput source="late_tag" reference="competition/late-tag" label="Je riešenie po termíne?">
Expand Down
Loading

0 comments on commit b0c972d

Please sign in to comment.