Sixtens hemsida Uppgifter Blogg Om

VED House bokningssystem

Gå till sida

Källkod

api/

database.php

api.php

ved/

monthBookingData.php

openingHoursException.php

bookingData.php

dayTimeframes.php

bookings.php

admin/

revokeToken.php

authorization.php

login.php

modules/

vedDatabase.php

utility.php

gyar/

log.php

auth/

revokeToken.php

authorization.php

login.php

station/

settings.php

log.php

modules/

gyarDatabase.php

databaseConnection.php

database.php

utility.php

credentials.json

weather/

latest.php

old.php

admin/

index.html

changeTimetable.html

login.html

js/

login.js

modules.js

admin.js

header.js

changeTimetable.js

cron/

vedClearExpiredTokens.php

weuweb01/ved/admin/js/admin.js

8 lines
// Classes

import { vedAPI as api, Cookies, getAuthHeader, httpErrorMessage } from "./modules.js";

class Calendar {
    static weekdays = ["Mån", "Tis", "Ons", "Tors", "Fre", "Lör", "Sön"];
    static months = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];

    #shouldButtonBeDisabledCallback
    #monthChangedCallback
    constructor(element) {
        let header = element.querySelector(".calendar-header");
        if (!header) throw new Error("Calendar needs a calendar-header.");
        let buttons = header.querySelectorAll("button");
        if (buttons.length != 2) throw new Error("Calendar header needs two buttons.");
        this.monthHeader = header.querySelector("p");
        if (!this.monthHeader) throw new Error("Calendar header is missing a paragraph element.");

        this.body = element.querySelector(".calendar-body");
        if (!this.body) throw new Error("Calendar needs a calendar-body.");

        this.previousButton = buttons[0];
        this.nextButton = buttons[1];
        this.dateButtons = new Map()
        this.markedButton = undefined;
        this.markedDate = -1;

        this.date = new Date();
        this.date.setUTCHours(0, 0, 0, 0);
        this.dateTimestamp = this.date.getTime();
        this.date.setUTCDate(1);
        this.monthTimestamp = this.date.getTime();

        this.previousButton.addEventListener("click", () => this.changeMonth(-1));
        this.nextButton.addEventListener("click", () => this.changeMonth(1));

        this.monthChangedCallback = undefined;
        this.dateSelectedCallback = undefined;

        Calendar.weekdays.forEach(day => CreateElement("p", day, this.body));
        this.updateAppearance();
    }

    async changeMonth(delta) {
        let old = this.date.getTime();
        this.date.setUTCMonth(this.date.getUTCMonth() + delta);
        if (this.date.getTime() < this.monthTimestamp) {
            this.date.setTime(old);
            return;
        }
        if (this.monthChangedCallback) {
            let result = await this.monthChangedCallback(this.date.getUTCFullYear(), this.date.getUTCMonth());
            if (!result) {
                this.date.setTime(old);
                return;
            }
        }
        this.updateAppearance();
    }

    async updateAppearance() {
        this.dateButtons.forEach((date, button) => button.remove());
        this.dateButtons.clear();

        let date = new Date(this.date);
        date.setUTCHours(0, 0, 0, 0);
        date.setUTCDate(1);

        let weekdayOffset = ConvertWeekday(date.getUTCDay());
        let timestamp = date.getTime();

        this.monthHeader.innerText = Calendar.months[date.getUTCMonth()] + " " + date.getUTCFullYear();

        let i = 1;
        while (true) {
            date.setTime(timestamp + (i - weekdayOffset - 1) * 86400000);
            let dateValue = date.getUTCDate();
            if (i >= 28 & dateValue <= 7 & i % 7 == 1) break;

            let button = CreateElement("button", dateValue, this.body);
            if ((i <= 7 && dateValue > 15) || (i > 28 && dateValue <= 15)) button.classList.add("unfocused");

            let isClickable = true;
            if (this.shouldButtonBeDisabledCallback) {
                let retValue = this.shouldButtonBeDisabledCallback(date);
                if (retValue != undefined) {
                    let [disabled, unfocused] = retValue;
                    if (disabled) {
                        button.classList.add("disabled");
                        button.setAttribute("tabindex", -1);
                        isClickable = false;
                    }
                    if (unfocused)
                        button.classList.add("unfocused");
                }
            }
            this.dateButtons.set(button, date.getTime());
            if (isClickable) {
                let mark = async () => {
                    let newDate = this.dateButtons.get(button);
                    if (this.dateSelectedCallback) {
                        let result = await this.dateSelectedCallback(newDate);
                        if (!result)
                            return;
                    }

                    if (this.markedButton) this.markedButton.classList.remove("selected");
                    button.classList.add("selected");
                    this.markedButton = button;
                    this.markedDate = newDate;
                };

                button.addEventListener("click", mark);
                if (this.markedDate == date.getTime()) await mark();
            }
            i++;
        }
        this.body.style["grid-template-columns"] = "1fr 1fr 1fr 1fr 1fr 1fr 1fr";
        this.body.style["grid-template-rows"] = "1fr" + " 2fr".repeat(i / 7);
    }

    get shouldButtonBeDisabledCallback() {
        return this.#shouldButtonBeDisabledCallback;
    }

    set shouldButtonBeDisabledCallback(newValue) {
        this.#shouldButtonBeDisabledCallback = newValue;
        this.updateAppearance();
    }

    get monthChangedCallback() {
        return this.#monthChangedCallback;
    }

    set monthChangedCallback(newValue) {
        this.#monthChangedCallback = newValue;
        this.changeMonth(0);
    }
}

