Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions frontend/src/composables/dayjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'dayjs/locale/de';
import {
dayjsKey, durationHumanizedKey, isoWeekdaysKey, tzGuessKey, isoFirstDayOfWeekKey,
} from '@/keys';
import { arrayRotate } from '@/utils';

export type IsoWeekday = {
iso: number,
Expand Down Expand Up @@ -60,9 +61,11 @@ export default function useDayJS(app: App<Element>, locale: string) {
// provide unified list of locale weekdays with Monday=1 to Sunday=7 (isoweekdays)
// taking locale first day of week into account
const isoWeekdays = [] as IsoWeekday[];
// TODO: generate order list for all starting days
const order = isoFirstDayOfWeek === 7 ? [7, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 7];
order.forEach((i) => {
// Generate order list for all starting days
const defaultWeekdays = [1,2,3,4,5,6,7];
const orderedWeekdays = arrayRotate(defaultWeekdays, -defaultWeekdays.indexOf(firstDayOfWeek));

orderedWeekdays.forEach((i) => {
const n = i === 7 ? 0 : i;
isoWeekdays.push({
iso: i,
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,23 @@ export type HTMLInputElementEvent = Event & {
export type CopyTemplate = {
[key:number]: boolean;
}

export type HourPeriod = {
startHour: number,
endHour: number,
}

export type GridElement = {
id: number|string,
gridColumn: number,
gridRowStart: number,
topOffset?: string,
height?: string,
}

export type GridTimeSlot = {
text: string,
startTime: string,
gridRowStart: number,
gridRowEnd: number,
}
79 changes: 77 additions & 2 deletions frontend/src/stores/schedule-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
MeetingLinkProviderType,
} from '@/definitions';
import {
Error, Fetch, Schedule, ScheduleListResponse, ScheduleResponse, Exception, ExceptionDetail,
Error, Fetch, Schedule, ScheduleListResponse, ScheduleResponse, Exception, ExceptionDetail, HourPeriod, Slot,
} from '@/models';
import { dayjsKey } from '@/keys';
import { posthog, usePosthog } from '@/composables/posthog';
import { timeFormat } from '@/utils';
import { arrayRotate, getStartOfWeek, timeFormat } from '@/utils';
import { Dayjs } from 'dayjs';


export const useScheduleStore = defineStore('schedules', () => {
Expand Down Expand Up @@ -225,6 +226,79 @@ export const useScheduleStore = defineStore('schedules', () => {
return data;
};

/**
* Takes the current schedule configuration and generates an array of time slots that are available, meaning they are
* within the time slots the user defined with their availability settings.
* This is the client-side implementation of controller/calendar.py::available_slots_from_schedule
*
* @param date The date object indicating the week to calculate available time slots for
*/
const availableTimeSlots = (date: Dayjs) => {
const s = firstSchedule.value;

if (!s) return [];

// Get general start and end hour from the schedule config
const startHour = parseInt(timeToFrontendTime(s.start_time, s.time_updated).slice(0, 2));
const endHour = parseInt(timeToFrontendTime(s.end_time, s.time_updated).slice(0, 2));

// Normalise availability to a list of weekdays with their general time window or, if set, custom availability
// Format: {[weekday]: [{startHour, endHour}, ...]}
const weeklyTimes = {} as {[key:number]: HourPeriod[]};
s.weekdays.forEach((weekday) => {
if (s.use_custom_availabilities) {
s.availabilities.filter((a) => a.day_of_week === weekday).forEach((a) => {
if (!(weekday in weeklyTimes)) weeklyTimes[weekday] = [];
weeklyTimes[weekday].push({
startHour: parseInt(timeToFrontendTime(a.start_time, s.time_updated).slice(0, 2)),
endHour: parseInt(timeToFrontendTime(a.end_time, s.time_updated).slice(0, 2)),
});
});
} else {
weeklyTimes[weekday] = [{
startHour: startHour,
endHour: endHour,
}];
}
});

// Handle custom start of week setting
const user = useUserStore();
const startOfWeek = user.data.settings.startOfWeek ?? 7;

const d = date.minute(0).second(0).millisecond(0);

const slots = [] as Slot[];

// Iterate over each day of week ordered by users preference
const defaultWeekdays = [1,2,3,4,5,6,7];
const orderedWeekdays = arrayRotate(defaultWeekdays, -defaultWeekdays.indexOf(startOfWeek));

orderedWeekdays.forEach((day, index) => {
// Iterate over each available time slot
weeklyTimes[day]?.forEach((slot) => {
const start = getStartOfWeek(d, startOfWeek).add(index, 'days').hour(slot.startHour);
const end = start.hour(slot.endHour);

// Init date pointer with first time slot start
let pointer = start;

while (pointer.isBefore(end)) {
slots.push({
id: null,
start: pointer,
duration: s.slot_duration,
attendee_id: null,
});

pointer = pointer.add(s.slot_duration, 'minutes');
}
});
});

return slots;
};

/**
* Converts a time (startTime or endTime) to a timezone that the backend expects
* @param {string} time
Expand Down Expand Up @@ -267,6 +341,7 @@ export const useScheduleStore = defineStore('schedules', () => {
createSchedule,
updateSchedule,
updateFirstSlug,
availableTimeSlots,
timeToBackendTime,
timeToFrontendTime,
};
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export const lcFirst = (s: string): string => {
return s[0].toLowerCase() + s.slice(1);
};

// Rotate the elements of an array for <n> steps. Works in both directions (n can be negative).
export const arrayRotate = <T>(a: T[], n: number): T[] => {
a.push(...a.splice(0, (-n % a.length + a.length) % a.length));
return a;
}

// Convert a numeric enum to an object for key-value iteration
export const enumToObject = (e: object): { [key in string]: number } => {
const o = {};
Expand Down Expand Up @@ -480,6 +486,7 @@ export const isUnconfirmed = (a: Appointment): boolean => {

export default {
keyByValue,
arrayRotate,
eventColor,
initials,
download,
Expand Down
Loading
Loading