From b2082f98798b628f42a8c1774bf0c1cf3140d297 Mon Sep 17 00:00:00 2001 From: Georgi-S <106542204+Georgi-S@users.noreply.github.com> Date: Thu, 12 Dec 2024 21:33:49 +0100 Subject: [PATCH] fix(EditSurveyName): prevent infinite API calls and add seed configuration --- next.config.js | 15 ++ package.json | 4 + prisma/migrations/0_init/migration.sql | 149 ++++++++++++++++++ .../migration.sql | 15 ++ prisma/schema.prisma | 11 +- prisma/seed.ts | 31 +++- src/app/api/survey/[surveyId]/stats/route.ts | 25 --- .../survey/[surveyId]/stats/update/route.ts | 60 ------- src/app/studio/[surveyId]/page.tsx | 73 ++++++++- src/components/buttons/edit-survey-option.tsx | 86 +--------- src/components/forms/create-answer-form.tsx | 63 -------- src/components/forms/create-question-form.tsx | 23 +-- src/components/forms/create-survey-form.tsx | 127 --------------- src/components/forms/delete-survey-form.tsx | 76 --------- src/components/forms/edit/SurveyEditor.tsx | 147 ++++++++++++----- src/components/forms/lists/surveyList.tsx | 2 +- src/components/forms/student-survey-view.tsx | 7 + src/components/ui/alert-dialog-custom.tsx | 50 ++++++ src/components/ui/alert-dialog.tsx | 117 ++++++++++++++ src/lib/api/questionClient.ts | 16 ++ src/lib/api/surveyClient.ts | 13 +- src/lib/apiMiddlewares/withSurvey.ts | 14 +- src/pages/api/question/index.ts | 20 +++ .../api/question/survey/[surveyId]/index.ts | 10 ++ src/pages/api/survey/[surveyId]/index.ts | 14 +- src/pages/api/survey/index.ts | 118 ++++++-------- .../api/surveys/[surveyId]/questions/index.ts | 54 +++++++ .../surveys/[surveyId]/questions/reorder.ts | 39 +++++ src/types/templates.ts | 3 + yarn.lock | 23 ++- 30 files changed, 830 insertions(+), 575 deletions(-) create mode 100644 prisma/migrations/0_init/migration.sql create mode 100644 prisma/migrations/20241212173834_add_question_position/migration.sql delete mode 100644 src/app/api/survey/[surveyId]/stats/route.ts delete mode 100644 src/app/api/survey/[surveyId]/stats/update/route.ts create mode 100644 src/components/ui/alert-dialog-custom.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/pages/api/surveys/[surveyId]/questions/index.ts create mode 100644 src/pages/api/surveys/[surveyId]/questions/reorder.ts diff --git a/next.config.js b/next.config.js index 3313157..52fa5cf 100644 --- a/next.config.js +++ b/next.config.js @@ -12,6 +12,21 @@ const nextConfig = { }); return config; }, + // Deaktiviere API-Routen-Logs im Development-Modus + logging: { + fetches: { + fullUrl: false, + }, + }, + // Deaktiviere Server-Logs + onDemandEntries: { + maxInactiveAge: 25 * 1000, + pagesBufferLength: 2, + }, + // Minimiere Logs + devIndicators: { + buildActivity: false, + }, }; module.exports = nextConfig; diff --git a/package.json b/package.json index 52c1143..44441ac 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,scss,yml,yaml}\"", "seed": "ts-node prisma/seed.ts" }, + "prisma": { + "seed": " ts-node prisma/seed.ts" + }, "dependencies": { "@hello-pangea/dnd": "^17.0.0", "@heroicons/react": "^2.0.18", @@ -21,6 +24,7 @@ "@paypal/react-paypal-js": "^8.7.0", "@prisma/client": "^5.13.0", "@radix-ui/react-accordion": "^1.2.1", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "1.0.4", diff --git a/prisma/migrations/0_init/migration.sql b/prisma/migrations/0_init/migration.sql new file mode 100644 index 0000000..53ed1c7 --- /dev/null +++ b/prisma/migrations/0_init/migration.sql @@ -0,0 +1,149 @@ +-- CreateTable +CREATE TABLE "Survey" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" TEXT NOT NULL, + "updatedAt" TIMESTAMP(3), + "status" TEXT NOT NULL DEFAULT 'draft', + "responseCount" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "Survey_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Question" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "min" TEXT NOT NULL, + "steps" INTEGER NOT NULL, + "max" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + "responseCount" INTEGER NOT NULL DEFAULT 0, + "averageValue" DOUBLE PRECISION NOT NULL DEFAULT 0, + "surveyId" TEXT NOT NULL, + "scaleType" TEXT NOT NULL DEFAULT 'numeric', + "scaleOptions" TEXT[] DEFAULT ARRAY[]::TEXT[], + + CONSTRAINT "Question_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Answer" ( + "id" TEXT NOT NULL, + "questionId" TEXT NOT NULL, + "surveyId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "value" INTEGER NOT NULL, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Answer_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "name" TEXT, + "email" TEXT, + "emailVerified" TIMESTAMP(3), + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastLogin" TIMESTAMP(3), + "role" TEXT NOT NULL DEFAULT 'RESEARCHER', + "updatedAt" TIMESTAMP(3), + "planId" TEXT, + "planActiveUntil" TIMESTAMP(3), + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sessionToken" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Account" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "VerificationToken" ( + "identifier" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "Plan" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "price" DOUBLE PRECISION NOT NULL, + "description" TEXT NOT NULL, + "features" TEXT[], + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3), + + CONSTRAINT "Plan_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); + +-- AddForeignKey +ALTER TABLE "Survey" ADD CONSTRAINT "Survey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Question" ADD CONSTRAINT "Question_surveyId_fkey" FOREIGN KEY ("surveyId") REFERENCES "Survey"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Answer" ADD CONSTRAINT "Answer_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "Question"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Answer" ADD CONSTRAINT "Answer_surveyId_fkey" FOREIGN KEY ("surveyId") REFERENCES "Survey"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_planId_fkey" FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20241212173834_add_question_position/migration.sql b/prisma/migrations/20241212173834_add_question_position/migration.sql new file mode 100644 index 0000000..79ada07 --- /dev/null +++ b/prisma/migrations/20241212173834_add_question_position/migration.sql @@ -0,0 +1,15 @@ +-- AlterTable +ALTER TABLE "Question" ADD COLUMN IF NOT EXISTS "position" INTEGER NOT NULL DEFAULT 0; + +-- Add index on surveyId +CREATE INDEX IF NOT EXISTS "Question_surveyId_idx" ON "Question"("surveyId"); + +-- UpdateData: Set position based on creation date within each survey +WITH numbered_questions AS ( + SELECT id, ROW_NUMBER() OVER (PARTITION BY "surveyId" ORDER BY "createdAt") - 1 as new_position + FROM "Question" +) +UPDATE "Question" q +SET position = nq.new_position +FROM numbered_questions nq +WHERE q.id = nq.id; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d3cf0f2..a7939be 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -28,14 +28,17 @@ model Question { steps Int max String createdAt DateTime @default(now()) - surveyId String updatedAt DateTime? @updatedAt responseCount Int @default(0) averageValue Float @default(0) - scaleType String @default("default") - scaleOptions String[] @default([]) - answers Answer[] + surveyId String survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade) + answers Answer[] + scaleType String @default("numeric") + scaleOptions String[] @default([]) + position Int @default(0) + + @@index([surveyId]) } model Answer { diff --git a/prisma/seed.ts b/prisma/seed.ts index af04ef1..2190e95 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -3,7 +3,9 @@ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function main() { - // Lösche existierende Pläne + // Lösche existierende Daten in der richtigen Reihenfolge + await prisma.survey.deleteMany(); + await prisma.user.deleteMany(); await prisma.plan.deleteMany(); // Erstelle Standard-Pläne @@ -48,10 +50,35 @@ async function main() { }, ]; + // Erstelle Pläne und speichere die IDs + const createdPlans: Record = {}; for (const plan of plans) { - await prisma.plan.create({ + const createdPlan = await prisma.plan.create({ data: plan, }); + createdPlans[createdPlan.name] = createdPlan.id; + } + + // Erstelle Pro-User + const proUsers = [ + { + email: 'semov_georgi@yahoo.de', + name: 'Georgi Semov', + }, + { + email: 't.bartoschek@reedu.de', + name: 'Thomas Bartoschek', + }, + ]; + + for (const user of proUsers) { + await prisma.user.create({ + data: { + ...user, + planId: createdPlans['Professional'], + planActiveUntil: new Date('2025-12-31'), // Plan gültig bis Ende 2025 + }, + }); } console.log('Seed erfolgreich ausgeführt'); diff --git a/src/app/api/survey/[surveyId]/stats/route.ts b/src/app/api/survey/[surveyId]/stats/route.ts deleted file mode 100644 index c74b619..0000000 --- a/src/app/api/survey/[surveyId]/stats/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; - -export async function GET(request: Request, { params }: { params: { surveyId: string } }) { - try { - const { surveyId } = params; - - const questions = await prisma.question.findMany({ - where: { - surveyId: surveyId, - }, - select: { - id: true, - name: true, - averageValue: true, - responseCount: true, - }, - }); - - return NextResponse.json(questions); - } catch (error) { - console.error('Error fetching survey stats:', error); - return NextResponse.json({ error: 'Failed to fetch survey statistics' }, { status: 500 }); - } -} diff --git a/src/app/api/survey/[surveyId]/stats/update/route.ts b/src/app/api/survey/[surveyId]/stats/update/route.ts deleted file mode 100644 index 4a75053..0000000 --- a/src/app/api/survey/[surveyId]/stats/update/route.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { NextResponse } from 'next/server'; -import { prisma } from '@/lib/prisma'; - -export async function POST(request: Request, { params }: { params: { surveyId: string } }) { - try { - const { surveyId } = params; - - // Hole alle Antworten für diese Umfrage - const answers = await prisma.answer.findMany({ - where: { - survey: { - id: surveyId, - }, - }, - include: { - question: true, - }, - }); - - // Berechne die Statistiken - const questionStats = answers.reduce((acc: Record, answer) => { - const questionId = answer.questionId; - if (!acc[questionId]) { - acc[questionId] = { - totalResponses: 0, - averageValue: 0, - values: [] as number[], - }; - } - - acc[questionId].totalResponses++; - acc[questionId].values.push(answer.value); - acc[questionId].averageValue = - acc[questionId].values.reduce((a: number, b: number) => a + b, 0) / - acc[questionId].values.length; - - return acc; - }, {}); - - // Speichere die Statistiken für jede Frage - await prisma.$transaction( - Object.entries(questionStats).map(([questionId, stats]: [string, any]) => { - return prisma.question.update({ - where: { - id: questionId, - }, - data: { - responseCount: stats.totalResponses, - averageValue: stats.averageValue, - }, - }); - }) - ); - - return NextResponse.json({ success: true }); - } catch (error) { - console.error('Error updating survey stats:', error); - return NextResponse.json({ error: 'Failed to update survey statistics' }, { status: 500 }); - } -} diff --git a/src/app/studio/[surveyId]/page.tsx b/src/app/studio/[surveyId]/page.tsx index 09b45de..24a9ef7 100644 --- a/src/app/studio/[surveyId]/page.tsx +++ b/src/app/studio/[surveyId]/page.tsx @@ -1,4 +1,3 @@ -// path: src/app/studio/%5BsurveyId%5D/page.tsx 'use client'; import React from 'react'; @@ -8,8 +7,66 @@ import { EditSurveyName } from '@/components/buttons/edit-survey-option'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Container } from '@/components/ui/layout/Container'; +import { CreateQuestionDialog } from '@/components/forms/create-question-form'; +import { useToast } from '@/hooks/use-toast'; +import { useSession } from 'next-auth/react'; +import { getQuestionsForSurvey } from '@/lib/api/questionClient'; + +export default function Studio({ params }: { params: { surveyId: string; surveyName: string } }) { + const [questions, setQuestions] = React.useState([]); + const { toast } = useToast(); + const { data: session } = useSession(); + + const loadQuestions = React.useCallback(async () => { + if (!session) { + toast({ + title: 'Fehler', + description: 'Bitte melden Sie sich an.', + variant: 'destructive', + }); + return; + } + + try { + const data = await getQuestionsForSurvey(params.surveyId); + setQuestions(data); + } catch (error: any) { + console.error('Error loading questions:', error); + toast({ + title: 'Fehler', + description: error.message || 'Die Fragen konnten nicht geladen werden.', + variant: 'destructive', + }); + } + }, [params.surveyId, session, toast]); + + const handleQuestionCreated = React.useCallback(() => { + console.log('Question created, reloading questions...'); + loadQuestions(); + }, [loadQuestions]); + + React.useEffect(() => { + if (session) { + loadQuestions(); // Initial laden + } + }, [session, loadQuestions]); + + if (!session) { + return ( + +
+ + +

