Skip to content

Commit

Permalink
Improve event edit form dirty state detection
Browse files Browse the repository at this point in the history
Type improvements around event edit
  • Loading branch information
Alf-Melmac committed Mar 25, 2023
1 parent aa17aed commit c3989a9
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 41 deletions.
9 changes: 7 additions & 2 deletions src/features/event/action/details/EventDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {randomId} from '@mantine/hooks';
import {EventDetail} from './EventDetail';
import {T} from '../../../../components/T';
import {SortableList} from '../../../../components/Form/Sortable/SortableList';
import {convertDtoToFormEvent} from '../../edit/utils';

const MAX_DETAILS = 23;

Expand All @@ -26,8 +27,12 @@ export function EventDetails(): JSX.Element {
};

const {mutate} = useEventUpdate({details: filterFrontendIds<EventActionFormType['details'][number]>(form.values.details)},
// @ts-ignore Details matches here
result => form.setFieldValue('details', result.details));
result => {
// @ts-ignore Details matches here
form.setFieldValue('details', result.details);
// @ts-ignore
form.resetDirty(convertDtoToFormEvent(result));
});
return <>
<SortableList<typeof form.values.details[number]> formPath={'details'} itemProps={{mt: 'sm'}}
renderItem={(item, index) => <EventDetail item={item} index={index} key={item.id}/>}/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {useEditMode} from '../../../../contexts/event/action/EditModeContext';
import {useEventUpdate} from '../useEventUpdate';
import {usePrevious} from '@mantine/hooks';
import {T} from '../../../../components/T';

export const randomColor = () => `#${Math.floor(Math.random() * 16777215).toString(16)}`;
import {randomColor} from '../utils';

type EventTypeInputsProps = {
query: UseQueryResult<Array<EventTypeDto>, Error>;
Expand Down
9 changes: 7 additions & 2 deletions src/features/event/action/slotlist/EventSlotlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {useChangeWatcher, useEventSlotListUpdate} from '../useEventUpdate';
import {FilteredEventAction, filterFrontendIds} from '../../../../utils/formHelper';
import {EventActionPageTitle} from '../EventActionPageTitle';
import {T} from '../../../../components/T';
import {convertDtoToFormEvent} from '../../edit/utils';

type EventSlotlistProps = Partial<Pick<EventEditDto, 'canUploadSlotlist'>>;

Expand Down Expand Up @@ -42,8 +43,12 @@ export function EventSlotlist(props: EventSlotlistProps): JSX.Element {
});

