Skip to content

Commit

Permalink
fix: meta form fields breaking reservation mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
joonatank committed Jan 31, 2025
1 parent 0d3d75f commit 7e27c9e
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 136 deletions.
35 changes: 1 addition & 34 deletions apps/admin-ui/src/common/util.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import { format, getDay, isSameDay, parseISO } from "date-fns";
import { trim, camelCase, get, pick, zipObject } from "lodash";
import { trim } from "lodash";
import {
type Maybe,
type ReservationNode,
ReservationTypeChoice,
type LocationFieldsFragment,
type ReservationCommonFragment,
type ReservationMetadataFieldNode,
} from "@gql/gql-types";
import type { TFunction } from "next-i18next";
import { toMondayFirstUnsafe, truncate } from "common/src/helpers";
import {
type ReservationFormType,
type RecurringReservationForm,
type ReservationChangeFormType,
} from "@/schemas";

export { formatDuration } from "common/src/common/util";

Expand Down Expand Up @@ -131,33 +125,6 @@ export function getTranslatedError(
return t(`Notifications.form.errors.${error}`);
}

// TODO this should be typed
// some mutations expect purpose others purposePk
// but because this isn't typed we have to check runtime errors for each mutation
export function flattenMetadata(
values:
| ReservationFormType
| RecurringReservationForm
| ReservationChangeFormType,
metadataSetFields: Pick<ReservationMetadataFieldNode, "fieldName">[],
shouldRenamePkFields = true
) {
const fieldNames = metadataSetFields.map((f) => f.fieldName).map(camelCase);
// TODO don't use pick
const metadataSetValues = pick(values, fieldNames);

const renamePkFields = shouldRenamePkFields
? ["ageGroup", "homeCity", "purpose"]
: [];

return zipObject(
Object.keys(metadataSetValues).map((k) =>
renamePkFields.includes(k) ? `${k}Pk` : k
),
Object.values(metadataSetValues).map((v) => get(v, "value") || v)
);
}

