Эх сурвалжийг харах

appointment overview added share, lock, edit and delete buttons

liontix 1 жил өмнө
parent
commit
34f556da30

+ 1 - 1
src/lib/dbdata.ts

@@ -45,7 +45,7 @@ export interface AppointmentList {
     appointments: WeekdayAppointment[];
 }
 
-interface DynamicJson {
+export interface DynamicJson {
     [key: string]: any; // Allows any key with any value
 }
 

+ 4 - 0
src/lib/funcs.ts

@@ -31,3 +31,7 @@ export function getTimeSlots(startDate: string, startTime: string, endTime: stri
 
     return timeSlots;
 }
+
+export function isDueDatePassed(dueDate: Date) {
+    return moment(dueDate).isBefore();
+}

+ 3 - 1
src/lib/modules/AppointmentConfirmDialog.svelte

@@ -1,4 +1,5 @@
 <script lang="ts">
+	import type { AppointmentInputs, DynamicJson } from "$lib/dbdata";
 	import { extractTime } from "$lib/funcs";
 	import AppointmentInfo from "./AppointmentInfo.svelte";
 	import AppointmentRegisterInputs from "./AppointmentRegisterInputs.svelte";
@@ -9,6 +10,7 @@
   export let location: string = "Somewhere";
   export let description: string = "Description ...";
   export let date: Date = new Date();
+  export let dynamicInputs: DynamicJson[] = [];
   let time: string = "00:00";
 
   $: {
@@ -29,7 +31,7 @@
       <AppointmentInfo bind:title bind:location bind:description
           bind:time bind:date isConfirmation={true} />
 
-      <AppointmentRegisterInputs />
+      <AppointmentRegisterInputs bind:dynamicInputs />
 
       <nav class="right-align">
         <button class="border" on:click={closeModal}>Cancel</button>

+ 3 - 6
src/lib/modules/AppointmentRegisterInputs.svelte

@@ -1,12 +1,8 @@
 <script lang="ts">
-	import type { AppointmentInputs } from "$lib/dbdata";
+	import type { AppointmentInputs, DynamicJson } from "$lib/dbdata";
     import validator from "validator";
 
-    let dynamicInputs: AppointmentInputs[] = [
-        {name: "First name", type: "text"},
-        {name: "Last name", type: "text"},
-        {name: "E-Mail", type: "email"}
-    ];
+    export let dynamicInputs: DynamicJson[] = [];
 
     let inputValues: Record<string, string> = {};
     let errorMessage: string = "";
@@ -27,6 +23,7 @@
         }
     }
 
