Skip to content

Commit

Permalink
fix: edit reservation failing
Browse files Browse the repository at this point in the history
- Fix: edit mutation called with incorrect end time.
- Fix: edit / new reservation steppers not doing anything.
- Refactor: cleaner edit step components.
  • Loading branch information
joonatank committed Mar 6, 2025
1 parent 95bed13 commit d595aa4
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 140 deletions.
25 changes: 5 additions & 20 deletions apps/ui/components/reservation/EditStep0.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@ import type {
ReservationUnitPageQuery,
} from "@gql/gql-types";
import { differenceInMinutes } from "date-fns";
import {
Button,
ButtonVariant,
IconArrowRight,
IconCross,
LoadingSpinner,
} from "hds-react";
import { Button, ButtonVariant, IconArrowRight, IconCross } from "hds-react";
import React from "react";
import { useTranslation } from "next-i18next";
import styled from "styled-components";
Expand Down Expand Up @@ -57,8 +51,6 @@ type Props = {
activeApplicationRounds: readonly RoundPeriod[];
blockingReservations: readonly BlockingReservationFieldsFragment[];
nextStep: () => void;
apiBaseUrl: string;
isLoading: boolean;
};

const StyledCalendarWrapper = styled.div`
Expand Down Expand Up @@ -120,7 +112,6 @@ export function EditStep0({
activeApplicationRounds,
reservationForm,
nextStep,
isLoading,
blockingReservations: blockingReservationsOrig,
}: Props): JSX.Element {
const { t, i18n } = useTranslation();
Expand Down Expand Up @@ -266,20 +257,14 @@ export function EditStep0({
href={getReservationPath(reservation.pk)}
data-testid="reservation-edit__button--cancel"
>
<IconCross aria-hidden="true" />
<IconCross />
{t("common:stop")}
</ButtonLikeLink>
<Button
type="submit"
variant={isLoading ? ButtonVariant.Clear : ButtonVariant.Primary}
iconEnd={
isLoading ? (
<LoadingSpinner small />
) : (
<IconArrowRight aria-hidden="true" />
)
}
disabled={!focusSlot.isReservable || !isDirty || isLoading}
variant={ButtonVariant.Primary}
iconEnd={<IconArrowRight />}
disabled={!focusSlot.isReservable || !isDirty}
data-testid="reservation__button--continue"
>
{t("common:next")}
Expand Down
68 changes: 46 additions & 22 deletions apps/ui/components/reservation/EditStep1.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type ReservationEditPageQuery,
type ReservationUnitPageQuery,
useAdjustReservationTimeMutation,
} from "@gql/gql-types";
import {
Button,
Expand All @@ -27,6 +28,8 @@ import { type UseFormReturn } from "react-hook-form";
import { convertReservationFormToApi } from "@/modules/reservation";
import { AcceptTerms } from "./AcceptTerms";
import { getReservationPath } from "@/modules/urls";
import { useDisplayError } from "@/hooks/useDisplayError";
import { useRouter } from "next/router";

type ReservationUnitNodeT = NonNullable<
ReservationUnitPageQuery["reservationUnit"]
Expand All @@ -37,8 +40,6 @@ type Props = {
reservationUnit: ReservationUnitNodeT;
options: OptionsRecord;
onBack: () => void;
handleSubmit: () => void;
isSubmitting: boolean;
form: UseFormReturn<PendingReservationFormType>;
};

Expand Down Expand Up @@ -97,8 +98,6 @@ export function EditStep1({
reservationUnit,
options,
onBack,
handleSubmit,
isSubmitting,
form,
}: Props): JSX.Element {
const { t } = useTranslation();
Expand Down Expand Up @@ -130,7 +129,13 @@ export function EditStep1({
frozenReservationUnit?.metadataSet?.supportedFields
);

const { watch } = form;
const {
handleSubmit,
watch,
formState: { isSubmitting },
} = form;
const router = useRouter();
const displayError = useDisplayError();

const apiValues = convertReservationFormToApi(watch());
const modifiedReservation = {
Expand All @@ -139,25 +144,44 @@ export function EditStep1({
end: apiValues?.end ?? reservation.end,
};

const [mutation, { loading }] = useAdjustReservationTimeMutation();

const isLoading = loading || isSubmitting;

// TODO refactor to use form submit instead of getValues
const onSubmit = async (formValues: PendingReservationFormType) => {
if (!termsAccepted) {
errorToast({
text: t("reservationCalendar:errors.termsNotAccepted"),
});
return;
}
// const formValues = getValues();
const times = convertReservationFormToApi(formValues);
if (reservation.pk == null || times == null) {
return;
}
try {
const input = { pk: reservation.pk, ...times };
await mutation({
variables: {
input,
},
});
router.push(`${getReservationPath(reservation.pk)}?timeUpdated=true`);
} catch (e) {
displayError(e);
}
};

const termsAccepted = isTermsAccepted.space && isTermsAccepted.service;
return (
<>
<StyledReservationInfoCard
reservation={modifiedReservation}
type="confirmed"
/>
<StyledForm
onSubmit={(e) => {
e.preventDefault();
if (!termsAccepted) {
errorToast({
text: t("reservationCalendar:errors.termsNotAccepted"),
});
} else {
handleSubmit();
}
}}
>
<StyledForm onSubmit={handleSubmit(onSubmit)}>
<GeneralFields
supportedFields={supportedFields}
reservation={reservation}
Expand All @@ -176,7 +200,7 @@ export function EditStep1({
<Actions>
<Button
variant={ButtonVariant.Secondary}
iconStart={<IconArrowLeft aria-hidden="true" />}
iconStart={<IconArrowLeft />}
onClick={onBack}
data-testid="reservation-edit__button--back"
>
Expand All @@ -187,14 +211,14 @@ export function EditStep1({
size="large"
data-testid="reservation-edit__button--cancel"
>
<IconCross aria-hidden="true" />
<IconCross />
{t("common:stop")}
</ButtonLikeLink>
<Button
type="submit"
variant={isSubmitting ? ButtonVariant.Clear : ButtonVariant.Primary}
iconStart={isSubmitting ? <LoadingSpinner small /> : undefined}
disabled={isSubmitting || !termsAccepted}
variant={isLoading ? ButtonVariant.Clear : ButtonVariant.Primary}
iconStart={isLoading ? <LoadingSpinner small /> : undefined}
disabled={!termsAccepted || isLoading}
data-testid="reservation__button--continue"
>
{t("reservations:saveNewTime")}
Expand Down
9 changes: 5 additions & 4 deletions apps/ui/modules/reservation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,18 +503,19 @@ export function createDateTime(date: string, time: string): Date {
return new Date();
}

// NOTE backend throws errors in some cases if we accidentally send seconds or milliseconds that are not 0
export function convertReservationFormToApi(
formValues: PendingReservationFormType
): { begin: string; end: string } | null {
const time = formValues.time;
const date = fromUIDate(formValues.date ?? "");
const date = fromUIDate(formValues.date);
const duration = formValues.duration;
if (date == null || time == null || duration == null) {
if (date == null || time === "" || duration === 0) {
return null;
}
const minutes = timeToMinutes(time);
const begin: Date = set(date, { minutes });
const end: Date = set(begin, { minutes: minutes + duration });
const begin: Date = set(date, { minutes, seconds: 0, milliseconds: 0 });
const end: Date = addMinutes(begin, duration);
return { begin: begin.toISOString(), end: end.toISOString() };
}

Expand Down
10 changes: 1 addition & 9 deletions apps/ui/pages/reservation-unit/[...params].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,15 +380,7 @@ function NewReservation(props: PropsNarrowed): JSX.Element | null {
language={i18n.language}
selectedStep={step}
style={{ width: "100%" }}
onStepClick={(e) => {
const target = e.currentTarget;
const s = target
.getAttribute("data-testid")
?.replace("hds-stepper-step-", "");
if (s) {
setStep(parseInt(s, 10));
}
}}
onStepClick={(_, index) => setStep(index)}
steps={steps}
/>
)}
Expand Down
98 changes: 13 additions & 85 deletions apps/ui/pages/reservations/[id]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import {
ReservationUnitPageDocument,
type ReservationUnitPageQuery,
type ReservationUnitPageQueryVariables,
type Mutation,
type MutationAdjustReservationTimeArgs,
useAdjustReservationTimeMutation,
type ReservationEditPageQuery,
type ReservationEditPageQueryVariables,
ReservationEditPageDocument,
Expand All @@ -25,12 +22,10 @@ import {
import { toApiDate } from "common/src/common/util";
import { addYears } from "date-fns";
import { RELATED_RESERVATION_STATES } from "common/src/const";
import { gql, type FetchResult } from "@apollo/client";
import { useRouter } from "next/router";
import { gql } from "@apollo/client";
import { StepState, Stepper } from "hds-react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { errorToast } from "common/src/common/toast";
import { EditStep0 } from "@/components/reservation/EditStep0";
import { EditStep1 } from "@/components/reservation/EditStep1";
import {
Expand All @@ -40,7 +35,6 @@ import {
import { ReservationPageWrapper } from "@/components/reservations/styles";
import { queryOptions } from "@/modules/queryOptions";
import {
convertReservationFormToApi,
isReservationEditable,
transformReservation,
} from "@/modules/reservation";
Expand All @@ -61,101 +55,39 @@ const StepperWrapper = styled.div`
`;

function ReservationEditPage(props: PropsNarrowed): JSX.Element {
const {
reservation,
reservationUnit,
apiBaseUrl,
options,
blockingReservations,
} = props;
const { reservation, reservationUnit, options, blockingReservations } = props;
const { t, i18n } = useTranslation();
const router = useRouter();

const [step, setStep] = useState<0 | 1>(0);

const [mutation, { loading: isLoading }] = useAdjustReservationTimeMutation();

// TODO should rework this so we don't pass a string here (use Dates till we do the mutation)
const adjustReservationTime = (
input: MutationAdjustReservationTimeArgs["input"]
): Promise<FetchResult<Mutation>> => {
if (!input.pk) {
throw new Error("No reservation pk provided");
}
if (!input.begin || !input.end) {
throw new Error("No begin or end time provided");
}
// NOTE backend throws errors in some cases if we accidentally send seconds or milliseconds that are not 0
const { begin, end, ...rest } = input;
const beginDate = new Date(begin);
beginDate.setSeconds(0);
beginDate.setMilliseconds(0);
const endDate = new Date(end);
endDate.setSeconds(0);
endDate.setMilliseconds(0);
return mutation({
variables: {
input: {
begin: beginDate.toISOString(),
end: endDate.toISOString(),
...rest,
},
},
});
};

const reservationForm = useForm<PendingReservationFormType>({
const form = useForm<PendingReservationFormType>({
defaultValues: transformReservation(reservation),
mode: "onChange",
resolver: zodResolver(PendingReservationFormSchema),
});

const { getValues, reset } = reservationForm;
const { reset } = form;
useEffect(() => {
reset(transformReservation(reservation));
}, [reservation, reset]);

// TODO refactor to use form submit instead of getValues
const handleSubmit = async () => {
const formValues = getValues();
const times = convertReservationFormToApi(formValues);
if (reservation.pk == null || times == null) {
return;
}
try {
await adjustReservationTime({ pk: reservation.pk, ...times });
router.push(`${getReservationPath(reservation.pk)}?timeUpdated=true`);
} catch (e) {
if (e instanceof Error) {
// TODO don't print the error message to the user
errorToast({ text: e.message });
} else {
errorToast({ text: "Unknown error occurred" });
}
}
};

const title =
step === 0
? "reservations:editReservationTime"
: "reservationCalendar:heading.pendingReservation";

const handleStepClick = (e: React.MouseEvent<HTMLButtonElement>) => {
const target = e.currentTarget;
const s = target
.getAttribute("data-testid")
?.replace("hds-stepper-step-", "");
if (s != null) {
const n = parseInt(s, 10);
if (n === 0 || n === 1) {
setStep(n);
}
const handleStepClick = (
_: React.MouseEvent<HTMLButtonElement>,
index: number
) => {
if (index === 0 || index === 1) {
setStep(index);
}
};

const {
formState: { isValid, dirtyFields },
} = reservationForm;
} = form;
// skip control fields
const isDirty = dirtyFields.date || dirtyFields.time || dirtyFields.duration;
const steps = [
Expand Down Expand Up @@ -193,10 +125,8 @@ function ReservationEditPage(props: PropsNarrowed): JSX.Element {
reservation={reservation}
reservationUnit={reservationUnit}
activeApplicationRounds={activeApplicationRounds}
reservationForm={reservationForm}
reservationForm={form}
nextStep={() => setStep(1)}
apiBaseUrl={apiBaseUrl}
isLoading={false}
blockingReservations={blockingReservations}
/>
) : (
Expand All @@ -205,9 +135,7 @@ function ReservationEditPage(props: PropsNarrowed): JSX.Element {
options={options}
reservationUnit={reservationUnit}
onBack={() => setStep(0)}
handleSubmit={handleSubmit}
form={reservationForm}
isSubmitting={isLoading}
form={form}
/>
)}
</ReservationPageWrapper>
Expand Down
Loading

0 comments on commit d595aa4

Please sign in to comment.