Skip to content

Commit

Permalink
fix: seasonal approved reservations tab
Browse files Browse the repository at this point in the history
  • Loading branch information
joonatank committed Nov 27, 2024
1 parent 0040802 commit fe0ab47
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 68 deletions.
15 changes: 7 additions & 8 deletions apps/ui/components/AccordionWithIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import { Button, IconAngleDown, IconAngleUp, useAccordion } from "hds-react";
import { useTranslation } from "next-i18next";
import styled from "styled-components";

// There is only one use case for this so not testing other cases (or making it flexible)
const N_ICONS = 3;

type Props = {
heading: string;
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
Expand All @@ -19,13 +16,14 @@ type Props = {
};

const Heading = styled.h2<{ as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" }>`
grid-column: 1 / -2;
padding: 0;
margin: 0;
`;

const ClosedAccordionWrapper = styled.div`
display: grid;
grid-template-columns: 1fr auto auto;
grid-template-columns: repeat(4, 1fr);
align-items: center;
gap: var(--spacing-s);
Expand All @@ -35,22 +33,23 @@ const ClosedAccordionWrapper = styled.div`

const IconListWrapper = styled.div`
display: grid;
gap: var(--spacing-s);
gap: var(--spacing-xs);
grid-row: subgrid;
grid-column: 1 / -1;
grid-template-columns: auto;
@media (width > ${breakpoints.m}) {
grid-column: unset;
grid-template-columns: repeat(${N_ICONS}, auto);
gap: var(--spacing-s);
grid-template-columns: auto 1fr 2fr;
}
`;

const ButtonListWrapper = styled.div`
display: block;
display: grid;
gap: var(--spacing-s);
align-self: center;
align-items: end;
justify-content: end;
@media (width > ${breakpoints.s}) {
grid-column-start: -1;
Expand Down
133 changes: 84 additions & 49 deletions apps/ui/components/application/ApprovedReservations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
IconCalendarRecurring,
IconClock,
IconCross,
IconEuroSign,
IconInfoCircle,
IconLinkExternal,
IconLocation,
Expand All @@ -46,7 +45,6 @@ import Sanitize from "../common/Sanitize";
import { LinkLikeButton } from "common/styles/buttonCss";
import { type TFunction } from "i18next";
import { convertWeekday } from "common/src/conversion";
import { formatTime } from "@/modules/util";
import { ButtonLikeLink } from "../common/ButtonLikeLink";
import { AccordionWithIcons } from "../AccordionWithIcons";
import { CenterSpinner } from "../common/common";
Expand Down Expand Up @@ -83,8 +81,14 @@ const ListContainer = styled.div`
// Tables can't do horizontal scroll without wrapping the table in a div
// NOTE HDS Table can't be styled so have to wrap it in an extra div.
const TableWrapper = styled.div`
/* TODO move this to a more general TableWrapper shared with admin-ui */
/* Mobile uses cards, so no horizontal scroll */
@media (width > ${BREAKPOINT}) {
/* NOTE this requires using buttons (or other elements with padding) on every row */
& tbody > tr > td {
padding-top: 0;
padding-bottom: 0;
}
& > div {
overflow-x: auto;
> table {
Expand Down Expand Up @@ -248,13 +252,22 @@ export function ApprovedReservations({ application }: Props) {

const lang = getLocalizationLang(i18n.language);

const sections = filterNonNullable(
app?.applicationSections?.filter((aes) => {
const slots = aes.reservationUnitOptions.flatMap(
(x) => x.allocatedTimeSlots
);
return slots.length > 0;
})
);
const initiallyOpen = app?.applicationSections?.length === 1;
return (
<ListContainer>
{loading && <CenterSpinner />}
{app?.applicationSections?.map((aes) => (
{sections.map((aes) => (
<AccordionWithIcons
heading={aes.name}
initiallyOpen={app.applicationSections?.length === 1}
initiallyOpen={initiallyOpen}
headingLevel={2}
icons={[
{
Expand Down Expand Up @@ -326,7 +339,6 @@ type ReservationUnitTableElem = {
| "nameEn"
>;
dateOfWeek: string;
price: string;
// same for this actual end / start times or a combined string
time: string;
};
Expand Down Expand Up @@ -377,28 +389,14 @@ function ReservationUnitTable({
</IconTextWrapper>
),
},
{
key: "price",
headerName: t("common:price"),
isSortable: false,
transform: ({ price }: ReservationUnitTableElem) =>
price ? (
<IconTextWrapper aria-label={t("common:price")}>
<IconEuroSign aria-hidden="true" />
{price}
</IconTextWrapper>
) : (
""
),
},
{
key: "helpLink",
headerName: t("application:view.helpModal.title"),
transform: ({ reservationUnit }: ReservationUnitTableElem) => (
<LinkLikeButton
onClick={() => setModal(reservationUnit)}
// table already includes padding
style={{ padding: 0 }}
// Match the size of a small button
style={{ minHeight: "44px" }}
>
<IconInfoCircle aria-hidden="true" />
{isMobile
Expand Down Expand Up @@ -514,6 +512,7 @@ function createReservationUnitLink({
const CancelButton = styled(Button).attrs({
theme: "black",
variant: "supplementary",
size: "small",
iconLeft: <IconCross aria-hidden="true" />,
})`
white-space: nowrap;
Expand Down Expand Up @@ -617,9 +616,10 @@ function ReservationsTable({
key: "cancelButton",
headerName: "",
isSortable: false,
transform: ({ pk }: ReservationsTableElem) => (
transform: ({ pk, status }: ReservationsTableElem) => (
<CancelButton
onClick={() => handleCancel(pk)}
disabled={status === "rejected"}
// TODO on mobile this should be hidden behind a popover (for now it's hidden)
className="hide-on-mobile"
>
Expand All @@ -636,29 +636,78 @@ 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
): ReservationsTableElem[] {
const recurringReservations = filterNonNullable(
section.reservationUnitOptions.flatMap((ruo) =>
ruo.allocatedTimeSlots.map((ats) => ats.recurringReservation)
ruo.allocatedTimeSlots.map((ats) =>
ats.recurringReservation != null
? {
...ats.recurringReservation,
allocatedTimeSlot: {
dayOfTheWeek: ats.dayOfTheWeek,
beginTime: ats.beginTime,
endTime: ats.endTime,
},
}
: null
)
)
);

function getRejected(
r: (typeof recurringReservations)[0]
): ReservationsTableElem[] {
return r.rejectedOccurrences.map((res) => {
const start = new Date(res.beginDatetime);
const end = new Date(res.endDatetime);
const dayOfWeek = t(`weekDayLong.${start.getDay()}`);
const stime = formatTime(t, start);
const etime = formatTime(t, end);
const time = `${stime} - ${etime}`;
const reservation = { begin: res.beginDatetime, end: res.endDatetime };
const rest = toTimeString(t, reservation);
return {
date: start,
dayOfWeek,
time,
...rest,
reservationUnit: r.reservationUnit,
status: "rejected",
pk: 0,
Expand All @@ -670,17 +719,7 @@ function sectionToreservations(
r: (typeof recurringReservations)[0]
): ReservationsTableElem[] {
return r.reservations.map((res) => {
const start = new Date(res.begin);
const end = new Date(res.end);
const dayOfWeek = t(`weekDayLong.${start.getDay()}`);

const beginMins = timeToMinutes(r.beginTime ?? "");
const endMins = timeToMinutes(r.endTime ?? "");
const beginMins2 = start.getHours() * 60 + start.getMinutes();
const endMins2 = end.getHours() * 60 + end.getMinutes();
const isModified = beginMins !== beginMins2 || endMins !== endMins2;
const btime = formatMinutes(beginMins2);
const etime = formatMinutes(endMins2);
const { isModified, ...rest } = toTimeString(t, res, r.allocatedTimeSlot);

const status =
res.state === ReservationStateChoice.Denied
Expand All @@ -689,9 +728,7 @@ function sectionToreservations(
? "modified"
: "";
return {
date: start,
dayOfWeek,
time: `${btime} - ${etime}`,
...rest,
reservationUnit: r.reservationUnit,
status,
pk: res.pk ?? 0,
Expand Down Expand Up @@ -742,8 +779,6 @@ function sectionToReservationUnits(
reservationUnit,
// NOTE our translations are sunday first
dateOfWeek: t(`weekDayLong.${fromMondayFirst(day)}`),
// Pricing is not implemented. So all are empty
price: "",
time,
};
});
Expand Down Expand Up @@ -801,7 +836,7 @@ export function ApplicationSection({
<Button
variant="secondary"
theme="black"
key="cancel"
size="small"
onClick={() => {
errorToast({ text: "Not implemented: cancel application" });
}}
Expand Down
8 changes: 4 additions & 4 deletions apps/ui/public/locales/en/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
},
"view": {
"heading": "Seasonal booking application",
"reservations": "Accepted bookings",
"reservations": "Confirmed bookings",
"application": "Application",
"handledDate": "Handled",
"allReservations": "All bookings",
Expand All @@ -224,14 +224,14 @@
"rejected": "Rejected",
"modified": "Modified",
"showAllReservations": "Show all bookings",
"cancelApplication": "Cancel whole application",
"cancelApplication": "Cancel all bookings",
"reservationUnitsTitle": "Location",
"reservationsTitle": "Reservations",
"others": "others",
"reservationCountPostfix": "bookings"
},
"helpLink": "Help",
"helpLinkLong": "Unit specific help",
"helpLink": "Instructions",
"helpLinkLong": "Space Instructions",
"helpModal": {
"title": "Unit specific information"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/ui/public/locales/fi/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@
"rejected": "Hylätty",
"modified": "Muokattu",
"showAllReservations": "Näytä kaikki varaukset",
"cancelApplication": "Peru koko kausivaraus",
"reservationUnitsTitle": "Toimipisteet",
"cancelApplication": "Peru kaikki varaukset",
"reservationUnitsTitle": "Toimipiste",
"reservationsTitle": "Varaukset",
"others": "muuta",
"reservationCountPostfix": "varausta"
Expand Down
8 changes: 4 additions & 4 deletions apps/ui/public/locales/sv/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,20 +214,20 @@
"handledDate": "Behandlad",
"allReservations": "Alla bokningar",
"reservationsTab": {
"reservationUnit": "Lokal",
"reservationUnit": "Utrymme",
"rejected": "Avslagen",
"modified": "Ändrad",
"showAllReservations": "Visa alla bokningar",
"cancelApplication": "Avbryt ansökan",
"cancelApplication": "Avboka alla bokningar",
"reservationUnitsTitle": "Verksamhetsställe",
"reservationsTitle": "Bokningar",
"others": "andra",
"reservationCountPostfix": "bokningar"
},
"helpLink": "Hjälp",
"helpLink": "Instruktioner",
"helpLinkLong": "Hjälp för ansökan",
"helpModal": {
"title": "Lokal specifik information"
"title": "Lokalens instruktioner"
}
},
"sent": {
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/public/locales/sv/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"login": "Logga in",
"loginAlt": "Logga in",
"logout": "Logga ut",
"cancel": "Avbryt",
"cancel": "Avboka",
"imgAltForSpace": "Bild på lokalen {{name}}",
"address": {
"streetAddress": "Adress",
Expand Down

0 comments on commit fe0ab47

Please sign in to comment.