Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit 18b5989

Browse files
committed
rework: reservation series mutation
Use fast single mutation to create a recurring reservation.
1 parent 3bd3523 commit 18b5989

11 files changed

Lines changed: 329 additions & 309 deletions

File tree

apps/admin-ui/gql/gql-types.ts

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8235,12 +8235,12 @@ export type ReservationUnitCalendarQuery = {
82358235
}> | null;
82368236
};
82378237

8238-
export type CreateRecurringReservationMutationVariables = Exact<{
8239-
input: RecurringReservationCreateMutationInput;
8238+
export type CreateReservationSeriesMutationVariables = Exact<{
8239+
input: ReservationSeriesCreateMutationInput;
82408240
}>;
82418241

8242-
export type CreateRecurringReservationMutation = {
8243-
createRecurringReservation?: { pk?: number | null } | null;
8242+
export type CreateReservationSeriesMutation = {
8243+
createReservationSeries?: { pk?: number | null } | null;
82448244
};
82458245

82468246
export type ReservationsInIntervalFragment = {
@@ -14670,59 +14670,58 @@ export type ReservationUnitCalendarQueryResult = Apollo.QueryResult<
1467014670
ReservationUnitCalendarQuery,
1467114671
ReservationUnitCalendarQueryVariables
1467214672
>;
14673-
export const CreateRecurringReservationDocument = gql`
14674-
mutation CreateRecurringReservation(
14675-
$input: RecurringReservationCreateMutationInput!
14673+
export const CreateReservationSeriesDocument = gql`
14674+
mutation CreateReservationSeries(
14675+
$input: ReservationSeriesCreateMutationInput!
1467614676
) {
14677-
createRecurringReservation(input: $input) {
14677+
createReservationSeries(input: $input) {
1467814678
pk
1467914679
}
1468014680
}
1468114681
`;
14682-
export type CreateRecurringReservationMutationFn = Apollo.MutationFunction<
14683-
CreateRecurringReservationMutation,
14684-
CreateRecurringReservationMutationVariables
14682+
export type CreateReservationSeriesMutationFn = Apollo.MutationFunction<
14683+
CreateReservationSeriesMutation,
14684+
CreateReservationSeriesMutationVariables
1468514685
>;
1468614686

1468714687
/**
14688-
* __useCreateRecurringReservationMutation__
14688+
* __useCreateReservationSeriesMutation__
1468914689
*
14690-
* To run a mutation, you first call `useCreateRecurringReservationMutation` within a React component and pass it any options that fit your needs.
14691-
* When your component renders, `useCreateRecurringReservationMutation` returns a tuple that includes:
14690+
* To run a mutation, you first call `useCreateReservationSeriesMutation` within a React component and pass it any options that fit your needs.
14691+
* When your component renders, `useCreateReservationSeriesMutation` returns a tuple that includes:
1469214692
* - A mutate function that you can call at any time to execute the mutation
1469314693
* - An object with fields that represent the current status of the mutation's execution
1469414694
*
1469514695
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
1469614696
*
1469714697
* @example
14698-
* const [createRecurringReservationMutation, { data, loading, error }] = useCreateRecurringReservationMutation({
14698+
* const [createReservationSeriesMutation, { data, loading, error }] = useCreateReservationSeriesMutation({
1469914699
* variables: {
1470014700
* input: // value for 'input'
1470114701
* },
1470214702
* });
1470314703
*/
14704-
export function useCreateRecurringReservationMutation(
14704+
export function useCreateReservationSeriesMutation(
1470514705
baseOptions?: Apollo.MutationHookOptions<
14706-
CreateRecurringReservationMutation,
14707-
CreateRecurringReservationMutationVariables
14706+
CreateReservationSeriesMutation,
14707+
CreateReservationSeriesMutationVariables
1470814708
>
1470914709
) {
1471014710
const options = { ...defaultOptions, ...baseOptions };
1471114711
return Apollo.useMutation<
14712-
CreateRecurringReservationMutation,
14713-
CreateRecurringReservationMutationVariables
14714-
>(CreateRecurringReservationDocument, options);
14712+
CreateReservationSeriesMutation,
14713+
CreateReservationSeriesMutationVariables
14714+
>(CreateReservationSeriesDocument, options);
1471514715
}
14716-
export type CreateRecurringReservationMutationHookResult = ReturnType<
14717-
typeof useCreateRecurringReservationMutation
14716+
export type CreateReservationSeriesMutationHookResult = ReturnType<
14717+
typeof useCreateReservationSeriesMutation
14718+
>;
14719+
export type CreateReservationSeriesMutationResult =
14720+
Apollo.MutationResult<CreateReservationSeriesMutation>;
14721+
export type CreateReservationSeriesMutationOptions = Apollo.BaseMutationOptions<
14722+
CreateReservationSeriesMutation,
14723+
CreateReservationSeriesMutationVariables
1471814724
>;
14719-
export type CreateRecurringReservationMutationResult =
14720-
Apollo.MutationResult<CreateRecurringReservationMutation>;
14721-
export type CreateRecurringReservationMutationOptions =
14722-
Apollo.BaseMutationOptions<
14723-
CreateRecurringReservationMutation,
14724-
CreateRecurringReservationMutationVariables
14725-
>;
1472614725
export const ReservationTimesInReservationUnitDocument = gql`
1472714726
query ReservationTimesInReservationUnit(
1472814727
$id: ID!

apps/admin-ui/src/common/util.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,18 +203,24 @@ export function getTranslatedError(
203203
return t(`Notifications.form.errors.${error}`);
204204
}
205205

206+
// TODO this should be typed
207+
// some mutations expect purpose others purposePk
208+
// but because this isn't typed we have to check runtime errors for each mutation
206209
export function flattenMetadata(
207210
values:
208211
| ReservationFormType
209212
| RecurringReservationForm
210213
| ReservationChangeFormType,
211-
metadataSetFields: Pick<ReservationMetadataFieldNode, "fieldName">[]
214+
metadataSetFields: Pick<ReservationMetadataFieldNode, "fieldName">[],
215+
shouldRenamePkFields = true
212216
) {
213217
const fieldNames = metadataSetFields.map((f) => f.fieldName).map(camelCase);
214218
// TODO don't use pick
215219
const metadataSetValues = pick(values, fieldNames);
216220

217-
const renamePkFields = ["ageGroup", "homeCity", "purpose"];
221+
const renamePkFields = shouldRenamePkFields
222+
? ["ageGroup", "homeCity", "purpose"]
223+
: [];
218224

219225
return zipObject(
220226
Object.keys(metadataSetValues).map((k) =>

apps/admin-ui/src/component/reservations/EditTimeModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,9 @@ function DialogContent({
230230
? t(`errors.descriptive.${message}`)
231231
: t("errors.descriptive.genericError");
232232
errorToast({
233-
text: t("ReservationDialog.saveFailed", { error: translatedError }),
233+
text: t("ReservationDialog.saveFailedWithError", {
234+
error: translatedError,
235+
}),
234236
});
235237
} else {
236238
errorToast({ text: t("ReservationDialog.saveFailed") });

apps/admin-ui/src/i18n/messages.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,9 @@ const translations: ITranslations = {
415415
reservationsList_one: ["Olet tekemässä yhden varauksen"],
416416
reservationsList_other: ["Olet tekemässä {{count}} varausta"],
417417
name: ["Varaussarjan nimi"],
418+
newOverlapError: [
419+
"Varausta ei pystytty tekemään, koska siinä on uusia päällekkäistä varauksia ({{ count }}). Tarkista lista ja yritä uudelleen.",
420+
],
418421
},
419422
},
420423
Application: {
@@ -1152,7 +1155,8 @@ const translations: ITranslations = {
11521155
// dialog specific
11531156
title: ["Varaa {{reservationUnit}}"],
11541157
accept: ["Varaa"],
1155-
saveFailed: ["Tallennus ei onnistunut. {{error}}"],
1158+
saveFailedWithError: ["Tallennus ei onnistunut. {{error}}"],
1159+
saveFailed: ["Tallennus ei onnistunut."],
11561160
saveSuccess: ["Varaus tehty kohteeseen {{reservationUnit}}"],
11571161
},
11581162
ReservationUnits: {

apps/admin-ui/src/spa/my-units/[id]/CreateReservationModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ function DialogContent({
278278
? t(`errors.descriptive.${errorMsg}`)
279279
: t("errors.descriptive.genericError");
280280
errorToast({
281-
text: t("ReservationDialog.saveFailed", {
281+
text: t("ReservationDialog.saveFailedWithError", {
282282
error: translatedError,
283283
}),
284284
});
Lines changed: 50 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import React from "react";
22
import { useTranslation } from "react-i18next";
3-
import { useNavigate, useLocation } from "react-router-dom";
3+
import { useParams } from "react-router-dom";
44
import styled from "styled-components";
5-
import { Container, Button } from "hds-react";
5+
import { Container } from "hds-react";
66
import { ErrorBoundary } from "react-error-boundary";
7-
import { z } from "zod";
87
import { H1, H6 } from "common/src/common/typography";
98
import { breakpoints } from "common/src/common/style";
109
import { RecurringReservationsView } from "@/component/reservations/requested/RecurringReservationsView";
1110
import { ActionsWrapper } from "./commonStyling";
12-
import { ReservationList } from "@/component/ReservationsList";
11+
import Error404 from "@/common/Error404";
12+
import { useRecurringReservationQuery } from "@gql/gql-types";
13+
import { base64encode, filterNonNullable } from "common/src/helpers";
14+
import { ButtonLikeLink } from "@/component/ButtonLikeLink";
15+
import { errorToast } from "common/src/common/toast";
1316

1417
const InfoSection = styled.p`
1518
margin: var(--spacing-l) 0;
@@ -27,96 +30,55 @@ const StyledContainer = styled(Container)`
2730
}
2831
`;
2932

30-
const ReservationMadeSchema = z.object({
31-
reservationPk: z.number().optional(),
32-
startTime: z.string(),
33-
endTime: z.string(),
34-
date: z.date(),
35-
error: z.string().or(z.unknown()).optional(),
36-
});
37-
export type ReservationMade = z.infer<typeof ReservationMadeSchema>;
38-
39-
const RecurringReservationDoneParamsSchema = z.object({
40-
reservations: z.array(ReservationMadeSchema),
41-
recurringPk: z.number(),
42-
});
43-
44-
function RecurringReservationDoneInner() {
45-
const location = useLocation();
33+
function RecurringReservationDoneInner({
34+
recurringPk,
35+
}: {
36+
recurringPk: number;
37+
}) {
4638
const { t } = useTranslation("translation", {
4739
keyPrefix: "MyUnits.RecurringReservation.Confirmation",
4840
});
49-
const navigate = useNavigate();
50-
51-
const props = RecurringReservationDoneParamsSchema.parse(location.state);
5241

53-
const handleGoToReservation = (id: number) => {
54-
const url = `/reservations/${id}`;
55-
navigate(url);
56-
};
57-
58-
const failed = props.reservations
59-
.filter(({ error }) => error != null)
60-
.map(({ error, ...x }) => ({ ...x, error: String(error) }));
42+
const id = base64encode(`RecurringReservationNode:${recurringPk}`);
43+
const { data } = useRecurringReservationQuery({
44+
skip: !recurringPk,
45+
fetchPolicy: "cache-and-network",
46+
nextFetchPolicy: "cache-first",
47+
variables: { id },
48+
onError: () => {
49+
errorToast({ text: t("errors.errorFetchingData") });
50+
},
51+
});
6152

62-
const successes = props.reservations.filter((x) => x.error == null);
53+
const { recurringReservation } = data ?? {};
54+
const reservations = filterNonNullable(recurringReservation?.reservations);
6355

64-
const reservationId = successes.map((x) => x.reservationPk).find(() => true);
56+
// TODO use urlbuilder
57+
const reservationPk = reservations[0]?.pk;
58+
const reservationUrl = reservationPk ? `/reservations/${reservationPk}` : "";
6559

66-
// TODO holidays not implemented
67-
const holidays = 0;
60+
const successes = reservations;
6861

6962
return (
7063
<StyledContainer>
71-
<H1 $legacy>{successes.length > 0 ? t(`title`) : t("allFailedTitle")}</H1>
72-
<InfoSection>
73-
<span>
74-
{failed.length === 0
75-
? t(`successInfo`)
76-
: t(`failureInfo`, {
77-
total: props.reservations.length,
78-
conflicts: failed.length,
79-
})}
80-
</span>
81-
{holidays > 0 && (
82-
<span>
83-
{t(`holidayInfo`, {
84-
total: props.reservations.length,
85-
holidays: 0,
86-
})}
87-
</span>
88-
)}
89-
</InfoSection>
90-
{failed.length > 0 && (
91-
<InfoSection>{t(`failureInfoSecondParagraph`)}</InfoSection>
92-
)}
93-
{failed.length > 0 && (
94-
<StyledH6 as="h2">
95-
{t("failedSubtitle")} ({failed.length})
96-
</StyledH6>
97-
)}
98-
<ReservationList items={failed} hasPadding />
64+
<H1 $legacy>
65+
{reservations.length > 0 ? t(`title`) : t("allFailedTitle")}
66+
</H1>
67+
<InfoSection>{t(`successInfo`)}</InfoSection>
9968
<StyledH6 as="h2">
10069
{t("successSubtitle")} ({successes.length})
10170
</StyledH6>
102-
<RecurringReservationsView recurringPk={props.recurringPk} />
71+
<RecurringReservationsView recurringPk={recurringPk} />
10372
<ActionsWrapper>
104-
<Button
105-
variant="secondary"
106-
onClick={() => navigate("../..", { relative: "path" })}
107-
theme="black"
108-
>
73+
<ButtonLikeLink to="../../.." relative="path">
10974
{t(`buttonToUnit`)}
110-
</Button>
111-
{reservationId != null && (
112-
<Button
113-
variant="secondary"
114-
onClick={() => handleGoToReservation(reservationId)}
115-
theme="black"
116-
>
117-
{t(`buttonToReservation`)}
118-
</Button>
119-
)}
75+
</ButtonLikeLink>
76+
<ButtonLikeLink
77+
disabled={reservations[0]?.pk == null}
78+
to={reservationUrl}
79+
>
80+
{t(`buttonToReservation`)}
81+
</ButtonLikeLink>
12082
</ActionsWrapper>
12183
</StyledContainer>
12284
);
@@ -128,9 +90,17 @@ function ErrorComponent() {
12890
}
12991

13092
export function RecurringReservationDone() {
93+
const { pk } = useParams() as { pk: string };
94+
const recurringPk = Number(pk);
95+
const isValid = recurringPk > 0;
96+
97+
if (!isValid) {
98+
return <Error404 />;
99+
}
100+
131101
return (
132102
<ErrorBoundary FallbackComponent={ErrorComponent}>
133-
<RecurringReservationDoneInner />
103+
<RecurringReservationDoneInner recurringPk={recurringPk} />
134104
</ErrorBoundary>
135105
);
136106
}

0 commit comments

Comments
 (0)