@@ -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";
4645import { LinkLikeButton } from "common/styles/buttonCss" ;
4746import { type TFunction } from "i18next" ;
4847import { convertWeekday } from "common/src/conversion" ;
49- import { formatTime } from "@/modules/util" ;
5048import { ButtonLikeLink } from "../common/ButtonLikeLink" ;
5149import { AccordionWithIcons } from "../AccordionWithIcons" ;
5250import { 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.
8583const 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({
514512const 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+
639682function 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 } }
0 commit comments