class BookingsGrid {
    constructor(element, timeBar) {
        this.element = element;
        this.timeBar = timeBar;
        this.todayMs = undefined;

        let today = new Date();
        today.setHours(0, 0, 0, 0);
        this.todayMs = today.getTime();

        //setInterval(() => this.updateTimeBar(), 1000);
    }

    changeConfig(openingMinute, closingMinute) {
        while (this.element.firstChild) {
            this.element.removeChild(this.element.lastChild);
        }
        this.bookings = [];

        this.openingMinute = openingMinute;
        this.closingMinute = closingMinute;

        let deltaMinutes = (closingMinute - openingMinute);
        let size = (deltaMinutes > 360 ? 2 : 1);
        this.element.style["grid-template-columns"] = "4rem" + " 1fr".repeat(seatCount);
        this.element.style["grid-template-rows"] = "1fr ".repeat(deltaMinutes / bookingInterval);

        for (let i = 0; i < deltaMinutes / bookingInterval; i += size) {
            let time = i * bookingInterval + openingMinute;
            let minutes = time % 60;
            let hours = (time - minutes) / 60;
            let text = CreateElement("p", `${DoubleDigit(hours)}:${DoubleDigit(minutes)}`, this.element);
            text.style["grid-row"] = `${i + 1} / span ${size}`;
        }

        //this.updateTimeBar();
    }

    addBooking(minute, clickCallback) {
        let gridRow = Math.floor((minute - this.openingMinute) / bookingInterval) + 1;
        let size = Math.floor(bookingLength / bookingInterval);
        let column;
        for (column = 1; column <= 10; column++) {
            var fits = true;
            this.bookings.forEach(booking => {
                if (booking.start + bookingLength > minute || booking.start - bookingLength < minute) fits = false;
            });
            if (fits) break;
        }

        let button = CreateElement("button", "", this.element);
        button.style["grid-row"] = `${gridRow} / span ${size}`;
        button.classList.add("booking-button");

        this.bookings.push({
            start: minute,
            column: column,
            button: button
        })

        button.addEventListener("click", () => {
            if (this.selectedButton) this.selectedButton.classList.remove("selected");
            button.classList.add("selected");
            this.selectedButton = button;
            clickCallback();
        });
        return button;
    }

    updateTimeBar() {
        var timeBarOffset = -this.timeBar.clientHeight / 2;
        let date = Date.now();
        let delta = date - this.todayMs;
        if (delta > 86400000)
            updateToday();
        let hours = delta / 3600000;
        let offset = (hours - this.openHour) / (this.closeHour - this.openHour);
        let pxOffset = Math.round(this.element.clientHeight * offset + timeBarOffset);
        console.log(this.timeBar.clientHeight);
        this.timeBar.style.transform = `translate(4rem, ${pxOffset}px)`;

        this.timeBar.classList.toggle("hidden", (clamp(offset, 0, 1) != offset))
    }
}

// Global functions

const FromId = id => document.getElementById(id);

function clamp(x, min, max) {
    return Math.max(Math.min(x, max), min);
}

function CreateElement(element, text, parent) {
    let newElement = document.createElement(element);
    if (text) {
        let textNode = document.createTextNode(text);
        newElement.appendChild(textNode);
    }
    if (parent) parent.appendChild(newElement);
    return newElement;
}

