Ver Fonte

connected booking modal to api

liontix há 1 ano atrás
pai
commit
d7d8a07af1

+ 4 - 2
src/lib/dbdata.ts

@@ -6,12 +6,12 @@ export interface Appointment {
     dueDate: Date;
     place: string;
     duration: number;
-    times: AppointmentTimes[];
+    times: AppointmentDateTimes[];
     inputs: DynamicJson[];
 }  
 
 export interface AppointmentTimes {
-    date: Date;
+    date: string;
     available: boolean;
 }
 
@@ -21,6 +21,7 @@ export interface AuthTokenResponse {
 
 export interface AppointmentDateTimes {
     date: Date;
+    available: boolean;
 }
 
 export interface AppointmentInputs {
@@ -37,6 +38,7 @@ export interface AppointmentTimeRange {
 export interface WeekdayAppointment {
     name: string;
     active: boolean;
+    date: string;
     times: AppointmentTimeRange[];
 }
 

+ 4 - 3
src/lib/funcs.ts

@@ -1,4 +1,5 @@
 import moment from "moment";
+import type { AppointmentTimes } from "./dbdata";
 
 export function isSameDate(dateA: Date, dateB: Date) {
     const date1 = moment(dateA).format('YYYY-MM-DD');
@@ -15,17 +16,17 @@ export function extractDate(date: Date): string {
     return moment(date).format("DD.MM.YYYY");
 }
 
-export function getTimeSlots(startDate: string, startTime: string, endTime: string, intervalMinutes: number = 15) {
+export function getTimeSlots(startDate: string, startTime: string, endTime: string, intervalMinutes: number = 15): AppointmentTimes[] {
     let start = moment(`${startDate} ${startTime}`, "YYYY-MM-DD HH:mm");
     let end = moment(`${startDate} ${endTime}`, "YYYY-MM-DD HH:mm");
-    let timeSlots = [];
+    let timeSlots: AppointmentTimes[] = [];
 
     while (start <= end) {
         if (start.clone().add(intervalMinutes, "minutes").isAfter(end)) {
             break; // Ensures the last slot does not exceed end time
         }
 
-        timeSlots.push(start.format("YYYY-MM-DDTHH:mm")); // ISO-like format
+        timeSlots.push({date: start.format('YYYY-MM-DDTHH:mm'), available: true}); // ISO-like format
         start.add(intervalMinutes, "minutes");
     }
 

+ 15 - 19
src/lib/modules/AppointmentConfirmDialog.svelte

@@ -1,42 +1,38 @@
 <script lang="ts">
-	import type { AppointmentInputs, DynamicJson } from "$lib/dbdata";
+	import type { Appointment, AppointmentInputs, DynamicJson } from "$lib/dbdata";
 	import { extractTime } from "$lib/funcs";
 	import AppointmentInfo from "./AppointmentInfo.svelte";
 	import AppointmentRegisterInputs from "./AppointmentRegisterInputs.svelte";
 
   export let active: boolean;
 
-  export let title: string = "Title";
-  export let location: string = "Somewhere";
-  export let description: string = "Description ...";
+  export let appointment: Appointment;
   export let date: Date = new Date();
   export let dynamicInputs: DynamicJson[] = [];
+
+  let errorMessage: string = "";
+  let successMessage: string = "";
+
   let time: string = "00:00";
 
   $: {
     time = extractTime(date);
   }
-
-  function closeModal() {
-    active = false;
-  }
-
-  function submitModal() {
-    // send to backend
-  }
 </script>
 
 <dialog class="max {active ? 'active': ''}">
     <div class="center medium-padding">
-      <AppointmentInfo bind:title bind:location bind:description
+      <AppointmentInfo bind:title={appointment.title} bind:location={appointment.place} bind:description={appointment.description}
           bind:time bind:date isConfirmation={true} />
 
-      <AppointmentRegisterInputs bind:dynamicInputs />
-
-      <nav class="right-align">
-        <button class="border" on:click={closeModal}>Cancel</button>
-        <button>Confirm</button>
-      </nav>
+      <AppointmentRegisterInputs bind:modalActive={active} bind:errorMessage bind:successMessage bind:date bind:appointmentId={appointment._id} bind:dynamicInputs />
   </div>
 </dialog>
   
+{#if errorMessage}
+    <div class="snackbar error active">{errorMessage}</div>
+{/if}
+
+{#if successMessage}
+    <div class="snackbar green active">{successMessage}</div>
+{/if}

+ 48 - 13
src/lib/modules/AppointmentRegisterInputs.svelte

@@ -1,11 +1,17 @@
 <script lang="ts">
+	import { api_host } from "$lib";
 	import type { AppointmentInputs, DynamicJson } from "$lib/dbdata";
     import validator from "validator";
 
     export let dynamicInputs: DynamicJson[] = [];
+    export let appointmentId: string;
+    export let date: Date;
+    export let modalActive: boolean;
+
+    export let errorMessage: string = "";
+    export let successMessage: string = "";
 
     let inputValues: Record<string, string> = {};
-    let errorMessage: string = "";
 
     function validateInput(value: string, type: string): boolean  {
         switch (type) {
@@ -23,35 +29,64 @@
         }
     }
 
-    // TODO: please send record to api
-    function submit() {
-        const jsonOutput: Record<string, any> = {};
+    async function confirmBooking(customInputs: DynamicJson[]) {
+        try {
+            const response = await fetch(`${api_host}/api/schedule/create`, {
+                method: "POST",
+                headers: {
+                    "Content-Type": "application/json"
+                },
+                body: JSON.stringify({
+                    appointmentId: appointmentId,
+                    time: date,
+                    inputs: customInputs
+                })
+            })
+
+            if (response.status === 200) {
+                successMessage = "successfully booked";
+            } else {
+                errorMessage = "invalid credentials";
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    }
+    
+    async function submit() {
+        let customInputs: DynamicJson[] = [];
         errorMessage = "";
         if (Object.keys(inputValues).length != dynamicInputs.length) {
             errorMessage = "Not all inputs filled";
+            return;
         }
         for (const dInput of dynamicInputs) {
             if (inputValues[dInput.name]) {
-                jsonOutput[dInput.name] = inputValues[dInput.name];
-                if (!validateInput(jsonOutput[dInput.name], dInput.type)) {
+                if (!validateInput(inputValues[dInput.name], dInput.type)) {
                     errorMessage = "Invalid inputs";
+                    return;
                 }
+                customInputs = [...customInputs, {name: dInput.name, value: inputValues[dInput.name]}];
             }
         }
-        console.log(jsonOutput); // Output the JSON object
+
+        await confirmBooking(customInputs);
+    }
+
+    function closeModal() {
+        modalActive = false;
     }
 
 </script>
 
-{#each dynamicInputs as dInput, index}
+{#each dynamicInputs as dInput}
     <div class="field border">
         <input id={dInput.name} type={dInput.type} placeholder={dInput.name} bind:value={inputValues[dInput.name]}>
         <label for={dInput.name}>{dInput.name}</label>
     </div>
 {/each}
 
-{#if errorMessage}
-    <p class="error-text">{errorMessage}</p>
-{/if}
-
-<button on:click={submit}>Submit</button>
+<nav class="right-align">
+    <button on:click={submit}>Submit</button>
+    <button class="border" on:click={closeModal}>Close</button>
+</nav>

+ 0 - 1
src/lib/modules/CreatorTimeInputs.svelte

@@ -3,7 +3,6 @@
 	import CreatorTimeRangeInput from "./CreatorTimeRangeInput.svelte";
 
     export let weekdays: WeekdayAppointment[] = [];
-	export let selectedWeek: number;
 
     $: {
         weekdays = weekdays.map(item => 

+ 66 - 58
src/routes/appointment/+page.svelte

@@ -2,75 +2,83 @@
 	import { goto } from '$app/navigation';
 	import { page } from '$app/stores';
 	import { api_host } from '$lib';
-	import type { Appointment, AppointmentDateTimes, AppointmentInputs, DynamicJson } from '$lib/dbdata';
+	import type {
+		Appointment,
+		AppointmentDateTimes,
+		AppointmentInputs,
+		DynamicJson
+	} from '$lib/dbdata';
 	import AppointmentConfirmDialog from '$lib/modules/AppointmentConfirmDialog.svelte';
-    import AppointmentInfo from '$lib/modules/AppointmentInfo.svelte';
-    import DatePicker from '$lib/modules/DatePicker.svelte';
+	import AppointmentInfo from '$lib/modules/AppointmentInfo.svelte';
+	import DatePicker from '$lib/modules/DatePicker.svelte';
 	import DateTimes from '$lib/modules/DateTimes.svelte';
 	import { onMount } from 'svelte';
 
-    let title: string = "Appointment";
-    let location: string = "Somewhere";
-    let description: string = "Description ...";
+	let appointment: Appointment;
 
-    let selectedDate: Date = new Date();
-    let activateModal: boolean = false;
-    let dynamicInputs: DynamicJson[] = [];
-    let appointmentDateTimes: AppointmentDateTimes[] = [
+	let selectedDate: Date = new Date();
+	let activateModal: boolean = false;
+	let dynamicInputs: DynamicJson[] = [];
+	let appointmentDateTimes: AppointmentDateTimes[] = [];
 
-    ];
-
-    async function getAppointmentInfo(appointmentId: string) {
-        const response = await fetch(`${api_host}/api/schedule?appointmentId=${appointmentId}`, {
-                method: "GET",
-                headers: {
-                    "Content-Type": "application/json"
-                }
-            })
-        const js: Appointment = await response.json();
-        appointmentDateTimes = js.times.filter(element => element.available === false);
-        title = js.title;
-        description = js.description;
-        location = js.place;
+	async function getAppointmentInfo(appointmentId: string) {
+		const response = await fetch(`${api_host}/api/schedule?appointmentId=${appointmentId}`, {
+			method: 'GET',
+			headers: {
+				'Content-Type': 'application/json'
+			}
+		});
+		const js: Appointment = await response.json();
+		appointment = js;
+		appointmentDateTimes = js.times.filter((element) => element.available === true);
         dynamicInputs = js.inputs;
 
-        if (response.status === 404) {
-            goto('/errors/404');
-        }
-        if (response.status === 410) {
-            goto('/errors/410');
-        }
-    }
-
-    onMount(async () => {
-
-        const id = $page.url.searchParams.get('id');
-        if (!id) {
-            goto('/errors/404');
-        } else {
-            await getAppointmentInfo(id);    
-        }
-    });
+		if (response.status === 404) {
+			goto('/errors/404');
+		}
+		if (response.status === 410) {
+			goto('/errors/410');
+		}
+	}
 
+	onMount(async () => {
+		const id = $page.url.searchParams.get('id');
+		if (!id) {
+			goto('/errors/404');
+		} else {
+			await getAppointmentInfo(id);
+		}
+	});
 </script>
-  
-<style>
-    .w-80 {
-        max-width: 600px;
-    }
 
-    .inline-margin {
-        margin-inline: auto;
-    }
-</style>
+{#if appointment}
+	<DatePicker bind:appointmentDateTimes bind:selectedDate />
+
+	<div class="top-margin w-80 inline-margin">
+		<AppointmentInfo
+			bind:title={appointment.title}
+			bind:description={appointment.description}
+			bind:location={appointment.place}
+		/>
+		{#key selectedDate}
+			<DateTimes bind:activateModal bind:selectedDate bind:availableTimes={appointmentDateTimes} />
+		{/key}
+	</div>
 
-<DatePicker bind:appointmentDateTimes bind:selectedDate />
+	<AppointmentConfirmDialog
+		bind:appointment
+		bind:dynamicInputs
+		bind:active={activateModal}
+		bind:date={selectedDate}
+	/>
+{/if}
 
-<div class="top-margin w-80 inline-margin"> 
-    <AppointmentInfo bind:title bind:description bind:location />
-    {#key selectedDate}
-        <DateTimes bind:activateModal bind:selectedDate bind:availableTimes={appointmentDateTimes} />    
-    {/key}
-</div>
+<style>
+	.w-80 {
+		max-width: 600px;
+	}
 
-<AppointmentConfirmDialog bind:dynamicInputs bind:active={activateModal} bind:date={selectedDate} bind:title bind:description bind:location />
+	.inline-margin {
+		margin-inline: auto;
+	}
+</style>

+ 84 - 24
src/routes/user/creator/+page.svelte

@@ -1,8 +1,11 @@
 <script lang="ts">
-	import type { AppointmentInputs, AppointmentList, WeekdayAppointment } from '$lib/dbdata';
+	import { api_host, authToken } from '$lib';
+	import type { AppointmentInputs, AppointmentList, AppointmentTimes, WeekdayAppointment } from '$lib/dbdata';
+	import { getTimeSlots } from '$lib/funcs';
 	import CreatorBaseInputs from '$lib/modules/CreatorBaseInputs.svelte';
 	import CreatorInputs from '$lib/modules/CreatorInputs.svelte';
 	import CreatorTimeInputs from '$lib/modules/CreatorTimeInputs.svelte';
+	import moment from 'moment';
 	import { onMount, tick } from 'svelte';
 
 	let creatorInputs: AppointmentInputs[] = [{ name: '', type: 'text' }];
@@ -11,25 +14,45 @@
 	let selectedWeek: number = 202502;
 	let previousWeek: number = selectedWeek;
 	let duration: number = 15;
+	let dueDate: Date = new Date();
+	let description: string = "test";
 	let renderTimeSelection: boolean = false;
 
-	$: console.log(appointmentList);
-
 	let appointmentList: AppointmentList[] = [
 		{
 			week: selectedWeek,
-			appointments: [
-				{ name: 'Montag', active: false, times: [] },
-				{ name: 'Dienstag', active: false, times: [] },
-				{ name: 'Mittwoch', active: false, times: [] },
-				{ name: 'Donnerstag', active: false, times: [] },
-				{ name: 'Freitag', active: false, times: [] },
-				{ name: 'Samstag', active: false, times: [] },
-				{ name: 'Sonntag', active: false, times: [] }
-			]
+			appointments: generateAppointments(splitYearAndWeek(selectedWeek).week, splitYearAndWeek(selectedWeek).year),
 		}
 	];
 
+	function splitYearAndWeek(value: number): { year: number, week: number } {
+		const year = Math.floor(value / 100); // Extracts the first 4 digits (year)
+		const week = value % 100; // Extracts the last 2 digits (week number)
+		return { year, week };
+	}
+
+	/**
+	 * Generates a JSON structure for a given calendar week with actual dates.
+	 * @param weekNumber The calendar week number (1-53).
+	 * @param year The year for which the week should be calculated.
+	 * @returns An array of objects representing the days of the given week.
+	 */
+	export function generateAppointments(weekNumber: number, year: number): WeekdayAppointment[] {
+		const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
+
+		// Get the first day of the given calendar week (Monday-based week system)
+		const startOfWeek = moment().year(year).isoWeek(weekNumber).startOf('isoWeek');
+
+		const appointments = weekdays.map((day, index) => ({
+			name: day,
+			date: startOfWeek.clone().add(index, 'days').format('YYYY-MM-DD'),
+			active: false,
+			times: []
+		}));
+
+		return appointments;
+	}
+
     async function changeWeek() {
 		await tick();
         console.log("changed");
@@ -48,15 +71,7 @@
 		if (ix === -1) {
             const tmp = {
                 week: selectedWeek,
-                appointments: [
-                    { name: 'Montag', active: false, times: [] },
-                    { name: 'Dienstag', active: false, times: [] },
-                    { name: 'Mittwoch', active: false, times: [] },
-                    { name: 'Donnerstag', active: false, times: [] },
-                    { name: 'Freitag', active: false, times: [] },
-                    { name: 'Samstag', active: false, times: [] },
-                    { name: 'Sonntag', active: false, times: [] }
-                ]
+                appointments: generateAppointments(splitYearAndWeek(selectedWeek).week, splitYearAndWeek(selectedWeek).year)
             }
             appointmentList = [... appointmentList, tmp];
 			selectedWeekAppointments = tmp.appointments;
@@ -92,6 +107,52 @@
 		}
 	}
 
+	function convertAppointmentList() {
+		let times: AppointmentTimes[] = [];
+
+		appointmentList.forEach(entry => {
+			entry.appointments.filter(element => element.active !== false).forEach(day => {
+				day.times.forEach(time => {
+					// if (!time.start || !time.end)
+					times = [... times, ...getTimeSlots(day.date, time.start, time.end, duration)];
+				});
+				
+			});
+		});
+
+		return times;
+	}
+
+	async function saveAppointment() {
+		if (!topic || !description || !dueDate || !location || !duration || !creatorInputs || !appointmentList) {
+			return;
+		}
+		const convertedTimes: AppointmentTimes[] = convertAppointmentList();
+		console.log(convertedTimes);
+
+		try {
+            const response = await fetch(`${api_host}/api/users/create`, {
+                method: "POST",
+                headers: {
+                    "Content-Type": "application/json",
+                    "Authorization": "Bearer " + $authToken
+                },
+                body: JSON.stringify({
+                    title: topic,
+					description: description,
+					dueDate: dueDate,
+					place: location,
+					duration: duration,
+					times: convertedTimes,
+					inputs: creatorInputs 
+                })
+            });
+
+        } catch (error) {
+            console.log(error);
+        }
+	}
+
 	onMount(() => {
 		// implement editor if query id param was detected
 	});
@@ -106,7 +167,7 @@
 
 	{#if selectedWeekAppointments}
 		{#key renderTimeSelection}
-			<CreatorTimeInputs bind:weekdays={selectedWeekAppointments} bind:selectedWeek />	
+			<CreatorTimeInputs bind:weekdays={selectedWeekAppointments} />	
 		{/key}
 	{/if}
 
@@ -115,8 +176,7 @@
 <CreatorInputs bind:creatorInputs />
 
 <div class="grid">
-	<button class="top-margin s6 large red">Loeschen</button>
-	<button class="top-margin s6 large">Speichern</button>
+	<button on:click={saveAppointment} class="top-margin s12 large">Speichern</button>
 </div>
 
 <style>