+ Bitte melden Sie sich an, um die Umfrage zu bearbeiten. +

+
+
+
+
+ ); + } -export default function Studio({ params }: { params: { surveyId: string } }) { return (
@@ -29,7 +86,17 @@ export default function Studio({ params }: { params: { surveyId: string } }) { - +
+ +
+ loadQuestions()} + />
diff --git a/src/components/buttons/edit-survey-option.tsx b/src/components/buttons/edit-survey-option.tsx index 5e0de3a..3cb68e4 100644 --- a/src/components/buttons/edit-survey-option.tsx +++ b/src/components/buttons/edit-survey-option.tsx @@ -1,83 +1,5 @@ -// "use client"; -// import React, { useState, useEffect } from "react"; -// import { readSurvey, updateSurvey } from "@/lib/api/surveyClient"; -// import { -// Dialog, -// DialogContent, -// DialogDescription, -// DialogFooter, -// DialogHeader, -// DialogTitle, -// DialogTrigger, -// } from "@/components/ui/dialog"; -// import { Input } from "@/components/ui/input"; -// import { Button } from "../ui/button"; -// import { Label } from "../ui/label"; -// import { PencilIcon } from "@heroicons/react/20/solid"; +/* eslint-disable react-hooks/exhaustive-deps */ -// export function EditSurveyName({ surveyId }: { surveyId: string }) { -// const [surveyName, setSurveyName] = useState(""); -// const [newName, setNewName] = useState(""); -// const [isDialogOpen, setIsDialogOpen] = useState(false); - -// useEffect(() => { -// readSurvey(surveyId) -// .then((survey) => { -// setSurveyName(survey.name); -// setNewName(survey.name); -// }) -// .catch((error) => console.error(error)); -// }, [surveyId]); - -// const handleSave = async () => { -// try { -// await updateSurvey(surveyId, newName); -// setSurveyName(newName); -// setIsDialogOpen(false); -// } catch (error) { -// console.error("Error when updating the survey name", error); -// } -// }; - -// return ( -//
-//
-//

{surveyName}

-//
-//
-// -// -// -// -// -// -// Umfrage umbenennen -// -// Ändern Sie den Namen der Umfrage und speichern Sie die -// Änderungen. -// -// -// -// setNewName(e.target.value)} -// placeholder="Neuer Name der Umfrage" -// /> -// -// -// -// -// -//
-//
-// ); -// } - -// export default EditSurveyName; 'use client'; import React, { useState, useEffect } from 'react'; @@ -128,12 +50,13 @@ export function EditSurveyName({ surveyId }: { surveyId: string }) { name: '', }, }); - useEffect(() => { const fetchSurveyName = async () => { try { + console.log('Starting to fetch survey name...'); setIsLoading(true); const survey = await readSurvey(surveyId); + console.log('Received survey:', survey); setSurveyName(survey.name); form.reset({ name: survey.name }); } catch (error) { @@ -144,12 +67,13 @@ export function EditSurveyName({ surveyId }: { surveyId: string }) { variant: 'destructive', }); } finally { + console.log('Setting isLoading to false'); setIsLoading(false); } }; fetchSurveyName(); - }, [surveyId, form, toast]); + }, [surveyId]); const onSubmit = async (values: FormValues) => { try { diff --git a/src/components/forms/create-answer-form.tsx b/src/components/forms/create-answer-form.tsx index 73ea11b..c80089d 100644 --- a/src/components/forms/create-answer-form.tsx +++ b/src/components/forms/create-answer-form.tsx @@ -1,66 +1,3 @@ -// // path: src/components/forms/create-answer-form.tsx -// import React, { useState } from "react"; -// import { Button } from "../ui/button"; -// import { Slider } from "../ui/slider"; -// import { createAnswer } from "@/lib/api/answerClient"; - -// export function CreateAnswerDialog({ -// questionId, -// steps, -// min, -// max, -// }: { -// questionId: string; -// steps: number; -// min: string; -// max: string; -// }) { -// const [value, setValue] = useState(Number); -// const [isButtonDisabled, setIsButtonDisabled] = useState(false); -// const onSubmitCreate = async () => { -// try { -// console.log(value); -// const answerData = await createAnswer(value, questionId); -// console.log("Answer created:", answerData); -// setIsButtonDisabled(true); -// } catch (error) { -// console.error("Error when creating the answer:", error); -// } -// }; - -// return ( -//
-//
-// {min} -//
{max}
-//
- -// { -// console.log(value); -// setValue(Number(value[0])); -// }} -// className="col-span-3" -// min={1} -// max={steps} -// /> - -// -//
-// ); -// } - -// export default CreateAnswerDialog; 'use client'; import React, { useState, useEffect } from 'react'; diff --git a/src/components/forms/create-question-form.tsx b/src/components/forms/create-question-form.tsx index 11d1d6d..cbd6223 100644 --- a/src/components/forms/create-question-form.tsx +++ b/src/components/forms/create-question-form.tsx @@ -48,7 +48,7 @@ type QuestionFormValues = z.infer; type CreateQuestionProps = { surveyId: string; - handleQuestionCreated: () => void; + handleQuestionCreated: (question: any) => void; }; export function CreateQuestionDialog({ surveyId, handleQuestionCreated }: CreateQuestionProps) { @@ -78,7 +78,7 @@ export function CreateQuestionDialog({ surveyId, handleQuestionCreated }: Create const onSubmit = async (values: QuestionFormValues) => { try { - await createQuestion( + const newQuestion = await createQuestion( values.name, values.description, values.scaleOptions[0].value, @@ -88,13 +88,18 @@ export function CreateQuestionDialog({ surveyId, handleQuestionCreated }: Create values.scaleType, values.scaleOptions.map((option) => option.value) ); - toast({ - title: 'Frage erstellt', - description: 'Die Frage wurde erfolgreich hinzugefügt.', - }); - setIsDialogOpen(false); - handleQuestionCreated(); - form.reset(); + + if (newQuestion) { + toast({ + title: 'Frage erstellt', + description: 'Die Frage wurde erfolgreich hinzugefügt.', + }); + setIsDialogOpen(false); + form.reset(); + // Warte einen kurzen Moment, damit die API Zeit hat, die Änderungen zu verarbeiten + await new Promise((resolve) => setTimeout(resolve, 100)); + handleQuestionCreated(newQuestion); + } } catch (error) { console.error('Fehler beim Erstellen der Frage:', error); toast({ diff --git a/src/components/forms/create-survey-form.tsx b/src/components/forms/create-survey-form.tsx index a88f29e..bcdadab 100644 --- a/src/components/forms/create-survey-form.tsx +++ b/src/components/forms/create-survey-form.tsx @@ -1,130 +1,3 @@ -// // src/components/forms/create-survey-form.tsx - -// "use client"; - -// import React, { useState } from "react"; -// import { useRouter } from "next/navigation"; -// import { useSession } from "next-auth/react"; -// import { zodResolver } from "@hookform/resolvers/zod"; -// import { useForm } from "react-hook-form"; -// import * as z from "zod"; -// import { PlusIcon, Loader2 } from "lucide-react"; -// import { createSurvey } from "@/lib/api/surveyClient"; -// import { -// Dialog, -// DialogContent, -// DialogDescription, -// DialogFooter, -// DialogHeader, -// DialogTitle, -// DialogTrigger, -// } from "@/components/ui/dialog"; -// import { Input } from "@/components/ui/input"; -// import { Button } from "@/components/ui/button"; -// import { Label } from "@/components/ui/label"; -// import { -// Form, -// FormControl, -// FormField, -// FormItem, -// FormLabel, -// FormMessage, -// } from "@/components/ui/form"; -// import { useToast } from "@/hooks/use-toast"; - -// const formSchema = z.object({ -// name: z.string().min(3, { -// message: "Der Umfragename muss mindestens 3 Zeichen lang sein.", -// }), -// }); - -// export function CreateSurveyDialog() { -// const { data: session } = useSession(); -// const router = useRouter(); -// const { toast } = useToast(); -// const [isOpen, setIsOpen] = useState(false); - -// const form = useForm>({ -// resolver: zodResolver(formSchema), -// defaultValues: { -// name: "", -// }, -// }); - -// const onSubmit = async (values: z.infer) => { -// try { -// const surveyData = await createSurvey(values.name); -// toast({ -// title: "Umfrage erstellt", -// description: `Die Umfrage "${values.name}" wurde erfolgreich erstellt.`, -// }); -// setIsOpen(false); -// router.push(`/studio/${surveyData.id}`); -// } catch (error) { -// console.error("Error when creating the survey:", error); -// toast({ -// title: "Fehler", -// description: -// "Die Umfrage konnte nicht erstellt werden. Bitte versuchen Sie es erneut.", -// variant: "destructive", -// }); -// } -// }; - -// return ( -// -// -// {session && ( -// -// )} -// -// -// -// Erstelle eine neue Umfrage -// -// Gib einen Namen für deine neue Umfrage ein. Du kannst ihn später -// noch ändern. -// -// - -//
-// -// ( -// -// Name -// -// -// -// -// -// )} -// /> -// -// -// -// -// -//
-//
-// ); -// } - -// export default CreateSurveyDialog; 'use client'; import React from 'react'; diff --git a/src/components/forms/delete-survey-form.tsx b/src/components/forms/delete-survey-form.tsx index e131d42..3936b3f 100644 --- a/src/components/forms/delete-survey-form.tsx +++ b/src/components/forms/delete-survey-form.tsx @@ -1,79 +1,3 @@ -// // path: src/components/forms/delete-survey-form.tsx -// "use client"; - -// import React, { useState } from "react"; -// import { -// Dialog, -// DialogContent, -// DialogDescription, -// DialogFooter, -// DialogHeader, -// DialogTitle, -// DialogTrigger, -// } from "../ui/dialog"; - -// import { Input } from "@/components/ui/input"; - -// import { Button } from "../ui/button"; -// import { Label } from "../ui/label"; -// import { PlusIcon } from "lucide-react"; -// import { deleteSurvey } from "@/lib/api/surveyClient"; - -// export function DeleteSurveyDialog() { -// const [name, setName] = useState(""); -// const [isDialogOpen, setIsDialogOpen] = useState(false); - -// const onSubmitDelete = async () => { -// try { -// const surveyData = await deleteSurvey(name); // -// console.log("Survey deleted:", surveyData); -// setIsDialogOpen(false); -// } catch (error) { -// console.error("Error when deleting the survey:", error); -// } -// }; - -// return ( -// -// setIsDialogOpen(true)}> -// -// -// -// -// Lösche eine Umfrage. -// -// Gib den Namen der Umfrage ein, die du löschen möchtest. -// -// - -//
-//
-// -// setName(e.target.value)} -// placeholder="Name" -// className="col-span-3" -// /> -//
-//
-// -//
-// -//
-//
-//
-//
-// ); -// } - -// export default DeleteSurveyDialog; 'use client'; import React from 'react'; diff --git a/src/components/forms/edit/SurveyEditor.tsx b/src/components/forms/edit/SurveyEditor.tsx index dc1b031..6479bc7 100644 --- a/src/components/forms/edit/SurveyEditor.tsx +++ b/src/components/forms/edit/SurveyEditor.tsx @@ -25,8 +25,10 @@ import { DialogTrigger, } from '@/components/ui/dialog'; import { Switch } from '@/components/ui/switch'; -import { Question } from '@/types/templates'; +import { Question, QuestionType } from '@/types/templates'; import { useToast } from '@/hooks/use-toast'; +import { deleteQuestion as deleteQuestionApi } from '@/lib/api/questionClient'; +import { DeleteAlertDialog } from '@/components/ui/alert-dialog-custom'; interface SurveyEditorProps { surveyId: string; @@ -35,16 +37,18 @@ interface SurveyEditorProps { } export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: SurveyEditorProps) { - const [questions, setQuestions] = useState(initialQuestions); const [editingQuestion, setEditingQuestion] = useState(null); const { toast } = useToast(); + // Verwende die initialQuestions direkt statt eines lokalen States + const questions = initialQuestions; + const loadQuestions = useCallback(async () => { try { const response = await fetch(`/api/surveys/${surveyId}/questions`); if (!response.ok) throw new Error('Failed to load questions'); const data = await response.json(); - setQuestions(data); + // Kein lokaler State, daher keine Aktualisierung von questions } catch (error) { console.error('Error loading questions:', error); toast({ @@ -61,59 +65,104 @@ export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: Survey const saveQuestions = async (updatedQuestions: Question[]) => { try { - const response = await fetch(`/api/surveys/${surveyId}/questions`, { + const response = await fetch(`/api/surveys/${surveyId}/questions/reorder`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(updatedQuestions), + body: JSON.stringify( + updatedQuestions.map((question, index) => ({ + id: question.id, + position: index, + })) + ), }); - if (!response.ok) throw new Error('Failed to save questions'); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Failed to save questions'); + } - toast({ - title: 'Erfolg', - description: 'Die Änderungen wurden gespeichert.', - }); + if (onSave) { + onSave(updatedQuestions); + } - onSave?.(updatedQuestions); + return true; } catch (error) { console.error('Error saving questions:', error); toast({ title: 'Fehler', - description: 'Die Änderungen konnten nicht gespeichert werden.', + description: 'Die Reihenfolge konnte nicht gespeichert werden.', variant: 'destructive', }); + throw error; } }; - const addQuestion = () => { + const addQuestion = async () => { const newQuestion: Question = { id: Math.random().toString(36).substr(2, 9), type: 'likert', - text: '', + name: '', + description: '', required: true, scale: 5, + text: '', + position: questions.length, }; - const updatedQuestions = [...questions, newQuestion]; - setQuestions(updatedQuestions); - setEditingQuestion(newQuestion); - saveQuestions(updatedQuestions); + + try { + // Kein lokaler State, daher kein optimistisches UI-Update + // Speichern der Fragen + await saveQuestions([...questions, newQuestion]); + // Dialog zum Bearbeiten öffnen + setEditingQuestion(newQuestion); + // Neu laden der Fragen um sicherzustellen, dass alles synchron ist + await loadQuestions(); + toast({ + title: 'Erfolg', + description: 'Neue Frage wurde erstellt.', + }); + } catch (error) { + console.error('Error adding question:', error); + toast({ + title: 'Fehler', + description: 'Die Frage konnte nicht erstellt werden.', + variant: 'destructive', + }); + } }; const updateQuestion = (updatedQuestion: Question) => { const updatedQuestions = questions.map((q) => q.id === updatedQuestion.id ? updatedQuestion : q ); - setQuestions(updatedQuestions); setEditingQuestion(null); saveQuestions(updatedQuestions); }; - const deleteQuestion = (questionId: string) => { - const updatedQuestions = questions.filter((q) => q.id !== questionId); - setQuestions(updatedQuestions); - saveQuestions(updatedQuestions); + const deleteQuestion = async (questionId: string) => { + try { + await deleteQuestionApi(questionId); + const updatedQuestions = questions.filter((q) => q.id !== questionId); + + // Benachrichtige die übergeordnete Komponente über die Änderung + if (onSave) { + onSave(updatedQuestions); + } + + toast({ + title: 'Erfolg', + description: 'Die Frage wurde gelöscht.', + }); + } catch (error) { + console.error('Error deleting question:', error); + toast({ + title: 'Fehler', + description: 'Die Frage konnte nicht gelöscht werden.', + variant: 'destructive', + }); + } }; const onDragEnd = (result: any) => { @@ -123,7 +172,6 @@ export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: Survey const [reorderedItem] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, reorderedItem); - setQuestions(items); saveQuestions(items); }; @@ -138,10 +186,6 @@ export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: Survey Erstelle und organisiere deine Fragen durch Drag & Drop
- @@ -171,10 +215,10 @@ export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: Survey
- Frage {index + 1} + {question.name || 'Unbenannte Frage'} - {question.text || 'Keine Frage eingegeben'} + {question.description || 'Keine Beschreibung'}
@@ -192,14 +236,27 @@ export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: Survey - + */} + deleteQuestion(question.id)} + > + + @@ -231,24 +288,38 @@ export function SurveyEditor({ surveyId, initialQuestions = [], onSave }: Survey
- + + + setEditingQuestion({ + ...editingQuestion, + name: e.target.value, + }) + } + placeholder="Gib hier den Namen der Frage ein..." + /> +
+ +
+