function ConvertWeekday(value) {
    return (value == 0 ? 7 : value) - 1;
}

function DoubleDigit(value) {
    return value < 10 ? "0" + value : value;
}

function FormatDate(date) {
    let year = date.getFullYear();
    let month = String(date.getMonth() + 1).padStart(2, '0');
    let day = String(date.getDate()).padStart(2, '0');

    return `${year}-${month}-${day}`;
}

// Main

const calendar = new Calendar(FromId("calendar"), true);
const grid = new BookingsGrid(FromId("time-grid"), FromId("meter"));
const dayInformation = FromId("day-information");
const bookingInformation = FromId("booking-information");
const deleteBookingButton = FromId("delete-booking");

let auth = getAuthHeader();

var seatCount = 1;
var bookingInterval = 0;
var bookingLength = 0;

async function loadBookingData() {
    let res = await api.fetch("bookingData");
    if (!res.ok) {
        alert(httpErrorMessage(res.status));
        return false;
    }

    let data = await res.json();
    seatCount = data["seatCount"];
    bookingInterval = data["bookingInterval"];
    bookingLength = data["bookingLength"];
}

loadBookingData();

var monthData = [];
calendar.monthChangedCallback = async (year, month) => {
    let res = await api.fetch(`monthBookingData?year=${year}&month=${month + 1}`);
    if (!res.ok) {
        alert(httpErrorMessage(res.status));
        return false;
    }

    let data = await res.json();
    monthData = data["dates"];
    return true;
}

calendar.shouldButtonBeDisabledCallback = (date) => {
    let dateDataL = monthData[FormatDate(date)];
    if (!dateDataL || !dateDataL["isOpen"]) return [true, false];

    return [false, false];
}

var dateData;
var selectedBookingData;
var selectedBookingButton;
calendar.dateSelectedCallback = async dateTime => {
    let date = new Date(dateTime);

    let formatted = FormatDate(date);
    let res = await api.fetch(`bookings?date=${formatted}`, "GET", { "headers": { "Authorization": auth } });
    if (!res.ok) {
        alert(httpErrorMessage(res.status));
        return false;
    }

    dateData = await res.json();

    bookingInformation.innerText = "";
    deleteBookingButton.setAttribute("hidden", "");
    grid.changeConfig(dateData["openingTimeMinutes"], dateData["closingTimeMinutes"]);

    let bookingCount = dateData["bookings"].length;
    let personCount = dateData["bookings"].reduce((accumulator, booking) => accumulator + booking["adultCount"] + booking["childCount"], 0);
    dayInformation.innerText = `Antal bokningar: ${bookingCount}
Antal personer: ${personCount}`;

    dateData["bookings"].forEach(booking => {
        date = new Date(booking.datetime);
        let dateFormatted = `${date.getDate()} ${Calendar.months[date.getMonth()]} ${date.getFullYear()}`;
        let timeFormatted = `${DoubleDigit(date.getHours())}:${DoubleDigit(date.getMinutes())}`
        let time = date.getHours() * 60 + date.getMinutes();
        let button = grid.addBooking(time, () => {
            bookingInformation.innerText = `Namn: ${booking.name}
Telefonnummer: ${booking.phoneNumber}
Antal vuxna: ${booking.adultCount}
Antal barn: ${booking.childCount}
Datum: ${dateFormatted}
Tid: ${timeFormatted}`;
            deleteBookingButton.removeAttribute("hidden");
            selectedBookingData = booking;
            selectedBookingButton = button;
        });
    })

    return true;
}

deleteBookingButton.addEventListener("click", async () => {
    if (!selectedBookingData) return;
    let res = await api.fetch("bookings", "DELETE", { "body": JSON.stringify({ "bookingId": selectedBookingData.bookingId }), "headers": { "Authorization": auth } });
    if (!res.ok) {
        alert(httpErrorMessage(res.status));
        return false;
    }
    grid.element.removeChild(selectedBookingButton);
    bookingInformation.innerText = "";
    deleteBookingButton.setAttribute("hidden", "");

    const index = dateData.bookings.indexOf(selectedBookingData);
    if (index > -1) {
        dateData.bookings.splice(index, 1);
    }
    let bookingCount = dateData["bookings"].length;
    let personCount = dateData["bookings"].reduce((accumulator, booking) => accumulator + booking["adultCount"] + booking["childCount"], 0);
    dayInformation.innerText = `Antal bokningar: ${bookingCount}
Antal personer: ${personCount}`;
});