parent
							
								
									f8c8a53ba2
								
							
						
					
					
						commit
						d6ad0d2ccd
					
				
					 10 changed files with 254 additions and 105 deletions
				
			
		| 
						 | 
				
			
			@ -1,6 +1,12 @@
 | 
			
		|||
import { error } from "@sveltejs/kit";
 | 
			
		||||
import debug from "debug";
 | 
			
		||||
 | 
			
		||||
const log = debug('survey:admin:edit');
 | 
			
		||||
 | 
			
		||||
import { eq, and, inArray } from "drizzle-orm";
 | 
			
		||||
import { db } from "../db";
 | 
			
		||||
import { surveyAccessTable, surveyAnswersTable, surveySkillsTable, surveysTable } from "../db/schema";
 | 
			
		||||
import { generateRandomToken } from "$lib/randomToken";
 | 
			
		||||
 | 
			
		||||
export async function loadSurvey(id: number, ownerId: number) {
 | 
			
		||||
    const survey = await db.select().from(surveysTable).where(and(eq(surveysTable.id, id), eq(surveysTable.owner, ownerId))).limit(1);
 | 
			
		||||
| 
						 | 
				
			
			@ -32,3 +38,33 @@ export async function loadSurvey(id: number, ownerId: number) {
 | 
			
		|||
        }))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function loadSurveyData(surveyId: string, userId: number | null) {
 | 
			
		||||
    const sId = parseInt(surveyId);
 | 
			
		||||
 | 
			
		||||
    if (isNaN(sId)) {
 | 
			
		||||
        log('Invalid survey ID %s', surveyId);
 | 
			
		||||
        error(404, 'Invalid survey ID');
 | 
			
		||||
    }
 | 
			
		||||
    if (!userId) {
 | 
			
		||||
        log('User is not logged in');
 | 
			
		||||
        error(403, 'User is not logged in');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const surveyData = await loadSurvey(sId, userId);
 | 
			
		||||
    if (!surveyData) {
 | 
			
		||||
        log('Survey not found or user (%s) does not have access: %s', userId, surveyId);
 | 
			
		||||
        error(404, 'Survey not found');
 | 
			
		||||
    }
 | 
			
		||||
    return surveyData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add a new participant to a survey
 | 
			
		||||
export async function addParticipant(surveyId: number, recepientEmail: string) {
 | 
			
		||||
    await db.insert(surveyAccessTable).values({ surveyId, recepientEmail, accessToken: generateRandomToken() });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add a new skill to a survey
 | 
			
		||||
export async function addSkill(surveyId: number, title: string, description: string) {
 | 
			
		||||
    await db.insert(surveySkillsTable).values({ surveyId, title, description });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								src/lib/components/SurveyEditForm.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/lib/components/SurveyEditForm.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	type Props = {
 | 
			
		||||
		title: string;
 | 
			
		||||
		description: string | null;
 | 
			
		||||
		participants: { email: string }[];
 | 
			
		||||
		skills: { title: string; description: string | null }[];
 | 
			
		||||
		submitButtonTitle: string;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const { title, description, participants, skills, submitButtonTitle }: Props = $props();
 | 
			
		||||
 | 
			
		||||
	let numParticipants = $state(participants?.length ?? 1);
 | 
			
		||||
	let numSkills = $state(skills?.length ?? 1);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<form class="grid grid-cols-2 gap-2 p-4" method="post">
 | 
			
		||||
	<label for="title" class="justify-self-end">Survey Title</label>
 | 
			
		||||
	<input type="text" name="title" id="title" class="min-w-80 justify-self-start" value={title} />
 | 
			
		||||
	<label for="description" class="justify-self-end">Survey Description</label>
 | 
			
		||||
	<textarea name="description" id="description" class="min-w-80 justify-self-start"
 | 
			
		||||
		>{description}</textarea
 | 
			
		||||
	>
 | 
			
		||||
 | 
			
		||||
	<h2 class="col-span-2 text-2xl">Participants</h2>
 | 
			
		||||
	{#each Array(numParticipants) as _, idx}
 | 
			
		||||
		<label for="participants" class="justify-self-end">Email</label>
 | 
			
		||||
		<input
 | 
			
		||||
			type="email"
 | 
			
		||||
			name="participants"
 | 
			
		||||
			id="participants"
 | 
			
		||||
			class="min-w-80 justify-self-start"
 | 
			
		||||
			value={participants?.[idx]?.email}
 | 
			
		||||
		/>
 | 
			
		||||
	{/each}
 | 
			
		||||
	<button
 | 
			
		||||
		type="button"
 | 
			
		||||
		class="col-span-2 w-40 justify-self-center bg-blue-200"
 | 
			
		||||
		onclick={() => (numParticipants += 1)}>Add participant</button
 | 
			
		||||
	>
 | 
			
		||||
 | 
			
		||||
	<h2 class="col-span-2 text-2xl">Skills</h2>
 | 
			
		||||
	{#each Array(numSkills) as _, idx}
 | 
			
		||||
		<div class="col-span-2 ml-4 grid grid-cols-2 gap-4">
 | 
			
		||||
			<div class="flex w-full flex-col justify-self-end">
 | 
			
		||||
				<label for="skill" class="text-xs">Skill title</label>
 | 
			
		||||
				<input name="skill" id="skill" class="w-full" value={skills?.[idx]?.title} />
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex w-full flex-col justify-self-start">
 | 
			
		||||
				<label for="skill-description" class="text-xs">Skill description</label>
 | 
			
		||||
				<textarea name="skill-description" id="skill-description" class="min-h-36 w-full"
 | 
			
		||||
					>{skills?.[idx]?.description}</textarea
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{/each}
 | 
			
		||||
	<button
 | 
			
		||||
		type="button"
 | 
			
		||||
		class="col-span-2 w-40 justify-self-center bg-blue-200"
 | 
			
		||||
		onclick={() => (numSkills += 1)}>Add skill</button
 | 
			
		||||
	>
 | 
			
		||||
 | 
			
		||||
	<button type="submit" class="col-span-2 w-40 justify-self-center bg-slate-200"
 | 
			
		||||
		>{submitButtonTitle}</button
 | 
			
		||||
	>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										14
									
								
								src/lib/components/icons/EditIcon.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/components/icons/EditIcon.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
<svg
 | 
			
		||||
	xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
	fill="none"
 | 
			
		||||
	viewBox="0 0 24 24"
 | 
			
		||||
	stroke-width="1.5"
 | 
			
		||||
	stroke="currentColor"
 | 
			
		||||
	class="size-4"
 | 
			
		||||
>
 | 
			
		||||
	<path
 | 
			
		||||
		stroke-linecap="round"
 | 
			
		||||
		stroke-linejoin="round"
 | 
			
		||||
		d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
 | 
			
		||||
	/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 375 B  | 
							
								
								
									
										20
									
								
								src/lib/survey.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/lib/survey.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
 | 
			
		||||
export function fromFormData(formData: FormData) {
 | 
			
		||||
    const participants = formData.getAll('participants').filter(email => !!email).map(email => email.toString());
 | 
			
		||||
    const title = formData.get('title')?.toString();
 | 
			
		||||
    const description = formData.get('description')?.toString();
 | 
			
		||||
    const skillTitles = formData.getAll('skill');
 | 
			
		||||
    const skillDescriptions = formData.getAll('skill-description');
 | 
			
		||||
 | 
			
		||||
    const skills = skillTitles.flatMap((title, index) => !!title ? [({
 | 
			
		||||
        title: title.toString(),
 | 
			
		||||
        description: skillDescriptions[index].toString()
 | 
			
		||||
    })] : []);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        participants,
 | 
			
		||||
        title,
 | 
			
		||||
        description,
 | 
			
		||||
        skills
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +1,19 @@
 | 
			
		|||
import { error, redirect } from '@sveltejs/kit';
 | 
			
		||||
import type { PageServerLoad, RouteParams } from './$types';
 | 
			
		||||
import { loadSurvey } from '$lib/queries';
 | 
			
		||||
 | 
			
		||||
import debug from 'debug';
 | 
			
		||||
import { deleteAnswers } from '../../../../db/answers';
 | 
			
		||||
import { loadSurveyData } from '../../../../db/survey';
 | 
			
		||||
 | 
			
		||||
const log = debug('survey:admin');
 | 
			
		||||
 | 
			
		||||
async function loadSurveyData(params: RouteParams, locals: App.Locals) {
 | 
			
		||||
    const surveyId = parseInt(params.surveyId);
 | 
			
		||||
 | 
			
		||||
    if (isNaN(surveyId)) {
 | 
			
		||||
        log('Invalid survey ID %s', params.surveyId);
 | 
			
		||||
        error(400, 'Invalid survey ID');
 | 
			
		||||
    }
 | 
			
		||||
    if (!locals.userId) {
 | 
			
		||||
        log('User is not logged in');
 | 
			
		||||
        error(403, 'User is not logged in');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const surveyData = await loadSurvey(surveyId, locals.userId);
 | 
			
		||||
    if (!surveyData) {
 | 
			
		||||
        log('Survey not found or user (%s) does not have access: %s', locals.userId, params.surveyId);
 | 
			
		||||
        error(404, 'Survey not found');
 | 
			
		||||
    }
 | 
			
		||||
    return surveyData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const load: PageServerLoad = async ({ params, locals }) => {
 | 
			
		||||
    return await loadSurveyData(params, locals);
 | 
			
		||||
    return await loadSurveyData(params.surveyId, locals.userId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const actions = {
 | 
			
		||||
    deleteAnswers: async ({ params, locals, request }) => {
 | 
			
		||||
        const survey = await loadSurveyData(params, locals);
 | 
			
		||||
        const survey = await loadSurveyData(params.surveyId, locals.userId);
 | 
			
		||||
 | 
			
		||||
        let formData = await request.formData();
 | 
			
		||||
        const participantId = parseInt(formData.get('participantId')?.toString() ?? '');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,9 +11,9 @@
 | 
			
		|||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import { success } from '$lib/toast';
 | 
			
		||||
	import MarkdownBlock from '$lib/components/MarkdownBlock.svelte';
 | 
			
		||||
	import MenuIcon from '$lib/components/icons/MenuIcon.svelte';
 | 
			
		||||
	import DeleteIcon from '$lib/components/icons/DeleteIcon.svelte';
 | 
			
		||||
	import WarningDialog from '$lib/components/WarningDialog.svelte';
 | 
			
		||||
	import EditIcon from '$lib/components/icons/EditIcon.svelte';
 | 
			
		||||
 | 
			
		||||
	let { data }: { data: PageData } = $props();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<Navbar title={data.title}>
 | 
			
		||||
	<div class="flex w-20 flex-row justify-self-end">
 | 
			
		||||
	<div class="w-30 flex flex-row justify-self-end">
 | 
			
		||||
		<a href="/" class="flex items-center" aria-label="Home" title="Home">
 | 
			
		||||
			<HomeIcon />
 | 
			
		||||
		</a>
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +54,13 @@
 | 
			
		|||
			title="Duplicate"
 | 
			
		||||
			><DuplicateIcon />
 | 
			
		||||
		</a>
 | 
			
		||||
		<a
 | 
			
		||||
			href="{data.id.toString()}/edit"
 | 
			
		||||
			class="ml-2 flex items-center"
 | 
			
		||||
			aria-label="Edit survey"
 | 
			
		||||
			title="Edit survey"
 | 
			
		||||
			><EditIcon />
 | 
			
		||||
		</a>
 | 
			
		||||
		<button
 | 
			
		||||
			onclick={() => {
 | 
			
		||||
				deleteSurveyDialogRef?.showModal();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										72
									
								
								src/routes/(app)/survey/[surveyId]/edit/+page.server.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/routes/(app)/survey/[surveyId]/edit/+page.server.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
import { error, redirect } from '@sveltejs/kit';
 | 
			
		||||
import type { PageServerLoad } from './$types';
 | 
			
		||||
 | 
			
		||||
import debug from 'debug';
 | 
			
		||||
import { fromFormData } from '$lib/survey';
 | 
			
		||||
import { db } from '../../../../../db';
 | 
			
		||||
import { surveyAccessTable, surveyAnswersTable, surveySkillsTable, surveysTable } from '../../../../../db/schema';
 | 
			
		||||
import { eq, inArray } from 'drizzle-orm';
 | 
			
		||||
import { addParticipant, addSkill, loadSurveyData } from '../../../../../db/survey';
 | 
			
		||||
 | 
			
		||||
const log = debug('survey:admin:edit');
 | 
			
		||||
 | 
			
		||||
export const load: PageServerLoad = async ({ params, locals }) => {
 | 
			
		||||
    return await loadSurveyData(params.surveyId, locals.userId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const actions = {
 | 
			
		||||
    default: async ({ request, params, locals }) => {
 | 
			
		||||
        const owner = locals.userId;
 | 
			
		||||
        if (!owner) {
 | 
			
		||||
            error(400, 'User is not logged in');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const formData = await request.formData();
 | 
			
		||||
 | 
			
		||||
        const { participants, title, description, skills } = fromFormData(formData);
 | 
			
		||||
 | 
			
		||||
        if (!title) {
 | 
			
		||||
            error(400, 'Title is required');
 | 
			
		||||
        }
 | 
			
		||||
        if (skills.length === 0) {
 | 
			
		||||
            error(400, 'At least one skill is required');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const survey = await loadSurveyData(params.surveyId, locals.userId);
 | 
			
		||||
 | 
			
		||||
        // update the actual survey object
 | 
			
		||||
        await db.update(surveysTable).set({ title, description }).where(eq(surveysTable.id, survey.id));
 | 
			
		||||
 | 
			
		||||
        // update the skills where applicable
 | 
			
		||||
        for (let idx = 0; idx < Math.max(survey.skills.length, skills.length); idx++) {
 | 
			
		||||
            const newSkill = skills[idx];
 | 
			
		||||
            const oldSkill = survey.skills[idx];
 | 
			
		||||
            if (newSkill && oldSkill) {
 | 
			
		||||
                // this is an update -> change the text only
 | 
			
		||||
                await db.update(surveySkillsTable).set({ title: newSkill.title, description: newSkill.description }).where(eq(surveySkillsTable.id, oldSkill.id));
 | 
			
		||||
            } else if (newSkill && !oldSkill) {
 | 
			
		||||
                // this is a new skill -> insert it into the table
 | 
			
		||||
                await addSkill(survey.id, newSkill.title, newSkill.description);
 | 
			
		||||
            } else if (!newSkill && oldSkill) {
 | 
			
		||||
                // this is a deleted skill -> delete it from the table
 | 
			
		||||
                await db.delete(surveySkillsTable).where(eq(surveySkillsTable.id, oldSkill.id));
 | 
			
		||||
                // if a skill is deleted, also delete any potential answers for that skill
 | 
			
		||||
                await db.delete(surveyAnswersTable).where(eq(surveyAnswersTable.skillId, oldSkill.id));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // update the participants where applicable (unchanged participants are left untouched)
 | 
			
		||||
        const deletedParticipants = survey.participants.filter(participant => !participants.includes(participant.email));
 | 
			
		||||
        const newParticipants = participants.filter(email => !survey.participants.some(candidate => candidate.email === email));
 | 
			
		||||
        // delete all participants no longer part of the survey
 | 
			
		||||
        await db.delete(surveyAccessTable).where(inArray(surveyAccessTable.recepientEmail, deletedParticipants.map(participant => participant.email)));
 | 
			
		||||
        // delete answers from deleted participants
 | 
			
		||||
        await db.delete(surveyAnswersTable).where(inArray(surveyAnswersTable.participantId, deletedParticipants.map(participant => participant.id)));
 | 
			
		||||
        // add any new participants
 | 
			
		||||
        for (const newParticipant of newParticipants) {
 | 
			
		||||
            await addParticipant(survey.id, newParticipant);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        redirect(303, ".");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/routes/(app)/survey/[surveyId]/edit/+page.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/routes/(app)/survey/[surveyId]/edit/+page.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import Navbar from '$lib/components/Navbar.svelte';
 | 
			
		||||
	import SurveyEditForm from '$lib/components/SurveyEditForm.svelte';
 | 
			
		||||
	import type { PageData } from '../$types';
 | 
			
		||||
 | 
			
		||||
	let { data }: { data: PageData } = $props();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Navbar title="Edit survey" />
 | 
			
		||||
{#if data.participants.some((participant) => participant.answers.length > 0)}
 | 
			
		||||
	<div class="bold mx-auto max-w-xl border-2 border-red-700 bg-red-100 text-center text-red-500">
 | 
			
		||||
		<p>There are participants who have already submitted ratings.</p>
 | 
			
		||||
		<p>
 | 
			
		||||
			Changing the structure of this survey (i.e. changing any of the skills) will potentially
 | 
			
		||||
			invalidate their answers. Consider either deleting their answers before changing the skills or
 | 
			
		||||
			leave the skills as is (e.g. only add/remove participants).
 | 
			
		||||
		</p>
 | 
			
		||||
	</div>
 | 
			
		||||
{/if}
 | 
			
		||||
<SurveyEditForm {...data} submitButtonTitle="Save" />
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
import type { Actions, PageServerLoad } from './$types';
 | 
			
		||||
import { error, redirect } from '@sveltejs/kit';
 | 
			
		||||
import { surveyAccessTable, surveySkillsTable, surveysTable } from '../../../../db/schema';
 | 
			
		||||
import { surveysTable } from '../../../../db/schema';
 | 
			
		||||
import { db } from '../../../../db';
 | 
			
		||||
import { generateRandomToken } from '$lib/randomToken';
 | 
			
		||||
import { loadSurvey } from '$lib/queries';
 | 
			
		||||
import { fromFormData } from '$lib/survey';
 | 
			
		||||
import { addParticipant, addSkill, loadSurvey } from '../../../../db/survey';
 | 
			
		||||
 | 
			
		||||
export const load: PageServerLoad = async ({ url, locals }) => {
 | 
			
		||||
    const baseSurveyId = url.searchParams.get('from');
 | 
			
		||||
| 
						 | 
				
			
			@ -17,29 +17,19 @@ export const load: PageServerLoad = async ({ url, locals }) => {
 | 
			
		|||
 | 
			
		||||
export const actions = {
 | 
			
		||||
    default: async (event) => {
 | 
			
		||||
        const formData = await event.request.formData();
 | 
			
		||||
 | 
			
		||||
        const participants = formData.getAll('participants').filter(email => !!email).map(email => email.toString());
 | 
			
		||||
        const title = formData.get('title')?.toString();
 | 
			
		||||
        const description = formData.get('description')?.toString();
 | 
			
		||||
        const skillTitles = formData.getAll('skill');
 | 
			
		||||
        const skillDescriptions = formData.getAll('skill-description');
 | 
			
		||||
 | 
			
		||||
        if (!title) {
 | 
			
		||||
            error(400, 'Title is required');
 | 
			
		||||
        }
 | 
			
		||||
        if (participants.length === 0) {
 | 
			
		||||
            error(400, 'At least one email is required');
 | 
			
		||||
        }
 | 
			
		||||
        const owner = event.locals.userId;
 | 
			
		||||
        if (!owner) {
 | 
			
		||||
            error(400, 'User is not logged in');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const skills = skillTitles.flatMap((title, index) => !!title ? [({
 | 
			
		||||
            title: title.toString(),
 | 
			
		||||
            description: skillDescriptions[index].toString()
 | 
			
		||||
        })] : []);
 | 
			
		||||
        const formData = await event.request.formData();
 | 
			
		||||
 | 
			
		||||
        const { participants, title, description, skills } = fromFormData(formData);
 | 
			
		||||
 | 
			
		||||
        if (!title) {
 | 
			
		||||
            error(400, 'Title is required');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (skills.length === 0) {
 | 
			
		||||
            error(400, 'At least one skill is required');
 | 
			
		||||
| 
						 | 
				
			
			@ -49,10 +39,10 @@ export const actions = {
 | 
			
		|||
 | 
			
		||||
        const surveyId = ids[0].id;
 | 
			
		||||
        for (const participant of participants) {
 | 
			
		||||
            await db.insert(surveyAccessTable).values({ surveyId, recepientEmail: participant, accessToken: generateRandomToken() });
 | 
			
		||||
            await addParticipant(surveyId, participant);
 | 
			
		||||
        }
 | 
			
		||||
        for (const skill of skills) {
 | 
			
		||||
            await db.insert(surveySkillsTable).values({ surveyId, title: skill.title, description: skill.description });
 | 
			
		||||
            await addSkill(surveyId, skill.title, skill.description);
 | 
			
		||||
        }
 | 
			
		||||
        redirect(303, `/survey/${surveyId}`);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,65 +1,10 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import Navbar from '$lib/components/Navbar.svelte';
 | 
			
		||||
	import SurveyEditForm from '$lib/components/SurveyEditForm.svelte';
 | 
			
		||||
	import type { PageData } from './$types';
 | 
			
		||||
 | 
			
		||||
	const { data }: { data: PageData } = $props();
 | 
			
		||||
 | 
			
		||||
	let participants = $state(data?.participants?.length ?? 1);
 | 
			
		||||
	let skills = $state(data?.skills?.length ?? 1);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Navbar title="Create a new survey" />
 | 
			
		||||
<form class="grid grid-cols-2 gap-2 p-4" method="post">
 | 
			
		||||
	<label for="title" class="justify-self-end">Survey Title</label>
 | 
			
		||||
	<input
 | 
			
		||||
		type="text"
 | 
			
		||||
		name="title"
 | 
			
		||||
		id="title"
 | 
			
		||||
		class="min-w-80 justify-self-start"
 | 
			
		||||
		value={data?.title}
 | 
			
		||||
	/>
 | 
			
		||||
	<label for="description" class="justify-self-end">Survey Description</label>
 | 
			
		||||
	<textarea name="description" id="description" class="min-w-80 justify-self-start"
 | 
			
		||||
		>{data?.description}</textarea
 | 
			
		||||
	>
 | 
			
		||||
 | 
			
		||||
	<h2 class="col-span-2 text-2xl">Participants</h2>
 | 
			
		||||
	{#each Array(participants) as _, idx}
 | 
			
		||||
		<label for="participants" class="justify-self-end">Email</label>
 | 
			
		||||
		<input
 | 
			
		||||
			type="email"
 | 
			
		||||
			name="participants"
 | 
			
		||||
			id="participants"
 | 
			
		||||
			class="min-w-80 justify-self-start"
 | 
			
		||||
			value={data?.participants?.[idx]?.email}
 | 
			
		||||
		/>
 | 
			
		||||
	{/each}
 | 
			
		||||
	<button
 | 
			
		||||
		type="button"
 | 
			
		||||
		class="col-span-2 w-40 justify-self-center bg-blue-200"
 | 
			
		||||
		onclick={() => (participants += 1)}>Add participant</button
 | 
			
		||||
	>
 | 
			
		||||
 | 
			
		||||
	<h2 class="col-span-2 text-2xl">Skills</h2>
 | 
			
		||||
	{#each Array(skills) as _, idx}
 | 
			
		||||
		<div class="col-span-2 ml-4 grid grid-cols-2 gap-4">
 | 
			
		||||
			<div class="flex w-full flex-col justify-self-end">
 | 
			
		||||
				<label for="skill" class="text-xs">Skill title</label>
 | 
			
		||||
				<input name="skill" id="skill" class="w-full" value={data?.skills?.[idx]?.title} />
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex w-full flex-col justify-self-start">
 | 
			
		||||
				<label for="skill-description" class="text-xs">Skill description</label>
 | 
			
		||||
				<textarea name="skill-description" id="skill-description" class="min-h-36 w-full"
 | 
			
		||||
					>{data?.skills?.[idx]?.description}</textarea
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{/each}
 | 
			
		||||
	<button
 | 
			
		||||
		type="button"
 | 
			
		||||
		class="col-span-2 w-40 justify-self-center bg-blue-200"
 | 
			
		||||
		onclick={() => (skills += 1)}>Add skill</button
 | 
			
		||||
	>
 | 
			
		||||
 | 
			
		||||
	<button type="submit" class="col-span-2 w-40 justify-self-center bg-slate-200">Create</button>
 | 
			
		||||
</form>
 | 
			
		||||
<SurveyEditForm {...data} submitButtonTitle="Create" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue