Skip to content

Commit 8530018

Browse files
committed
further improved user interaction, added disable weekend interactions to show case
1 parent 3e50929 commit 8530018

File tree

12 files changed

+137
-68
lines changed

12 files changed

+137
-68
lines changed

library/src/components/timetable/LPTimeTable.tsx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface TimeTableGroup {
4242

4343
export interface TimeTableEntry<
4444
G extends TimeTableGroup,
45-
I extends TimeSlotBooking
45+
I extends TimeSlotBooking,
4646
> {
4747
group: G
4848
items: I[]
@@ -57,7 +57,7 @@ export interface SelectedTimeSlot<G extends TimeTableGroup> {
5757

5858
export interface LPTimeTableProps<
5959
G extends TimeTableGroup,
60-
I extends TimeSlotBooking
60+
I extends TimeSlotBooking,
6161
> {
6262
/* The start date also defines the time the time slots starts in the morning */
6363
startDate: Dayjs
@@ -80,7 +80,7 @@ export interface LPTimeTableProps<
8080

8181
/* this function gets called when a selection was made, i.g. to create a booking. the return value states if the selection should be cleared or not */
8282
onTimeRangeSelected?: (
83-
s: { group: G; startDate: Dayjs; endDate: Dayjs } | undefined
83+
s: { group: G; startDate: Dayjs; endDate: Dayjs } | undefined,
8484
) => boolean | void
8585

8686
/* The selected time range context sets this callback to be able for a time table parent component to clear the selected time range from outside */
@@ -137,11 +137,11 @@ const nowbarUpdateIntervall = 1000 * 60 // 1 minute
137137

138138
export default function LPTimeTable<
139139
G extends TimeTableGroup,
140-
I extends TimeSlotBooking
140+
I extends TimeSlotBooking,
141141
>({ timeTableMessages, ...props }: LPTimeTableProps<G, I>) {
142142
if (!getCurrentTheme()) {
143143
console.warn(
144-
"LPTimeTable - no theme set, LPTable required Atlassian.design token to have the color scheme set correctly"
144+
"LPTimeTable - no theme set, LPTable required Atlassian.design token to have the color scheme set correctly",
145145
)
146146
}
147147

@@ -205,13 +205,13 @@ const LPTimeTableImpl = <G extends TimeTableGroup, I extends TimeSlotBooking>({
205205
endDate,
206206
timeStepsMinutes,
207207
rounding ?? "round",
208-
setMessage
208+
setMessage,
209209
)
210210
const slotsArray = calculateTimeSlots(
211211
timeSlotsPerDay,
212212
daysDifference,
213213
timeSteps,
214-
startDate
214+
startDate,
215215
)
216216
return { slotsArray, timeSteps, timeSlotsPerDay }
217217
}, [startDate, endDate, timeStepsMinutes, rounding, setMessage])
@@ -227,7 +227,7 @@ const LPTimeTableImpl = <G extends TimeTableGroup, I extends TimeSlotBooking>({
227227
const itemsOutside = itemsOutsideOfDayRange(
228228
entry.items,
229229
slotsArray,
230-
timeSteps
230+
timeSteps,
231231
)
232232
foundItemsOutsideOfDayRange += itemsOutside.length
233233
}
@@ -300,6 +300,7 @@ const LPTimeTableImpl = <G extends TimeTableGroup, I extends TimeSlotBooking>({
300300
timeSteps={timeSteps}
301301
onTimeRangeSelected={onTimeRangeSelected}
302302
setClearSelectedTimeRangeCB={setClearSelectedTimeRangeCB}
303+
disableWeekendInteractions={disableWeekendInteractions}
303304
>
304305
<div
305306
style={{
@@ -383,7 +384,7 @@ function TimeSlotBarRow({
383384
// when the debugging overwrite is active, we still want to move the bar to test it
384385
nowRef.current = nowRef.current.add(
385386
nowbarUpdateIntervall,
386-
"milliseconds"
387+
"milliseconds",
387388
)
388389
} else {
389390
nowRef.current = dayjs()
@@ -396,7 +397,7 @@ function TimeSlotBarRow({
396397
nowBarRef,
397398
tableHeaderRef,
398399
tableBodyRef,
399-
setMessage
400+
setMessage,
400401
)
401402
}, [
402403
slotsArray,
@@ -431,7 +432,7 @@ function TimeSlotBarRow({
431432
messageKey: "timetable.timeSlotColumnsNotFound",
432433
})
433434
console.log(
434-
"LPTimeTable - unable to find time slot columns for the time slot bars"
435+
"LPTimeTable - unable to find time slot columns for the time slot bars",
435436
)
436437
return
437438
}
@@ -499,7 +500,7 @@ function calculateTimeSlotProperties(
499500
endDate: Dayjs,
500501
timeStepsMinute: number,
501502
rounding: "ceil" | "floor" | "round",
502-
setMessage: (message: TimeTableMessage) => void
503+
setMessage: (message: TimeTableMessage) => void,
503504
) {
504505
let timeSlotsPerDay = 0
505506
let timeSteps = timeStepsMinute
@@ -542,7 +543,7 @@ function calculateTimeSlotProperties(
542543
.startOf("day")
543544
.add(startDate.hour(), "hours")
544545
.add(startDate.minute(), "minutes"),
545-
"minutes"
546+
"minutes",
546547
)
547548

548549
if (timeDiff === 0) {
@@ -574,22 +575,22 @@ function calculateTimeSlots(
574575
timeSlotsPerDay: number,
575576
daysDifference: number,
576577
timeSteps: number,
577-
startDate: Dayjs
578+
startDate: Dayjs,
578579
) {
579580
if (!isFinite(timeSlotsPerDay)) {
580581
return null
581582
}
582583
const daysArray = Array.from({ length: daysDifference }, (x, i) => i).map(
583584
(day) => {
584585
return dayjs(startDate).add(day, "days")
585-
}
586+
},
586587
)
587588

588589
const slotsArray = daysArray.flatMap((date) => {
589590
console.log("LPTimeTable - timeSlotsPerDay", timeSlotsPerDay)
590591
return Array.from(
591592
{ length: timeSlotsPerDay },
592-
(_, i) => i * timeSteps
593+
(_, i) => i * timeSteps,
593594
).map((minutes) => {
594595
return dayjs(date).add(minutes, "minutes")
595596
})
@@ -616,7 +617,7 @@ function moveNowBar(
616617
nowBarRef: MutableRefObject<HTMLDivElement | undefined>,
617618
tableHeaderRef: MutableRefObject<HTMLTableSectionElement | null>,
618619
tableBodyRef: MutableRefObject<HTMLTableSectionElement | null>,
619-
setMessage: (message: TimeTableMessage) => void
620+
setMessage: (message: TimeTableMessage) => void,
620621
) {
621622
if (!tableHeaderRef.current || !tableBodyRef.current) {
622623
console.log("LPTimeTable - time table header or body ref not yet set")
@@ -667,7 +668,7 @@ function moveNowBar(
667668
if (!slotBar) {
668669
console.log(
669670
"LPTimeTable - unable to find time slot column for the now bar: ",
670-
startSlot
671+
startSlot,
671672
)
672673
return
673674
}

library/src/components/timetable/Messages.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,6 @@ export const DeselectFromOuterBorder = () => (
7070
/>
7171
)
7272

73-
export const OnlySuccessiveTimeSlots = () => (
74-
<FormattedMessage
75-
defaultMessage={"Please select only successive time slots."}
76-
id="timetable.onlySuccessiveTimeSlots"
77-
/>
78-
)
79-
8073
export const WeekendsDeactivated = () => (
8174
<FormattedMessage
8275
defaultMessage={"Weekends are deactivated."}

library/src/components/timetable/SelectedTimeSlotsContext.tsx

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type InteractionType = "click" | "drag" | "remove"
3939
export function SelectedTimeSlotsProvider<G extends TimeTableGroup>({
4040
slotsArray,
4141
timeSteps,
42+
disableWeekendInteractions,
4243
onTimeRangeSelected,
4344
setClearSelectedTimeRangeCB,
4445
children,
@@ -50,6 +51,7 @@ export function SelectedTimeSlotsProvider<G extends TimeTableGroup>({
5051
) => boolean | void // if return is true, clear selection
5152
// this is a callback that can be used to clear the selected time slots... maybe there is a better way to do this?
5253
setClearSelectedTimeRangeCB?: (cb: () => void) => void
54+
disableWeekendInteractions?: boolean
5355
children: JSX.Element
5456
}) {
5557
const { setMessage } = useTimeTableMessage()
@@ -69,10 +71,10 @@ export function SelectedTimeSlotsProvider<G extends TimeTableGroup>({
6971
// remove any selection in case fundamental time table properties change
7072
useEffect(() => {
7173
console.log(
72-
"LPTimeTable - clearing selection because the slotsArray or timeSteps changed",
74+
"LPTimeTable - clearing selection because the slotsArray, timeSteps or weekend interactions changed",
7375
)
7476
setSelectedTimeSlotsG(undefined)
75-
}, [slotsArray, timeSteps])
77+
}, [slotsArray, timeSteps, disableWeekendInteractions])
7678

7779
// maybe there is a better way to clear the selection from the parent component, then returning a callback from this component
7880
const clearSelectionCB = useCallback(
@@ -98,14 +100,6 @@ export function SelectedTimeSlotsProvider<G extends TimeTableGroup>({
98100
return
99101
}
100102

101-
if (selectedTimeSlots.group !== group && interaction === "click") {
102-
setSelectedTimeSlotsG({
103-
timeSlots: [timeSlot],
104-
group,
105-
})
106-
return
107-
}
108-
109103
const timeSlotBefore = selectedTimeSlots.timeSlots.find(
110104
(it) => timeSlot - 1 === it,
111105
)
@@ -115,12 +109,18 @@ export function SelectedTimeSlotsProvider<G extends TimeTableGroup>({
115109
const alreadySelected =
116110
selectedTimeSlots.timeSlots.includes(timeSlot)
117111

118-
console.log(
119-
"timeSlotBefore",
120-
timeSlotBefore,
121-
timeSlotAfter,
122-
alreadySelected,
123-
)
112+
if (interaction === "click") {
113+
if (
114+
selectedTimeSlots.group !== group ||
115+
(!alreadySelected && !timeSlotAfter && !timeSlotBefore)
116+
) {
117+
setSelectedTimeSlotsG({
118+
timeSlots: [timeSlot],
119+
group,
120+
})
121+
return
122+
}
123+
}
124124

125125
if (alreadySelected) {
126126
if (interaction === "drag") {
@@ -159,23 +159,42 @@ export function SelectedTimeSlotsProvider<G extends TimeTableGroup>({
159159
return
160160
}
161161

162-
// not selected yet
163-
if (timeSlotBefore !== undefined || timeSlotAfter !== undefined) {
162+
if (interaction === "drag" && multiselectionMode) {
163+
// selectedTimeSlots.timeSlots is already sorted
164+
let min = selectedTimeSlots.timeSlots[0]
165+
let max =
166+
selectedTimeSlots.timeSlots[
167+
selectedTimeSlots.timeSlots.length - 1
168+
]
169+
if (timeSlot < min) {
170+
min = timeSlot
171+
} else if (timeSlot > max) {
172+
max = timeSlot
173+
}
174+
const newTimeSlots = []
175+
for (let i = min; i <= max; i++) {
176+
newTimeSlots.push(i)
177+
}
178+
setSelectedTimeSlotsG({
179+
timeSlots: newTimeSlots,
180+
group: selectedTimeSlots.group,
181+
})
182+
} else if (
183+
timeSlotAfter !== undefined ||
184+
timeSlotBefore !== undefined
185+
) {
164186
setSelectedTimeSlotsG({
165187
timeSlots: [...selectedTimeSlots.timeSlots, timeSlot],
166188
group: selectedTimeSlots.group,
167189
})
168-
return
190+
} else {
191+
setSelectedTimeSlotsG({
192+
timeSlots: [timeSlot],
193+
group: selectedTimeSlots.group,
194+
})
169195
}
170-
171-
// that means this is not selected, but there are other selected time slots of this group, but not directly before of after
172-
setMessage({
173-
urgency: "information",
174-
messageKey: "timetable.onlySuccessiveTimeSlots",
175-
timeOut: 3,
176-
})
177196
},
178-
[selectedTimeSlots, setMessage],
197+
[multiselectionMode, selectedTimeSlots, setMessage],
179198
)
180199

181200
useEffect(() => {

library/src/components/timetable/TimeTableMessageContext.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ export function TimeTableMessageProvider({
5353
}) {
5454
const [message, setMessage] = useState<TimeTableMessage>()
5555

56-
useEffect(() => {
57-
console.log("messagesTranslations", messagesTranslations)
58-
}, [messagesTranslations])
59-
6056
return (
6157
<timeTableMessageContext.Provider
6258
value={{ message, setMessage, messagesTranslations }}

library/src/localization/translations-compiled/de.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"timetable.endDateAfterStartDate": "Das Enddatum muss nach dem Startdatum liegen.",
55
"timetable.itemsOutsideTimeFrame": "Es sind {outsideItemCount} Einträge außerhalb des Zeitbereichs eines Tages.",
66
"timetable.noHeaderTimeSlotRow": "Keine Kopfzeile für Zeitslots gefunden.",
7-
"timetable.onlySuccessiveTimeSlots": "Bitte wählen Sie nur aufeinanderfolgende Zeitslots aus.",
87
"timetable.timeSlotColumnsNotFound": "Keine Spalten für die Zeitslots gefunden.",
98
"timetable.timeSlotSizeGreaterZero": "Die Zeitslotgröße muss größer als 0 Minuten sein.",
109
"timetable.unableToFindEarliestTS": "Der früheste Zeitslot konnte nicht gefunden werden.",

library/src/localization/translations-compiled/en.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"timetable.endDateAfterStartDate": "The end date must be after start date.",
55
"timetable.itemsOutsideTimeFrame": "Found {outsideItemCount} outside of the valid time frame of each day.",
66
"timetable.noHeaderTimeSlotRow": "No header time slot row found.",
7-
"timetable.onlySuccessiveTimeSlots": "Please select only successive time slots.",
87
"timetable.timeSlotColumnsNotFound": "Unable to find time slot columns for the time slot bars.",
98
"timetable.timeSlotSizeGreaterZero": "Time slot size must be greater than zero.",
109
"timetable.unableToFindEarliestTS": "Unable to find the earliest time slot.",

library/src/localization/translations/de.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
"timetable.noHeaderTimeSlotRow": {
1515
"defaultMessage": "Keine Kopfzeile für Zeitslots gefunden."
1616
},
17-
"timetable.onlySuccessiveTimeSlots": {
18-
"defaultMessage": "Bitte wählen Sie nur aufeinanderfolgende Zeitslots aus."
19-
},
2017
"timetable.timeSlotColumnsNotFound": {
2118
"defaultMessage": "Keine Spalten für die Zeitslots gefunden."
2219
},

library/src/localization/translations/en.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
"timetable.noHeaderTimeSlotRow": {
1515
"defaultMessage": "No header time slot row found."
1616
},
17-
"timetable.onlySuccessiveTimeSlots": {
18-
"defaultMessage": "Please select only successive time slots."
19-
},
2017
"timetable.timeSlotColumnsNotFound": {
2118
"defaultMessage": "Unable to find time slot columns for the time slot bars."
2219
},

0 commit comments

Comments
 (0)