const {mutate} = useEventSlotListUpdate({squadList: squadList},
// @ts-ignore SquadList matches here
result => form.setFieldValue('squadList', result.squadList));
result => {
// @ts-ignore SquadList matches here
form.setFieldValue('squadList', result.squadList);
// @ts-ignore
form.resetDirty(convertDtoToFormEvent(result));
});
useChangeWatcher('reserveParticipating');
const reserveParticipatingInputProps = form.getInputProps('reserveParticipating', {type: 'checkbox'});
return <>
Expand Down
41 changes: 22 additions & 19 deletions src/features/event/action/useEventUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import slotbotServerClient, {voidFunction} from '../../../hooks/slotbotServerClient';
import slotbotServerClient from '../../../hooks/slotbotServerClient';
import {useMutation} from '@tanstack/react-query';
import {AxiosError} from 'axios';
import {useEventPage} from '../../../contexts/event/EventPageContext';
Expand All @@ -14,11 +14,13 @@ export function useEventTextChange(formPath: string, value: string, onSuccess?:
const eventId = useEventPage();
const postTextChange = () => slotbotServerClient.put(`/events/${eventId}/edit/text`, {
[formPath]: value,
}).then(voidFunction);
const {mutate} = useMutation<void, AxiosError>(postTextChange, {
onSuccess: () => {
onSuccess?.(value);
successNotification(value);
}).then((res) => res.data);
const {mutate} = useMutation<EventEditDto, AxiosError>(postTextChange, {
onSuccess: (response) => {
// @ts-ignore Posted it like that, so response is the same format
onSuccess?.(response[formPath]);
// @ts-ignore
successNotification(response[formPath]);
},
onError: errorNotification,
});
Expand All @@ -29,29 +31,30 @@ export function useEventTextChange(formPath: string, value: string, onSuccess?:
export function useEventUpdate(data: unknown, onSuccess?: (saved: EventEditDto) => void) {
const eventId = useEventPage();
const postEventUpdate = () => slotbotServerClient.put(`/events/${eventId}`, data).then((res) => res.data);
const {mutate} = useMutation<EventEditDto, AxiosError>(postEventUpdate, {
onSuccess: (response) => {
onSuccess?.({...response, dateTime: convertUtcDateTimeToLocal(response.dateTime)});
successNotification();
},
onError: errorNotification,
});
const {mutate} = useMutation<EventEditDto, AxiosError>(postEventUpdate, getEventEditMutationOptions(onSuccess));

return {mutate};
}

export function useEventSlotListUpdate(data: unknown, onSuccess?: (saved: EventEditDto) => void) {
const eventId = useEventPage();
const postEventUpdate = () => slotbotServerClient.put(`/events/${eventId}/slotlist`, data).then((res) => res.data);
const {mutate} = useMutation<EventEditDto, AxiosError>(postEventUpdate, {
onSuccess: (response) => {
onSuccess?.(response);
const {mutate} = useMutation<EventEditDto, AxiosError>(postEventUpdate, getEventEditMutationOptions(onSuccess));

return {mutate};
}

/**
* Mutation options for event updates. Converts utc times to local data and shows result-notifications.
*/
function getEventEditMutationOptions(onSuccess?: (saved: EventEditDto) => void) {
return {
onSuccess: (response: EventEditDto) => {
onSuccess?.({...response, dateTime: convertUtcDateTimeToLocal(response.dateTime)});
successNotification();
},
onError: errorNotification,
});

return {mutate};
};
}

export function useChangeWatcher(field: string) {
Expand Down
6 changes: 6 additions & 0 deletions src/features/event/action/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Generates a random color in hex format
*/
export function randomColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
23 changes: 13 additions & 10 deletions src/features/event/edit/EventEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import {Container, Divider} from '@mantine/core';
import {Nav} from '../../../components/nav/Nav';
import {Breadcrumb} from '../../../components/Breadcrumb';
import {EventEditDto} from '../eventTypes';
import {EventGeneralInformation} from '../action/generalInformation/EventGeneralInformation';
import {EventDetailsPage} from '../action/details/EventDetailsPage';
import {EventSlotlist} from '../action/slotlist/EventSlotlist';
import {EventEditProvider, useEventEditForm} from '../../../contexts/event/action/EventActionFormContext';
import {
EventEditFormType,
EventEditProvider,
useEventEditForm,
} from '../../../contexts/event/action/EventActionFormContext';
import {EventPageParams} from '../EventRoutes';
import {eventActionValidate} from '../action/validation';
import {EventEditDto} from '../eventTypes';

export type EventEditProps = EventPageParams & {
event: EventEditDto;
type EventEditProps = EventPageParams & {
event: EventEditFormType;
permissions: Pick<EventEditDto, 'canRevokeShareable' | 'canUploadSlotlist'>
}

export function EventEdit(props: EventEditProps): JSX.Element {
const {eventId, event} = props;
const {eventId, event, permissions: {canRevokeShareable, canUploadSlotlist}} = props;

const breadcrumbItems = [
{
Expand All @@ -30,10 +35,8 @@ export function EventEdit(props: EventEditProps): JSX.Element {
title: 'breadcrumb.edit',
}];

const {dateTime, ...initialValues} = event;
const date = new Date(dateTime);
const form = useEventEditForm({
initialValues: {...initialValues, date: date, startTime: date},
initialValues: event,
validate: (values) => eventActionValidate(values),
validateInputOnChange: true,
});
Expand All @@ -44,15 +47,15 @@ export function EventEdit(props: EventEditProps): JSX.Element {
<EventEditProvider form={form} eventId={eventId}>
<Breadcrumb items={breadcrumbItems}/>

<EventGeneralInformation canRevokeShareable={event.canRevokeShareable}/>
<EventGeneralInformation canRevokeShareable={canRevokeShareable}/>

<Divider my={'lg'}/>

<EventDetailsPage/>

<Divider my={'lg'}/>

<EventSlotlist canUploadSlotlist={event.canUploadSlotlist}/>
<EventSlotlist canUploadSlotlist={canUploadSlotlist}/>
</EventEditProvider>
</Container>
</Nav>
Expand Down
12 changes: 7 additions & 5 deletions src/features/event/edit/EventEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {fetchEventForEdit} from '../EventFetcher';
import {GeneralError} from '../../../components/error/GeneralError';
import {EventEdit} from './EventEdit';
import {useEffect, useState} from 'react';
import {replaceNullWithEmpty, replaceNullWithUndefined} from '../../../utils/typesHelper';
import {EventEditLoading} from './EventEditLoading';
import {useTranslatedDocumentTitle} from '../../../hooks/useTranslatedDocumentTitle';
import {useLanguage} from '../../../contexts/language/Language';
import {convertDtoToFormEvent} from './utils';

export function EventEditPage(): JSX.Element {
const {t} = useLanguage();
Expand All @@ -26,8 +26,10 @@ export function EventEditPage(): JSX.Element {
if (isLoading) return <EventEditLoading/>;
if (error || !event) return <GeneralError error={error}/>;

replaceNullWithEmpty(event, ['description', 'missionLength', 'missionType', 'pictureUrl']);
replaceNullWithUndefined(event, ['reserveParticipating']);

return <EventEdit eventId={eventId} event={event}/>;
return <EventEdit eventId={eventId}
event={convertDtoToFormEvent(event)}
permissions={{
canUploadSlotlist: event.canUploadSlotlist,
canRevokeShareable: event.canRevokeShareable,
}}/>;
}
14 changes: 14 additions & 0 deletions src/features/event/edit/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {EventEditDto} from '../eventTypes';
import {EventEditFormType} from '../../../contexts/event/action/EventActionFormContext';
import {replaceNullWithEmpty, replaceNullWithUndefined} from '../../../utils/typesHelper';

/**
* Converts a {@link EventEditDto} to the format {@link EventEditFormType expected by the form}.
*/
export function convertDtoToFormEvent(dto: EventEditDto): EventEditFormType {
const {dateTime, ...eventDto} = dto;
replaceNullWithEmpty(eventDto, ['description', 'missionLength', 'missionType', 'pictureUrl']);
replaceNullWithUndefined(eventDto, ['reserveParticipating']);
const date = new Date(dateTime);
return {...eventDto, date: date, startTime: date};
}
2 changes: 1 addition & 1 deletion src/features/event/wizard/EventWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Nav} from '../../../components/nav/Nav';
import {Breadcrumb} from '../../../components/Breadcrumb';
import {useEffect, useState} from 'react';
import {EventDetailsDto} from '../eventTypes';
import {randomColor} from '../action/generalInformation/EventTypeInputs';
import {randomColor} from '../action/utils';
import {useAuth} from '../../../contexts/authentication/AuthProvider';
import {eventActionValidate} from '../action/validation';
import {EventWizardProvider, useEventWizardForm} from '../../../contexts/event/action/EventActionFormContext';
Expand Down

0 comments on commit c3989a9

Please sign in to comment.