Skip to content

Commit

Permalink
add: application reservation cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
joonatank committed Dec 10, 2024
1 parent 10c9a8c commit 4620d95
Show file tree
Hide file tree
Showing 21 changed files with 432 additions and 192 deletions.
4 changes: 2 additions & 2 deletions apps/ui/components/AccordionWithIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const ClosedAccordionWrapper = styled.div`
padding: var(--spacing-s);
`;

const IconListWrapper = styled.div`
const IconListWrapper = styled.ul`
display: grid;
gap: var(--spacing-xs);
Expand Down Expand Up @@ -123,7 +123,7 @@ export function AccordionWithIcons({
<Heading as={`h${headingLevel}`}>{heading}</Heading>
<IconListWrapper>
{icons.map(({ text, icon, textPostfix }) => (
<IconLabel key={text}>
<IconLabel key={text} as="li">
{icon}
<span>{text}</span>
<span>{textPostfix}</span>
Expand Down
100 changes: 40 additions & 60 deletions apps/ui/components/application/ApprovedReservations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ReservationStateChoice,
} from "@/gql/gql-types";
import {
getApplicationPath,
getApplicationSectionPath,
getReservationUnitPath,
} from "@/modules/urls";
Expand All @@ -20,11 +21,9 @@ import { IconButton, StatusLabel } from "common/src/components";
import {
filterNonNullable,
formatApiTimeInterval,
formatMinutes,
fromMondayFirst,
getLocalizationLang,
LocalizationLanguages,
timeToMinutes,
type LocalizationLanguages,
} from "common/src/helpers";
import {
Button,
Expand All @@ -51,7 +50,11 @@ import { CenterSpinner } from "../common/common";
import { useMedia } from "react-use";
import { ButtonContainer, Flex } from "common/styles/util";
import { useRouter } from "next/router";
import { isReservationCancellable } from "@/modules/reservation";
import {
isReservationCancellableReason,
ReservationCancellableReason,
} from "@/modules/reservation";
import { formatDateTimeStrings } from "@/modules/util";

const N_RESERVATIONS_TO_SHOW = 20;

Expand Down Expand Up @@ -244,7 +247,7 @@ export function ApprovedReservations({ application }: Props) {
return slots.length > 0;
})
);
const initiallyOpen = app?.applicationSections?.length === 1;
const initiallyOpen = sections.length === 1;
return (
<Flex>
{loading && <CenterSpinner />}
Expand Down Expand Up @@ -449,7 +452,7 @@ type ReservationsTableElem = {
"nameSv" | "nameFi" | "nameEn" | "id" | "pk"
>;
status: "" | "rejected" | "modified";
isCancellable: boolean;
isCancellableReason: ReservationCancellableReason;
pk: number;
};

Expand Down Expand Up @@ -513,8 +516,10 @@ const StyledStatusLabel = styled(StatusLabel)`

function ReservationsTable({
reservations,
application,
}: {
reservations: ReservationsTableElem[];
application: Pick<ApplicationT, "pk">;
}) {
const { i18n } = useTranslation();
const { t } = useTranslation();
Expand All @@ -523,7 +528,9 @@ function ReservationsTable({
const router = useRouter();

const handleCancel = (pk: number) => {
router.push(`/reservations/${pk}/cancel`);
const appPath = getApplicationPath(application.pk, "view");
const url = `${appPath}/${pk}/cancel`;
router.push(url);
};

const cols = [
Expand Down Expand Up @@ -602,11 +609,16 @@ function ReservationsTable({
key: "cancelButton",
headerName: "",
isSortable: false,
transform: ({ pk, isCancellable }: ReservationsTableElem) => (
transform: ({ pk, isCancellableReason }: ReservationsTableElem) => (
<CancelButton
onClick={() => handleCancel(pk)}
disabled={!isCancellable}
// FIXME on mobile this should be hidden behind a popover (for now it's hidden)
disabled={isCancellableReason !== ""}
title={
isCancellableReason === ""
? t("common:cancel")
: t(`reservations:modifyTimeReasons.${isCancellableReason}`)
}
// TODO on mobile this should be hidden behind a popover (for now it's hidden)
className="hide-on-mobile"
>
{t("common:cancel")}
Expand All @@ -622,49 +634,6 @@ function ReservationsTable({
);
}

/// Converts a date to minutes discarding date and seconds
function toMinutes(d: Date): number {
return d.getHours() * 60 + d.getMinutes();
}

/// Creates time and date strings for reservations
/// @param t - translation function
/// @param res - reservation object
/// @param orig - original reservation object (use undefined if not possible to modify)
function toTimeString(
t: TFunction,
reservation: {
begin: string;
end: string;
},
orig?: {
beginTime: string;
endTime: string;
}
): { date: Date; time: string; dayOfWeek: string; isModified: boolean } {
const start = new Date(reservation.begin);
const end = new Date(reservation.end);
const dayOfWeek = t(`weekDayLong.${start.getDay()}`);

const originalBeginMins = orig != null ? timeToMinutes(orig.beginTime) : -1;
const originalEndMins = orig != null ? timeToMinutes(orig.endTime) : -1;

const beginMins = toMinutes(start);
const endMins = toMinutes(end);
const isModified =
orig != null &&
(originalBeginMins !== beginMins || originalEndMins !== endMins);
const btime = formatMinutes(beginMins);
const etime = formatMinutes(endMins);
const time = `${btime} - ${etime}`;
return {
date: start,
time,
dayOfWeek,
isModified,
};
}

function sectionToreservations(
t: TFunction,
section: ApplicationSectionT
Expand All @@ -691,12 +660,12 @@ function sectionToreservations(
): ReservationsTableElem[] {
return r.rejectedOccurrences.map((res) => {
const reservation = { begin: res.beginDatetime, end: res.endDatetime };
const rest = toTimeString(t, reservation);
const rest = formatDateTimeStrings(t, reservation);
return {
...rest,
reservationUnit: r.reservationUnit,
status: "rejected",
isCancellable: false,
isCancellableReason: "ALREADY_CANCELLED",
pk: 0,
};
});
Expand All @@ -706,9 +675,12 @@ function sectionToreservations(
r: (typeof recurringReservations)[0]
): ReservationsTableElem[] {
return r.reservations.map((res) => {
const { isModified, ...rest } = toTimeString(t, res, r.allocatedTimeSlot);
const { isModified, ...rest } = formatDateTimeStrings(
t,
res,
r.allocatedTimeSlot
);

const isCancellable = isReservationCancellable(res);
const status =
res.state === ReservationStateChoice.Cancelled ||
res.state === ReservationStateChoice.Denied
Expand All @@ -720,7 +692,7 @@ function sectionToreservations(
...rest,
reservationUnit: r.reservationUnit,
status,
isCancellable: isCancellable && status !== "rejected",
isCancellableReason: isReservationCancellableReason(res),
pk: res.pk ?? 0,
};
});
Expand Down Expand Up @@ -776,8 +748,10 @@ function sectionToReservationUnits(

export function AllReservations({
applicationSection,
application,
}: {
applicationSection: ApplicationSectionT;
application: Pick<ApplicationT, "pk">;
}) {
const { t } = useTranslation();
const reservations = sectionToreservations(t, applicationSection);
Expand All @@ -786,7 +760,10 @@ export function AllReservations({
<H3 $noMargin>
{t("application:view.reservationsTab.reservationsTitle")}
</H3>
<ReservationsTable reservations={reservations} />
<ReservationsTable
reservations={reservations}
application={application}
/>
</>
);
}
Expand All @@ -812,7 +789,10 @@ export function ApplicationSection({
<H3>{t("application:view.reservationsTab.reservationUnitsTitle")}</H3>
<ReservationUnitTable reservationUnits={reservationUnits} />
<H3>{t("application:view.reservationsTab.reservationsTitle")}</H3>
<ReservationsTable reservations={reservations} />
<ReservationsTable
reservations={reservations}
application={application}
/>
<ButtonContainer $justifyContent="center">
<ButtonLikeLink
href={getApplicationSectionPath(
Expand Down
6 changes: 3 additions & 3 deletions apps/ui/components/application/ViewApplication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import { ApplicantInfoPreview } from "./ApplicantInfoPreview";
import {
ApplicationSection,
ApplicationSectionHeader,
CompactTermsBox,
StyledNotification,
TermsAccordion as Accordion,
} from "./styled";
import { ApplicationEventList } from "./ApplicationEventList";
import Sanitize from "../common/Sanitize";
import TermsBox from "common/src/termsbox/TermsBox";

type Node = NonNullable<ApplicationQuery["application"]>;
export function ViewApplication({
Expand Down Expand Up @@ -54,15 +54,15 @@ export function ViewApplication({
heading={t("reservationUnit:termsOfUse")}
open
>
<CompactTermsBox
<TermsBox
id="preview.acceptTermsOfUse"
body={<Sanitize html={getTranslation(tos, "text")} />}
/>
</Accordion>
)}
{tos2 && (
<Accordion heading={t("application:preview.reservationUnitTerms")} open>
<CompactTermsBox
<TermsBox
id="preview.acceptServiceSpecificTerms"
body={<Sanitize html={getTranslation(tos2, "text")} />}
/* TODO TermsBox has accepted and checkbox we could use but for now leaving the single
Expand Down
5 changes: 0 additions & 5 deletions apps/ui/components/application/styled.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import styled from "styled-components";
import { Notification } from "hds-react";
import TermsBox from "common/src/termsbox/TermsBox";
import { breakpoints, fontMedium, fontRegular } from "common";
import { AccordionWithState } from "@/components/Accordion";
import { H5 } from "common/src/common/typography";
Expand Down Expand Up @@ -168,10 +167,6 @@ export const TermsAccordion = styled(AccordionWithState)`
}
`;

export const CompactTermsBox = styled(TermsBox)`
margin-bottom: 0;
`;

export const SpanTwoColumns = styled(FullRow).attrs({ as: "span" })``;

export const FormSubHeading = styled(H5).attrs({ as: "h2" })`
Expand Down
5 changes: 3 additions & 2 deletions apps/ui/components/reservation/AcceptTerms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from "next-i18next";
import { getTranslation } from "@/modules/util";
import Sanitize from "../common/Sanitize";
import { type ReservationUnitPageQuery } from "@/gql/gql-types";
import { Flex } from "common/styles/util";

type ReservationUnitNodeT = NonNullable<
ReservationUnitPageQuery["reservationUnit"]
Expand Down Expand Up @@ -49,7 +50,7 @@ export function AcceptTerms({
);

return (
<>
<Flex>
<TermsBox
id="cancellation-and-payment-terms"
heading={paymentTermsHeading}
Expand Down Expand Up @@ -90,6 +91,6 @@ export function AcceptTerms({
accepted={isTermsAccepted.space}
setAccepted={(val) => setIsTermsAccepted("space", val)}
/>
</>
</Flex>
);
}
Loading

0 comments on commit 4620d95

Please sign in to comment.