Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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