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

Commit fe0ab47

Browse files
committed
fix: seasonal approved reservations tab
1 parent 0040802 commit fe0ab47

6 files changed

Lines changed: 102 additions & 68 deletions

File tree

apps/ui/components/AccordionWithIcons.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import { Button, IconAngleDown, IconAngleUp, useAccordion } from "hds-react";
33
import { useTranslation } from "next-i18next";
44
import styled from "styled-components";
55

6-
// There is only one use case for this so not testing other cases (or making it flexible)
7-
const N_ICONS = 3;
8-
96
type Props = {
107
heading: string;
118
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
@@ -19,13 +16,14 @@ type Props = {
1916
};
2017

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

2624
const ClosedAccordionWrapper = styled.div`
2725
display: grid;
28-
grid-template-columns: 1fr auto auto;
26+
grid-template-columns: repeat(4, 1fr);
2927
align-items: center;
3028
gap: var(--spacing-s);
3129
@@ -35,22 +33,23 @@ const ClosedAccordionWrapper = styled.div`
3533

3634
const IconListWrapper = styled.div`
3735
display: grid;
38-
gap: var(--spacing-s);
36+
gap: var(--spacing-xs);
3937
4038
grid-row: subgrid;
4139
grid-column: 1 / -1;
4240
grid-template-columns: auto;
4341
@media (width > ${breakpoints.m}) {
44-
grid-column: unset;
45-
grid-template-columns: repeat(${N_ICONS}, auto);
42+
gap: var(--spacing-s);
43+
grid-template-columns: auto 1fr 2fr;
4644
}
4745
`;
4846

4947
const ButtonListWrapper = styled.div`
50-
display: block;
48+
display: grid;
5149
gap: var(--spacing-s);
5250
align-self: center;
5351
align-items: end;
52+
justify-content: end;
5453
5554
@media (width > ${breakpoints.s}) {
5655
grid-column-start: -1;

apps/ui/components/application/ApprovedReservations.tsx

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
IconCalendarRecurring,
3333
IconClock,
3434
IconCross,
35-
IconEuroSign,
3635
IconInfoCircle,
3736
IconLinkExternal,
3837
IconLocation,
@@ -46,7 +45,6 @@ import Sanitize from "../common/Sanitize";
4645
import { LinkLikeButton } from "common/styles/buttonCss";
4746
import { type TFunction } from "i18next";
4847
import { convertWeekday } from "common/src/conversion";
49-
import { formatTime } from "@/modules/util";
5048
import { ButtonLikeLink } from "../common/ButtonLikeLink";
5149
import { AccordionWithIcons } from "../AccordionWithIcons";
5250
import { CenterSpinner } from "../common/common";
@@ -83,8 +81,14 @@ const ListContainer = styled.div`
8381
// Tables can't do horizontal scroll without wrapping the table in a div
8482
// NOTE HDS Table can't be styled so have to wrap it in an extra div.
8583
const TableWrapper = styled.div`
84+
/* TODO move this to a more general TableWrapper shared with admin-ui */
8685
/* Mobile uses cards, so no horizontal scroll */
8786
@media (width > ${BREAKPOINT}) {
87+
/* NOTE this requires using buttons (or other elements with padding) on every row */
88+
& tbody > tr > td {
89+
padding-top: 0;
90+
padding-bottom: 0;
91+
}
8892
& > div {
8993
overflow-x: auto;
9094
> table {
@@ -248,13 +252,22 @@ export function ApprovedReservations({ application }: Props) {
248252

249253
const lang = getLocalizationLang(i18n.language);
250254

255+
const sections = filterNonNullable(
256+
app?.applicationSections?.filter((aes) => {
257+
const slots = aes.reservationUnitOptions.flatMap(
258+
(x) => x.allocatedTimeSlots
259+
);
260+
return slots.length > 0;
261+
})
262+
);
263+
const initiallyOpen = app?.applicationSections?.length === 1;
251264
return (
252265
<ListContainer>
253266
{loading && <CenterSpinner />}
254-
{app?.applicationSections?.map((aes) => (
267+
{sections.map((aes) => (
255268
<AccordionWithIcons
256269
heading={aes.name}
257-
initiallyOpen={app.applicationSections?.length === 1}
270+
initiallyOpen={initiallyOpen}
258271
headingLevel={2}
259272
icons={[
260273
{
@@ -326,7 +339,6 @@ type ReservationUnitTableElem = {
326339
| "nameEn"
327340
>;
328341
dateOfWeek: string;
329-
price: string;
330342
// same for this actual end / start times or a combined string
331343
time: string;
332344
};
@@ -377,28 +389,14 @@ function ReservationUnitTable({
377389
</IconTextWrapper>
378390
),
379391
},
380-
{
381-
key: "price",
382-
headerName: t("common:price"),
383-
isSortable: false,
384-
transform: ({ price }: ReservationUnitTableElem) =>
385-
price ? (
386-
<IconTextWrapper aria-label={t("common:price")}>
387-
<IconEuroSign aria-hidden="true" />
388-
{price}
389-
</IconTextWrapper>
390-
) : (
391-
""
392-
),
393-
},
394392
{
395393
key: "helpLink",
396394
headerName: t("application:view.helpModal.title"),
397395
transform: ({ reservationUnit }: ReservationUnitTableElem) => (
398396
<LinkLikeButton
399397
onClick={() => setModal(reservationUnit)}
400-
// table already includes padding
401-
style={{ padding: 0 }}
398+
// Match the size of a small button
399+
style={{ minHeight: "44px" }}
402400
>
403401
<IconInfoCircle aria-hidden="true" />
404402
{isMobile
@@ -514,6 +512,7 @@ function createReservationUnitLink({
514512
const CancelButton = styled(Button).attrs({
515513
theme: "black",
516514
variant: "supplementary",
515+
size: "small",
517516
iconLeft: <IconCross aria-hidden="true" />,
518517
})`
519518
white-space: nowrap;
@@ -617,9 +616,10 @@ function ReservationsTable({
617616
key: "cancelButton",
618617
headerName: "",
619618
isSortable: false,
620-
transform: ({ pk }: ReservationsTableElem) => (
619+
transform: ({ pk, status }: ReservationsTableElem) => (
621620
<CancelButton
622621
onClick={() => handleCancel(pk)}
622+
disabled={status === "rejected"}
623623
// TODO on mobile this should be hidden behind a popover (for now it's hidden)
624624
className="hide-on-mobile"
625625
>
@@ -636,29 +636,78 @@ function ReservationsTable({
636636
);
637637
}
638638

639+
/// Converts a date to minutes discarding date and seconds
640+
function toMinutes(d: Date): number {
641+
return d.getHours() * 60 + d.getMinutes();
642+
}
643+
644+
/// Creates time and date strings for reservations
645+
/// @param t - translation function
646+
/// @param res - reservation object
647+
/// @param orig - original reservation object (use undefined if not possible to modify)
648+
function toTimeString(
649+
t: TFunction,
650+
reservation: {
651+
begin: string;
652+
end: string;
653+
},
654+
orig?: {
655+
beginTime: string;
656+
endTime: string;
657+
}
658+
): { date: Date; time: string; dayOfWeek: string; isModified: boolean } {
659+
const start = new Date(reservation.begin);
660+
const end = new Date(reservation.end);
661+
const dayOfWeek = t(`weekDayLong.${start.getDay()}`);
662+
663+
const originalBeginMins = orig != null ? timeToMinutes(orig.beginTime) : -1;
664+
const originalEndMins = orig != null ? timeToMinutes(orig.endTime) : -1;
665+
666+
const beginMins = toMinutes(start);
667+
const endMins = toMinutes(end);
668+
const isModified =
669+
orig != null &&
670+
(originalBeginMins !== beginMins || originalEndMins !== endMins);
671+
const btime = formatMinutes(beginMins);
672+
const etime = formatMinutes(endMins);
673+
const time = `${btime} - ${etime}`;
674+
return {
675+
date: start,
676+
time,
677+
dayOfWeek,
678+
isModified,
679+
};
680+
}
681+
639682
function sectionToreservations(
640683
t: TFunction,
641684
section: ApplicationSectionT
642685
): ReservationsTableElem[] {
643686
const recurringReservations = filterNonNullable(
644687
section.reservationUnitOptions.flatMap((ruo) =>
645-
ruo.allocatedTimeSlots.map((ats) => ats.recurringReservation)
688+
ruo.allocatedTimeSlots.map((ats) =>
689+
ats.recurringReservation != null
690+
? {
691+
...ats.recurringReservation,
692+
allocatedTimeSlot: {
693+
dayOfTheWeek: ats.dayOfTheWeek,
694+
beginTime: ats.beginTime,
695+
endTime: ats.endTime,
696+
},
697+
}
698+
: null
699+
)
646700
)
647701
);
702+
648703
function getRejected(
649704
r: (typeof recurringReservations)[0]
650705
): ReservationsTableElem[] {
651706
return r.rejectedOccurrences.map((res) => {
652-
const start = new Date(res.beginDatetime);
653-
const end = new Date(res.endDatetime);
654-
const dayOfWeek = t(`weekDayLong.${start.getDay()}`);
655-
const stime = formatTime(t, start);
656-
const etime = formatTime(t, end);
657-
const time = `${stime} - ${etime}`;
707+
const reservation = { begin: res.beginDatetime, end: res.endDatetime };
708+
const rest = toTimeString(t, reservation);
658709
return {
659-
date: start,
660-
dayOfWeek,
661-
time,
710+
...rest,
662711
reservationUnit: r.reservationUnit,
663712
status: "rejected",
664713
pk: 0,
@@ -670,17 +719,7 @@ function sectionToreservations(
670719
r: (typeof recurringReservations)[0]
671720
): ReservationsTableElem[] {
672721
return r.reservations.map((res) => {
673-
const start = new Date(res.begin);
674-
const end = new Date(res.end);
675-
const dayOfWeek = t(`weekDayLong.${start.getDay()}`);
676-
677-
const beginMins = timeToMinutes(r.beginTime ?? "");
678-
const endMins = timeToMinutes(r.endTime ?? "");
679-
const beginMins2 = start.getHours() * 60 + start.getMinutes();
680-
const endMins2 = end.getHours() * 60 + end.getMinutes();
681-
const isModified = beginMins !== beginMins2 || endMins !== endMins2;
682-
const btime = formatMinutes(beginMins2);
683-
const etime = formatMinutes(endMins2);
722+
const { isModified, ...rest } = toTimeString(t, res, r.allocatedTimeSlot);
684723

685724
const status =
686725
res.state === ReservationStateChoice.Denied
@@ -689,9 +728,7 @@ function sectionToreservations(
689728
? "modified"
690729
: "";
691730
return {
692-
date: start,
693-
dayOfWeek,
694-
time: `${btime} - ${etime}`,
731+
...rest,
695732
reservationUnit: r.reservationUnit,
696733
status,
697734
pk: res.pk ?? 0,
@@ -742,8 +779,6 @@ function sectionToReservationUnits(
742779
reservationUnit,
743780
// NOTE our translations are sunday first
744781
dateOfWeek: t(`weekDayLong.${fromMondayFirst(day)}`),
745-
// Pricing is not implemented. So all are empty
746-
price: "",
747782
time,
748783
};
749784
});
@@ -801,7 +836,7 @@ export function ApplicationSection({
801836
<Button
802837
variant="secondary"
803838
theme="black"
804-
key="cancel"
839+
size="small"
805840
onClick={() => {
806841
errorToast({ text: "Not implemented: cancel application" });
807842
}}

apps/ui/public/locales/en/application.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@
215215
},
216216
"view": {
217217
"heading": "Seasonal booking application",
218-
"reservations": "Accepted bookings",
218+
"reservations": "Confirmed bookings",
219219
"application": "Application",
220220
"handledDate": "Handled",
221221
"allReservations": "All bookings",
@@ -224,14 +224,14 @@
224224
"rejected": "Rejected",
225225
"modified": "Modified",
226226
"showAllReservations": "Show all bookings",
227-
"cancelApplication": "Cancel whole application",
227+
"cancelApplication": "Cancel all bookings",
228228
"reservationUnitsTitle": "Location",
229229
"reservationsTitle": "Reservations",
230230
"others": "others",
231231
"reservationCountPostfix": "bookings"
232232
},
233-
"helpLink": "Help",
234-
"helpLinkLong": "Unit specific help",
233+
"helpLink": "Instructions",
234+
"helpLinkLong": "Space Instructions",
235235
"helpModal": {
236236
"title": "Unit specific information"
237237
}

apps/ui/public/locales/fi/application.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@
220220
"rejected": "Hylätty",
221221
"modified": "Muokattu",
222222
"showAllReservations": "Näytä kaikki varaukset",
223-
"cancelApplication": "Peru koko kausivaraus",
224-
"reservationUnitsTitle": "Toimipisteet",
223+
"cancelApplication": "Peru kaikki varaukset",
224+
"reservationUnitsTitle": "Toimipiste",
225225
"reservationsTitle": "Varaukset",
226226
"others": "muuta",
227227
"reservationCountPostfix": "varausta"

apps/ui/public/locales/sv/application.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,20 +214,20 @@
214214
"handledDate": "Behandlad",
215215
"allReservations": "Alla bokningar",
216216
"reservationsTab": {
217-
"reservationUnit": "Lokal",
217+
"reservationUnit": "Utrymme",
218218
"rejected": "Avslagen",
219219
"modified": "Ändrad",
220220
"showAllReservations": "Visa alla bokningar",
221-
"cancelApplication": "Avbryt ansökan",
221+
"cancelApplication": "Avboka alla bokningar",
222222
"reservationUnitsTitle": "Verksamhetsställe",
223223
"reservationsTitle": "Bokningar",
224224
"others": "andra",
225225
"reservationCountPostfix": "bokningar"
226226
},
227-
"helpLink": "Hjälp",
227+
"helpLink": "Instruktioner",
228228
"helpLinkLong": "Hjälp för ansökan",
229229
"helpModal": {
230-
"title": "Lokal specifik information"
230+
"title": "Lokalens instruktioner"
231231
}
232232
},
233233
"sent": {

apps/ui/public/locales/sv/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"login": "Logga in",
7575
"loginAlt": "Logga in",
7676
"logout": "Logga ut",
77-
"cancel": "Avbryt",
77+
"cancel": "Avboka",
7878
"imgAltForSpace": "Bild på lokalen {{name}}",
7979
"address": {
8080
"streetAddress": "Adress",

0 commit comments

Comments
 (0)