+    // TODO: please send record to api
     function submit() {
         const jsonOutput: Record<string, any> = {};
         errorMessage = "";

+ 30 - 31
src/lib/modules/DatePicker.svelte

@@ -6,29 +6,29 @@
 
 	let currentDate: Date = new Date();
 	let days: Array<{ day: number; isActive: boolean; isAvailable: boolean }> = [];
-	
-  let currentMonth: string = '';
+
+	let currentMonth: string = '';
 	export let selectedDate: Date;
-  export let appointmentDateTimes: AppointmentDateTimes[];
+	export let appointmentDateTimes: AppointmentDateTimes[];
 
-  $: {
-    if (appointmentDateTimes.length > 0) {
-      renderCalendar();
-    }
-  } 
+	$: {
+		if (appointmentDateTimes.length > 0) {
+			renderCalendar();
+		}
+	}
 
-  function isDayAvailable(year: number, month: number, day: number) {
-    const dateStr = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
-    const testDate: Date = moment(dateStr).toDate();
+	function isDayAvailable(year: number, month: number, day: number) {
+		const dateStr = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
+		const testDate: Date = moment(dateStr).toDate();
 
-    return appointmentDateTimes.some(date => isSameDate(testDate, date.date));    
-  }
+		return appointmentDateTimes.some((date) => isSameDate(testDate, date.date));
+	}
 
 	/**
 	 * Updates the calendar display by calculating the days of the current month.
 	 */
 	const renderCalendar = (): void => {
-    const mDate: Moment = moment(currentDate);
+		const mDate: Moment = moment(currentDate);
 		const year: number = currentDate.getFullYear();
 		const month: number = mDate.month() + 1;
 		currentMonth = currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
@@ -58,17 +58,17 @@
 	 */
 	const changeMonth = (offset: number): void => {
 		//currentDate.setMonth(currentDate.getMonth() + offset);
-    if (offset === -1) {
-      currentDate = moment(currentDate).subtract(1, "month").toDate();
-    } else {
-      currentDate = moment(currentDate).add(1, "month").toDate();
-    }
-    
+		if (offset === -1) {
+			currentDate = moment(currentDate).subtract(1, 'month').toDate();
+		} else {
+			currentDate = moment(currentDate).add(1, 'month').toDate();
+		}
+
 		renderCalendar();
 	};
 
 	onMount(() => {
-    currentDate = new Date();
+		currentDate = new Date();
 		renderCalendar();
 	});
 
@@ -106,22 +106,21 @@
 		<!-- Render days -->
 		{#each days as { day, isActive, isAvailable }, index}
 			{#if isAvailable}
-      <!-- svelte-ignore a11y_click_events_have_key_events -->
-      <!-- svelte-ignore a11y_no_static_element_interactions -->
+				<!-- svelte-ignore a11y_click_events_have_key_events -->
+				<!-- svelte-ignore a11y_no_static_element_interactions -->
 				<div
-					class="date-picker-cell {!isActive ? 'secondary' : ''} {isActive
-						? 'active'
-						: ''} {day === 0 ? 'empty' : ''}"
+					class="date-picker-cell {!isActive ? 'secondary' : ''} {isActive ? 'active' : ''} {day ===
+					0
+						? 'empty'
+						: ''}"
 					on:click={() => day && selectDay(day)}
 				>
 					{day || ''}
 				</div>
 			{:else}
-      <!-- svelte-ignore a11y_click_events_have_key_events -->
-      <!-- svelte-ignore a11y_no_static_element_interactions -->
-				<div
-					class="date-picker-cell"
-				>
+				<!-- svelte-ignore a11y_click_events_have_key_events -->
+				<!-- svelte-ignore a11y_no_static_element_interactions -->
+				<div class="date-picker-cell">
 					{day || ''}
 				</div>
 			{/if}

+ 137 - 46
src/lib/modules/EventTables.svelte

@@ -1,59 +1,150 @@
 <script lang="ts">
-	import { api_host, authToken } from "$lib";
-	import type { Appointment } from "$lib/dbdata";
-	import { onMount } from "svelte";
-
-    let appointmentData: Appointment[] = [];
-    let ready: boolean = false;
-
-    async function fetchAppointments() {
-        try {
-            const response = await fetch(`${api_host}/api/users/appointments`, {
-                method: "GET",
-                headers: {
-                    "Authorization": "Bearer " + $authToken
-                },
-            });
-
-            if (!response.ok) {
-                throw new Error(`HTTP error! Status: ${response.status}`);
-            }
+	import { goto } from '$app/navigation';
+	import { api_host, authToken } from '$lib';
+	import type { Appointment } from '$lib/dbdata';
+	import { isDueDatePassed } from '$lib/funcs';
+	import { onMount } from 'svelte';
+
+	let appointmentData: Appointment[] = [];
+	let ready: boolean = false;
+	let errorMessage: string = '';
+    let actionCounter: number = 0;
+    let action: {action: string, id: string} = {action: '', id: ''};
+
+	async function fetchAppointments() {
+		try {
+			const response = await fetch(`${api_host}/api/users/appointments`, {
+				method: 'GET',
+				headers: {
+					Authorization: 'Bearer ' + $authToken
+				}
+			});
+
+			if (!response.ok) {
+				throw new Error(`HTTP error! Status: ${response.status}`);
+			}
+
+			const data: Appointment[] = await response.json();
+			appointmentData = data;
+			console.log(appointmentData);
+		} catch (error) {
+			console.log('Error during fetch:', error);
+		}
+	}
+
+	function copyShareLink(appointmentId: string) {
+		window.navigator.clipboard.writeText(
+			`${window.location.origin}/appointment?id=${appointmentId}`
+		);
+	}
 
-            const data: Appointment[] = await response.json();
-            appointmentData = data;
-        } catch (error) {
-            console.log('Error during fetch:', error);
+	async function deleteEntry(appointmentId: string) {
+		manageAppointment(appointmentId, "DELETE", "delete");
+	}
+
+	async function lockAppointment(appointmentId: string) {
+		manageAppointment(appointmentId, "POST", "lock");
+	}
+
+	async function manageAppointment(appointmentId: string, method: 'DELETE' | 'POST', path: string) {
+        actionCounter += 1;
+        if (action.action !== method || action.id !== appointmentId) {
+            actionCounter = 1;
+            action = {action: method, id: appointmentId};
         }
-    }
 
+        if (actionCounter !== 3) {
+            return;
+        }
+
+        actionCounter = 0;
+
+		try {
+			const response = await fetch(`${api_host}/api/users/${path}`, {
+				method: method,
+				headers: {
+					Authorization: 'Bearer ' + $authToken,
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify({
+					appointmentId
+				})
+			});
 
-    onMount(async () => {
-       await fetchAppointments();
-       
-       ready = true;
-    });
+			if (response.status === 404) {
+				console.log(`entry not found`);
+				errorMessage = `entry not found`;
+				await fetchAppointments();
+				setTimeout(() => errorMessage = "", 4000);
+			}
 
+			if (response.status === 200 && path === "delete") {
+				appointmentData = appointmentData.filter((element) => element._id !== appointmentId);
+			}
+
+            if (response.status === 200 && path === "lock") {
+				await fetchAppointments();
+                errorMessage = "";
+                
+            }
+		} catch (error) {
+			console.log('Error during fetch:', error);
+		}
+	}
+
+    function redirectEditCreator(appointmentId: string) {
+        goto(`/user/creator?${appointmentId}`);
+    }
+
+	onMount(async () => {
+		await fetchAppointments();
+
+		ready = true;
+	});
 </script>
 
 {#each appointmentData as data}
-    <tr>
-        <td>{data.title}</td>
-        <td class="center-align">14/21</td>
-        <td class="center-align">
-            <button class="medium">
-                <span>Sperren</span>
-            </button>
-        </td>
-        <td class="center-align">
-            <button class="medium secondary">
+	<tr>
+		<td>{data.title}</td>
+		<td on:click={() => copyShareLink(data._id)} class="center-align">
+			<button class="medium cyan">
+				<span>Teilen</span>
+			</button>
+		</td>
+        {#if !isDueDatePassed(data.dueDate)}          
+            <td on:click={() => lockAppointment(data._id)} class="center-align">
+                <button class="medium">
+                    <span>Sperren</span>
+                </button>
+            </td>
+        {:else}
+            <td class="center-align">
+                <button class="medium secondary">
+                    <span>Gesperrt</span>
+                </button>
+            </td>
+        {/if}
+		
+		<!-- load into the creator -->
+        <td on:click={() => redirectEditCreator(data._id)} class="center-align">
+            <button class="medium secondary blue">
                 <span>Bearbeiten</span>
             </button>
         </td>
 
-        <td class="center-align">
-            <button class="medium red">
-                <span>Loeschen</span>
-            </button>
-        </td>
-    </tr> 
-{/each}
+		<!-- send a delete request -->
+		<td on:click={() => deleteEntry(data._id)} class="center-align">
+			<button class="medium red">
+				<span>Loeschen</span>
+			</button>
+		</td>
+	</tr>
+{/each}
+
+{#if errorMessage.length > 0}
+	<div class="snackbar error active">{errorMessage}</div>
+{/if}
+
+{#if actionCounter > 0}
+    <div class="snackbar active">Click another two times to confirm</div>
+{/if}

+ 21 - 4
src/routes/appointment/+page.svelte

@@ -1,6 +1,8 @@
 <script lang="ts">
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
 	import { api_host } from '$lib';
-	import type { Appointment, AppointmentDateTimes } 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';
@@ -13,6 +15,7 @@
 
     let selectedDate: Date = new Date();
     let activateModal: boolean = false;
+    let dynamicInputs: DynamicJson[] = [];
     let appointmentDateTimes: AppointmentDateTimes[] = [
 
     ];
@@ -25,14 +28,28 @@
                 }
             })
         const js: Appointment = await response.json();
-        appointmentDateTimes = js.times;
+        appointmentDateTimes = js.times.filter(element => element.available === false);
         title = js.title;
         description = js.description;
         location = js.place;
+        dynamicInputs = js.inputs;
+
+        if (response.status === 404) {
+            goto('/errors/404');
+        }
+        if (response.status === 410) {
+            goto('/errors/410');
+        }
     }
 
     onMount(async () => {
-        await getAppointmentInfo("6799f8a6b9285925c01491e0");
+
+        const id = $page.url.searchParams.get('id');
+        if (!id) {
+            goto('/errors/404');
+        } else {
+            await getAppointmentInfo(id);    
+        }
     });
 
 </script>
@@ -56,4 +73,4 @@
     {/key}
 </div>
 
-<AppointmentConfirmDialog bind:active={activateModal} bind:date={selectedDate} bind:title bind:description bind:location />
+<AppointmentConfirmDialog bind:dynamicInputs bind:active={activateModal} bind:date={selectedDate} bind:title bind:description bind:location />

+ 1 - 0
src/routes/errors/404/+page.svelte

@@ -0,0 +1 @@
+<h2>404: Not found</h2>

+ 1 - 0
src/routes/errors/410/+page.svelte

@@ -0,0 +1 @@
+<h2>410: Gone</h2>

+ 3 - 3
src/routes/user/+layout.svelte

@@ -6,14 +6,14 @@
 <nav class="bottom no-space tabbed large">
     <a href="/user/bookings">
         <i>event_note</i>
-        <span>Termine</span>
+        <span>Buchungen</span>
     </a>
     <a href="/user/events">
         <i>list</i>
-        <span>Buchungen</span>
+        <span>Termine</span>
     </a>
     <a href="/user/creator">
         <i>library_add</i>
-        <span>Neue Buchung</span>
+        <span>Neue Termine</span>
     </a>
 </nav>

+ 5 - 1
src/routes/user/creator/+page.svelte

@@ -3,7 +3,7 @@
 	import CreatorBaseInputs from '$lib/modules/CreatorBaseInputs.svelte';
 	import CreatorInputs from '$lib/modules/CreatorInputs.svelte';
 	import CreatorTimeInputs from '$lib/modules/CreatorTimeInputs.svelte';
-	import { tick } from 'svelte';
+	import { onMount, tick } from 'svelte';
 
 	let creatorInputs: AppointmentInputs[] = [{ name: '', type: 'text' }];
 	let topic: string;
@@ -92,6 +92,10 @@
 		}
 	}
 
+	onMount(() => {
+		// implement editor if query id param was detected
+	});
+
 	// get time slots before sending
 </script>
 

+ 4 - 4
src/routes/user/events/+page.svelte

@@ -7,10 +7,10 @@
     <thead>
         <tr>
             <th class="min">Thema</th>
-            <th class="min center-align">Teilnehmer</th>
-            <th class="min center-align">Sperren</th>
-            <th class="min center-align">Bearbeiten</th>
-            <th class="min center-align">Loeschen</th>
+            <th class="min center-align">Aktionen</th>
+            <th class="min center-align"></th>
+            <th class="min center-align"></th>
+            <th class="min center-align"></th>
         </tr>
     </thead>
     <tbody>