export function getReserveeName(
reservation: ReservationCommonFragment,
t: TFunction,
Expand Down
7 changes: 3 additions & 4 deletions apps/admin-ui/src/schemas/reservationMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
// CreateReservationModal / RecurringReservation / EditReservation
import { CustomerTypeChoice } from "@gql/gql-types";
import { z } from "zod";
import { OptionSchema } from "common/src/schemas/schemaCommon";

const ReservationFormMetaSchema = z.object({
name: z.string().optional(),
description: z.string().optional(),
ageGroup: OptionSchema.optional(),
ageGroup: z.number().optional(),
applyingForFreeOfCharge: z.boolean().optional(),
billingAddressCity: z.string().optional(),
billingAddressStreet: z.string().optional(),
Expand All @@ -18,9 +17,9 @@ const ReservationFormMetaSchema = z.object({
billingLastName: z.string().optional(),
billingPhone: z.string().optional(),
freeOfChargeReason: z.string().optional(),
homeCity: OptionSchema.optional(),
homeCity: z.number().optional(),
numPersons: z.number().optional(),
purpose: OptionSchema.optional(),
purpose: z.number().optional(),
reserveeAddressCity: z.string().optional(),
reserveeAddressStreet: z.string().optional(),
reserveeAddressZip: z.string().optional(),
Expand Down
48 changes: 24 additions & 24 deletions apps/admin-ui/src/spa/my-units/[id]/CreateReservationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ import { useModal } from "@/context/ModalContext";
import { ControlledTimeInput } from "@/component/ControlledTimeInput";
import { ControlledDateInput } from "common/src/components/form";
import ReservationTypeForm from "@/component/ReservationTypeForm";
import { flattenMetadata } from "@/common/util";
import { base64encode, filterNonNullable } from "common/src/helpers";
import { base64encode } from "common/src/helpers";
import { errorToast, successToast } from "common/src/common/toast";
import { CenterSpinner } from "common/styles/util";

Expand Down Expand Up @@ -258,22 +257,22 @@ function DialogContent({
}, [start, trigger]);

// force form vaildation on date change but not on first render
const date = watch("date");
const formDate = watch("date");
useEffect(() => {
// Is touched is always false with controller
if (getFieldState("date").isDirty) {
trigger();
}
}, [date, trigger, getFieldState]);
}, [formDate, trigger, getFieldState]);

// force revalidation of end time on start time change
const startTime = watch("startTime");
const formStartTime = watch("startTime");
useEffect(() => {
// Is touched is always false with controller
if (getFieldState("endTime").isDirty) {
trigger("endTime");
}
}, [startTime, trigger, getFieldState]);
}, [formStartTime, trigger, getFieldState]);

const [create] = useCreateStaffReservationMutation();

Expand All @@ -297,33 +296,34 @@ function DialogContent({
throw new Error("Missing reservation unit");
}

const fields = filterNonNullable(
reservationUnit.metadataSet?.supportedFields
);
const {
comments,
date,
startTime,
endTime,
type,
bufferTimeBefore,
bufferTimeAfter,
...rest
} = values;

const flatMetaValues = flattenMetadata(values, fields);

if (values.type == null) {
throw new Error("Invalid reservation type");
}
const bufferBefore =
values.type !== ReservationTypeChoice.Blocked && values.bufferTimeBefore
? (reservationUnit.bufferTimeBefore ?? 0)
type !== ReservationTypeChoice.Blocked && bufferTimeBefore
? reservationUnit.bufferTimeBefore
: 0;
const bufferAfter =
values.type !== ReservationTypeChoice.Blocked && values.bufferTimeAfter
? (reservationUnit.bufferTimeAfter ?? 0)
type !== ReservationTypeChoice.Blocked && bufferTimeAfter
? reservationUnit.bufferTimeAfter
: 0;
const input: ReservationStaffCreateMutationInput = {
...rest,
reservationUnit: reservationUnit.pk,
type: values.type,
begin: dateTime(values.date, values.startTime),
end: dateTime(values.date, values.endTime),
type,
begin: dateTime(date, startTime),
end: dateTime(date, endTime),
bufferTimeBefore: bufferBefore,
bufferTimeAfter: bufferAfter,
workingMemo: values.comments,
...flatMetaValues,
reserveeType: values.reserveeType,
workingMemo: comments,
};

await createStaffReservation(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useNavigate, useSearchParams } from "react-router-dom";
import { fromUIDate } from "common/src/common/util";
import {
RecurringReservationFormSchema,
type ReservationFormMeta,
type RecurringReservationForm as RecurringReservationFormT,
} from "@/schemas";
import { type NewReservationListItem } from "@/component/ReservationsList";
Expand All @@ -34,7 +35,7 @@ import ReservationTypeForm, {
} from "@/component/ReservationTypeForm";
import { ControlledTimeInput } from "@/component/ControlledTimeInput";
import { ControlledDateInput } from "common/src/components/form";
import { base64encode, filterNonNullable } from "common/src/helpers";
import { base64encode } from "common/src/helpers";
import { Element } from "@/styles/util";
import { Label } from "@/styles/layout";
import { AutoGrid, Flex } from "common/styles/util";
Expand Down Expand Up @@ -120,6 +121,7 @@ type QueryT = NonNullable<NonNullable<ReservationUnitQuery>["reservationUnit"]>;
type ReservationUnitType = TypeFormReservationUnit &
Pick<QueryT, "pk" | "reservationStartInterval">;

type FormValues = RecurringReservationFormT & ReservationFormMeta;
function RecurringReservationForm({
reservationUnit,
}: {
Expand All @@ -131,7 +133,7 @@ function RecurringReservationForm({
reservationUnit?.reservationStartInterval
);

const form = useForm<RecurringReservationFormT>({
const form = useForm<FormValues>({
// TODO onBlur doesn't work properly we have to submit the form to get validation errors
mode: "onBlur",
defaultValues: {
Expand Down Expand Up @@ -200,7 +202,7 @@ function RecurringReservationForm({

const navigate = useNavigate();

const onSubmit = async (data: RecurringReservationFormT) => {
const onSubmit = async (data: FormValues) => {
setLocalError(null);

const skipDates = removedReservations
Expand All @@ -217,9 +219,6 @@ function RecurringReservationForm({
return;
}

const metaFields = filterNonNullable(
reservationUnit?.metadataSet?.supportedFields
);
const buffers = {
before: data.bufferTimeBefore
? getBufferTime(reservationUnit.bufferTimeBefore, data.type)
Expand All @@ -234,7 +233,6 @@ function RecurringReservationForm({
data,
skipDates,
reservationUnitPk: reservationUnit.pk,
metaFields,
buffers,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import {
ReservationStateChoice,
ReservationTypeChoice,
ReservationTypeStaffChoice,
type ReservationMetadataFieldNode,
type ReservationSeriesCreateMutationInput,
useCreateReservationSeriesMutation,
type ReservationSeriesReservationCreateSerializerInput,
} from "@gql/gql-types";
import type { RecurringReservationForm } from "@/schemas";
import type { RecurringReservationForm, ReservationFormMeta } from "@/schemas";
import { fromUIDateUnsafe, toApiDateUnsafe } from "common/src/common/util";
import { flattenMetadata } from "@/common/util";
import { gql } from "@apollo/client";
import { errorToast } from "common/src/common/toast";
import { useSession } from "@/hooks/auth";
import { transformReserveeType } from "common/src/conversion";

// Not all choices are valid for recurring reservations (the ui should not allow these)
function transformReservationTypeStaffChoice(
Expand Down Expand Up @@ -60,34 +60,47 @@ export function useCreateRecurringReservation() {

// NOTE unsafe
const mutate = async (props: {
data: RecurringReservationForm;
// FIXME this type is incorrect, it should include the reservation meta fields
data: RecurringReservationForm & ReservationFormMeta;
skipDates: Date[];
reservationUnitPk: number;
metaFields: ReservationMetadataFieldNode[];
// metaFields: ReservationMetadataFieldNode[];
buffers: { before?: number; after?: number };
}): Promise<number | undefined> => {
const { data, reservationUnitPk, metaFields, buffers } = props;
const flattenedMetadataSetValues = flattenMetadata(data, metaFields, false);
// unlike reservation creation age group are passed to the main mutation and others to details
const ageGroup = flattenedMetadataSetValues.ageGroup;
delete flattenedMetadataSetValues.ageGroup;
const { data, reservationUnitPk, buffers } = props;
const {
ageGroup,
type,
seriesName,
comments,
startingDate,
startTime,
endingDate,
endTime,
repeatOnDays,
repeatPattern,
reserveeType,
...rest
} = data;

const name = data.type === "BLOCKED" ? "BLOCKED" : (data.seriesName ?? "");

if (user?.pk == null) {
throw new Error("Current user pk missing");
}

const reservationDetails = {
...flattenedMetadataSetValues,
type: transformReservationTypeStaffChoice(data.type),
bufferTimeBefore: buffers.before,
bufferTimeAfter: buffers.after,
workingMemo: data.comments,
state: ReservationStateChoice.Confirmed,
// TODO why is this needed in the mutation?
user: user.pk,
};
const reservationDetails: ReservationSeriesReservationCreateSerializerInput =
{
...rest,
type: transformReservationTypeStaffChoice(type),
reserveeType: transformReserveeType(reserveeType),
bufferTimeBefore: buffers.before,
bufferTimeAfter: buffers.after,
workingMemo: comments,
state: ReservationStateChoice.Confirmed,
// TODO why is this needed in the mutation?
user: user.pk,
};

const skipDates: string[] = props.skipDates.map((d) => toApiDateUnsafe(d));
const input: ReservationSeriesCreateMutationInput = {
Expand All @@ -97,13 +110,13 @@ export function useCreateRecurringReservation() {
ageGroup: !Number.isNaN(Number(ageGroup)) ? Number(ageGroup) : undefined,
reservationUnit: reservationUnitPk,
beginDate: toApiDateUnsafe(fromUIDateUnsafe(data.startingDate)),
beginTime: data.startTime,
beginTime: startTime,
endDate: toApiDateUnsafe(fromUIDateUnsafe(data.endingDate)),
endTime: data.endTime,
weekdays: data.repeatOnDays,
recurrenceInDays: data.repeatPattern === "weekly" ? 7 : 14,
endTime,
weekdays: repeatOnDays,
recurrenceInDays: repeatPattern === "weekly" ? 7 : 14,
name,
description: data.comments,
description: comments,
};

const { data: createResponse } = await createReservationSeries(input);
Expand Down
Loading

0 comments on commit 7e27c9e

Please sign in to comment.