From 3cfe2bff4e0a47b1e3b46a950fe9fb66bcc876ae Mon Sep 17 00:00:00 2001 From: danielswiatek Date: Mon, 3 Feb 2025 08:38:38 +0100 Subject: [PATCH 01/25] feat: implement Unterveranstaltung landing settings and related schemas --- .../migration.sql | 74 +++++++++++++++ api/prisma/migrations/migration_lock.toml | 2 +- api/prisma/schema/Faq.prisma | 4 +- api/prisma/schema/File.prisma | 21 +++-- api/prisma/schema/PublicLanding.prisma | 47 ++++++++++ api/prisma/schema/Unterveranstaltung.prisma | 9 +- .../unterveranstaltung/helpers/create | 0 .../schema/unterveranstaltung.schema.ts | 63 +++++++++++++ .../unterveranstaltungGliederungCreate.ts | 56 +++++++++-- .../unterveranstaltungVerwaltungCreate.ts | 62 +++++++++--- .../FormUnterveranstaltungGeneral.vue | 47 ++++++++-- .../UnterveranstaltungLandingSettings.vue | 94 +++++++++++++------ 12 files changed, 404 insertions(+), 75 deletions(-) create mode 100644 api/prisma/migrations/20250202193845_add_landing_settings/migration.sql create mode 100644 api/prisma/schema/PublicLanding.prisma create mode 100644 api/src/services/unterveranstaltung/helpers/create create mode 100644 api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts diff --git a/api/prisma/migrations/20250202193845_add_landing_settings/migration.sql b/api/prisma/migrations/20250202193845_add_landing_settings/migration.sql new file mode 100644 index 00000000..bf6064f8 --- /dev/null +++ b/api/prisma/migrations/20250202193845_add_landing_settings/migration.sql @@ -0,0 +1,74 @@ +/* + Warnings: + + - The primary key for the `_AnmeldungToMahlzeit` table will be changed. If it partially fails, the table could be left without primary key constraint. + - The primary key for the `_FaqToUnterveranstaltung` table will be changed. If it partially fails, the table could be left without primary key constraint. + - A unique constraint covering the columns `[A,B]` on the table `_AnmeldungToMahlzeit` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[A,B]` on the table `_FaqToUnterveranstaltung` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "_AnmeldungToMahlzeit" DROP CONSTRAINT "_AnmeldungToMahlzeit_AB_pkey"; + +-- AlterTable +ALTER TABLE "_FaqToUnterveranstaltung" DROP CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey"; + +-- CreateTable +CREATE TABLE "UnterveranstaltungLandingSettings" ( + "id" SERIAL NOT NULL, + "unterveranstaltungId" INTEGER NOT NULL, + "heroTitle" TEXT NOT NULL, + "heroSubtitle" TEXT NOT NULL, + "eventDetailsTitle" TEXT NOT NULL, + "eventDetailsContent" TEXT NOT NULL, + "miscellaneousVisible" BOOLEAN, + "miscellaneousTitle" TEXT, + "faqVisible" BOOLEAN, + "instagramVisible" BOOLEAN, + "instagramUrl" TEXT, + "facebookVisible" BOOLEAN, + "facebookUrl" TEXT, + + CONSTRAINT "UnterveranstaltungLandingSettings_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UnterveranstaltungLandingImages" ( + "id" SERIAL NOT NULL, + "name" TEXT, + "unterveranstaltungLandingSettingsId" INTEGER, + "fileId" TEXT NOT NULL, + + CONSTRAINT "UnterveranstaltungLandingImages_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UnterveranstaltungLandingMiscellaneous" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "conent" TEXT NOT NULL, + "unterveranstaltungLandingSettingsId" INTEGER, + + CONSTRAINT "UnterveranstaltungLandingMiscellaneous_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "UnterveranstaltungLandingImages_fileId_key" ON "UnterveranstaltungLandingImages"("fileId"); + +-- CreateIndex +CREATE UNIQUE INDEX "_AnmeldungToMahlzeit_AB_unique" ON "_AnmeldungToMahlzeit"("A", "B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_FaqToUnterveranstaltung_AB_unique" ON "_FaqToUnterveranstaltung"("A", "B"); + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingSettings" ADD CONSTRAINT "UnterveranstaltungLandingSettings_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingImages" ADD CONSTRAINT "UnterveranstaltungLandingImages_unterveranstaltungLandingS_fkey" FOREIGN KEY ("unterveranstaltungLandingSettingsId") REFERENCES "UnterveranstaltungLandingSettings"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingImages" ADD CONSTRAINT "UnterveranstaltungLandingImages_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "File"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingMiscellaneous" ADD CONSTRAINT "UnterveranstaltungLandingMiscellaneous_unterveranstaltungL_fkey" FOREIGN KEY ("unterveranstaltungLandingSettingsId") REFERENCES "UnterveranstaltungLandingSettings"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/migration_lock.toml b/api/prisma/migrations/migration_lock.toml index 648c57fd..fbffa92c 100644 --- a/api/prisma/migrations/migration_lock.toml +++ b/api/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) +# It should be added in your version-control system (i.e. Git) provider = "postgresql" \ No newline at end of file diff --git a/api/prisma/schema/Faq.prisma b/api/prisma/schema/Faq.prisma index 335d3e02..9bd4186b 100644 --- a/api/prisma/schema/Faq.prisma +++ b/api/prisma/schema/Faq.prisma @@ -6,9 +6,9 @@ model FaqCategory { model Faq { id Int @id @default(autoincrement()) - categoryId Int - category FaqCategory @relation(fields: [categoryId], references: [id]) question String answer String unterveranstaltung Unterveranstaltung[] + FaqCategory FaqCategory? @relation(fields: [faqCategoryId], references: [id]) + faqCategoryId Int? } diff --git a/api/prisma/schema/File.prisma b/api/prisma/schema/File.prisma index 0e2011ab..15f8768c 100644 --- a/api/prisma/schema/File.prisma +++ b/api/prisma/schema/File.prisma @@ -4,14 +4,15 @@ enum FileProvider { } model File { - id String @id @unique @default(uuid()) - createdAt DateTime @default(now()) - uploaded Boolean @default(false) - uploadedAt DateTime? - provider FileProvider - key String @unique() - filename String? - mimetype String? - UnterveranstaltungDocument UnterveranstaltungDocument? - Persons Person[] + id String @id @unique @default(uuid()) + createdAt DateTime @default(now()) + uploaded Boolean @default(false) + uploadedAt DateTime? + provider FileProvider + key String @unique() + filename String? + mimetype String? + UnterveranstaltungDocument UnterveranstaltungDocument? + Persons Person[] + UnterveranstaltungLandingImages UnterveranstaltungLandingImages[] } diff --git a/api/prisma/schema/PublicLanding.prisma b/api/prisma/schema/PublicLanding.prisma new file mode 100644 index 00000000..1fdc107e --- /dev/null +++ b/api/prisma/schema/PublicLanding.prisma @@ -0,0 +1,47 @@ +model UnterveranstaltungLandingSettings { + id Int @id @default(autoincrement()) + + unterveranstaltungId Int + unterveranstaltung Unterveranstaltung @relation(fields: [unterveranstaltungId], references: [id]) + + heroTitle String + heroSubtitle String + heroImages UnterveranstaltungLandingImages[] + + eventDetailsTitle String + eventDetailsContent String + + miscellaneousVisible Boolean? + miscellaneousTitle String? + miscellaneousItems UnterveranstaltungLandingMiscellaneous[] + + faqVisible Boolean? + faqEmail String? + + instagramVisible Boolean? + instagramUrl String? + + facebookVisible Boolean? + facebookUrl String? +} + +model UnterveranstaltungLandingImages { + id Int @id @default(autoincrement()) + name String? + + UnterveranstaltungLandingSettings UnterveranstaltungLandingSettings? @relation(fields: [unterveranstaltungLandingSettingsId], references: [id]) + unterveranstaltungLandingSettingsId Int? + + file File @relation(fields: [fileId], references: [id]) + fileId String @unique +} + +model UnterveranstaltungLandingMiscellaneous { + id Int @id @default(autoincrement()) + + title String + content String + + UnterveranstaltungLandingSettings UnterveranstaltungLandingSettings? @relation(fields: [unterveranstaltungLandingSettingsId], references: [id]) + unterveranstaltungLandingSettingsId Int? +} diff --git a/api/prisma/schema/Unterveranstaltung.prisma b/api/prisma/schema/Unterveranstaltung.prisma index 94eacf0a..735d4e44 100644 --- a/api/prisma/schema/Unterveranstaltung.prisma +++ b/api/prisma/schema/Unterveranstaltung.prisma @@ -4,22 +4,23 @@ enum UnterveranstaltungType { } model Unterveranstaltung { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) maxTeilnehmende Int teilnahmegebuehr Int meldebeginn DateTime meldeschluss DateTime veranstaltungId Int - veranstaltung Veranstaltung @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) + veranstaltung Veranstaltung @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) gliederungId Int - gliederung Gliederung @relation(fields: [gliederungId], references: [id], onDelete: Cascade) + gliederung Gliederung @relation(fields: [gliederungId], references: [id], onDelete: Cascade) Anmeldung Anmeldung[] beschreibung String? bedingungen String? - type UnterveranstaltungType @default(GLIEDERUNG) + type UnterveranstaltungType @default(GLIEDERUNG) customFields CustomField[] documents UnterveranstaltungDocument[] faq Faq[] + landingSettings UnterveranstaltungLandingSettings[] } model UnterveranstaltungDocument { diff --git a/api/src/services/unterveranstaltung/helpers/create b/api/src/services/unterveranstaltung/helpers/create new file mode 100644 index 00000000..e69de29b diff --git a/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts b/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts new file mode 100644 index 00000000..4edee0f3 --- /dev/null +++ b/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts @@ -0,0 +1,63 @@ +import { z } from 'zod' + +export const unterveranstaltungCreateSchema = z.strictObject({ + veranstaltungId: z.number().int(), + maxTeilnehmende: z.number().int(), + teilnahmegebuehr: z.number({ description: 'In Cent' }).int(), + meldebeginn: z.date(), + meldeschluss: z.date(), + beschreibung: z.string().optional(), + bedingungen: z.string().optional(), +}) + +export type TUnterveranstaltungCreateSchema = z.infer + +export const unterveranstaltungLandingSchema = z.strictObject({ + hero: z.strictObject({ + title: z.string(), + subtitle: z.string(), + images: z + .array( + z.strictObject({ + name: z.string(), + fileId: z.string().uuid(), + }) + ) + .optional(), + }), + eventDetails: z.strictObject({ + title: z.string(), + content: z.string(), + }), + miscellaneous: z + .strictObject({ + visible: z.boolean(), + title: z.string().optional(), + subtitle: z.string().optional(), + items: z + .array( + z.strictObject({ + title: z.string(), + content: z.string(), + }) + ) + .optional(), + }) + .optional(), + faq: z + .strictObject({ + visible: z.boolean(), + email: z.string().email().optional(), + items: z + .array( + z.strictObject({ + question: z.string(), + answer: z.string(), + }) + ) + .optional(), + }) + .optional(), +}) + +export type TUnterveranstaltungLandingSchema = z.infer diff --git a/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts b/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts index c37879f1..5778d8e9 100644 --- a/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts +++ b/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts @@ -5,20 +5,14 @@ import z from 'zod' import prisma from '../../prisma.js' import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' +import { unterveranstaltungCreateSchema, unterveranstaltungLandingSchema } from './schema/unterveranstaltung.schema.js' export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutateProcedure({ key: 'gliederungCreate', roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ - data: z.strictObject({ - veranstaltungId: z.number().int(), - maxTeilnehmende: z.number().int(), - teilnahmegebuehr: z.number({ description: 'In Cent' }).int(), - meldebeginn: z.date(), - meldeschluss: z.date(), - beschreibung: z.string().optional(), - bedingungen: z.string().optional(), - }), + data: unterveranstaltungCreateSchema, + landingSettings: unterveranstaltungLandingSchema, }), async handler(options) { // check logged in user is admin of gliederung @@ -46,7 +40,8 @@ export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutate code: 'BAD_REQUEST', }) } - return prisma.unterveranstaltung.create({ + + const unterveranstaltung = await prisma.unterveranstaltung.create({ data: { ...options.input.data, type: 'GLIEDERUNG', @@ -56,5 +51,46 @@ export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutate id: true, }, }) + + const landingSettings = await prisma.unterveranstaltungLandingSettings.create({ + data: { + unterveranstaltungId: unterveranstaltung.id, + heroTitle: options.input.landingSettings.hero.title, + heroSubtitle: options.input.landingSettings.hero.subtitle, + eventDetailsTitle: options.input.landingSettings.eventDetails.title, + eventDetailsContent: options.input.landingSettings.eventDetails.content, + + miscellaneousVisible: options.input.landingSettings.miscellaneous?.visible ?? false, + miscellaneousTitle: options.input.landingSettings.miscellaneous?.title, + miscellaneousItems: options.input.landingSettings.miscellaneous?.items + ? { + createMany: { + data: options.input.landingSettings.miscellaneous.items.map((item) => ({ + title: item.title, + content: item.content, + })), + }, + } + : undefined, + faqVisible: options.input.landingSettings.faq?.visible ?? false, + faqEmail: options.input.landingSettings.faq?.email, + }, + select: { + id: true, + }, + }) + + const faq = await prisma.faq.createMany({ + data: options.input.landingSettings.faq?.items + ? options.input.landingSettings.faq.items.map((item) => ({ + unterveranstaltungId: unterveranstaltung.id, + question: item.question, + answer: item.answer, + category: 'DEFAULT', + })) + : [], + }) + + return { unterveranstaltung, landingSettings, faq } }, }) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts index 26030020..eac21255 100644 --- a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts +++ b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts @@ -3,29 +3,67 @@ import z from 'zod' import prisma from '../../prisma.js' import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { unterveranstaltungCreateSchema, unterveranstaltungLandingSchema } from './schema/unterveranstaltung.schema.js' + +const unterveranstaltungVerwaltungCreateSchema = unterveranstaltungCreateSchema.extend({ + gliederungId: z.number().int(), + type: z.nativeEnum(UnterveranstaltungType), +}) export const unterveranstaltungVerwaltungCreateProcedure = defineProtectedMutateProcedure({ key: 'verwaltungCreate', roleIds: [Role.ADMIN], inputSchema: z.strictObject({ - data: z.strictObject({ - veranstaltungId: z.number().int(), - maxTeilnehmende: z.number().int(), - teilnahmegebuehr: z.number({ description: 'In Cent' }).int(), - meldebeginn: z.date(), - meldeschluss: z.date(), - gliederungId: z.number().int(), - type: z.nativeEnum(UnterveranstaltungType), - beschreibung: z.string().optional(), - bedingungen: z.string().optional(), - }), + data: unterveranstaltungVerwaltungCreateSchema, + landingSettings: unterveranstaltungLandingSchema, }), async handler(options) { - return prisma.unterveranstaltung.create({ + const unterveranstaltung = await prisma.unterveranstaltung.create({ data: options.input.data, select: { id: true, }, }) + + await prisma.unterveranstaltungLandingSettings.create({ + data: { + unterveranstaltungId: unterveranstaltung.id, + heroTitle: options.input.landingSettings.hero.title, + heroSubtitle: options.input.landingSettings.hero.subtitle, + eventDetailsTitle: options.input.landingSettings.eventDetails.title, + eventDetailsContent: options.input.landingSettings.eventDetails.content, + + miscellaneousVisible: options.input.landingSettings.miscellaneous?.visible ?? false, + miscellaneousTitle: options.input.landingSettings.miscellaneous?.title, + miscellaneousItems: options.input.landingSettings.miscellaneous?.items + ? { + createMany: { + data: options.input.landingSettings.miscellaneous.items.map((item) => ({ + title: item.title, + content: item.content, + })), + }, + } + : undefined, + faqVisible: options.input.landingSettings.faq?.visible ?? false, + faqEmail: options.input.landingSettings.faq?.email, + }, + select: { + id: true, + }, + }) + + await prisma.faq.createMany({ + data: options.input.landingSettings.faq?.items + ? options.input.landingSettings.faq.items.map((item) => ({ + unterveranstaltungId: unterveranstaltung.id, + question: item.question, + answer: item.answer, + category: 'DEFAULT', + })) + : [], + }) + + return unterveranstaltung }, }) diff --git a/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue b/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue index 17748c84..cb6abcfe 100644 --- a/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue +++ b/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue @@ -18,7 +18,7 @@ import router from '@/router' import type { RouterInput } from '@codeanker/api' import { UnterveranstaltungTypeMapping, getEnumOptions } from '@codeanker/api' import { ValidateForm } from '@codeanker/validation' -import UnterveranstaltungLandingSettings from './UnterveranstaltungLandingSettings.vue' +import UnterveranstaltungLandingSettings, { type ILandingSettings } from './UnterveranstaltungLandingSettings.vue' const props = defineProps<{ unterveranstaltung?: any @@ -27,6 +27,35 @@ const props = defineProps<{ onUpdate?: () => void }>() +const unterveranstaltungId = props.unterveranstaltung?.id +const gliederung = ref(props.unterveranstaltung?.gliederung) +const documents = ref(props.unterveranstaltung?.documents || []) +const deletedDocumentIds = ref([]) +const landingSettings = ref( + props.unterveranstaltung?.landingSettings || { + hero: { + title: undefined, + subtitle: undefined, + images: [], + }, + eventDetails: { + title: null, + content: null, + }, + miscellaneous: { + visible: false, + title: undefined, + subtitle: undefined, + items: [], + }, + faq: { + visible: false, + email: undefined, + items: [], + }, + } +) + const unterveranstaltungCopy = ref({ beschreibung: props.unterveranstaltung?.beschreibung, bedingungen: props.unterveranstaltung?.bedingungen, @@ -39,11 +68,6 @@ const unterveranstaltungCopy = ref({ type: props.unterveranstaltung?.type, }) -const unterveranstaltungId = props.unterveranstaltung?.id -const gliederung = ref(props.unterveranstaltung?.gliederung) -const documents = ref(props.unterveranstaltung?.documents || []) -const deletedDocumentIds = ref([]) - // Wird benötig damit man direkt von einer Veranstaltung eine Unterveranstaltung anlegen kann ohne diese extra auswählen zu müssen if (props.mode === 'create') { unterveranstaltungCopy.value.veranstaltungId = props?.veranstaltungId @@ -83,12 +107,16 @@ const { unterveranstaltungCopy.value.gliederungId = gliederung.value.id await apiClient.unterveranstaltung.verwaltungCreate.mutate({ data: unterveranstaltungCopy.value as unknown as RouterInput['unterveranstaltung']['verwaltungCreate']['data'], + landingSettings: + landingSettings.value as unknown as RouterInput['unterveranstaltung']['verwaltungCreate']['landingSettings'], }) } else { delete unterveranstaltungCopy.value.gliederungId delete unterveranstaltungCopy.value.type await apiClient.unterveranstaltung.gliederungCreate.mutate({ data: unterveranstaltungCopy.value as unknown as RouterInput['unterveranstaltung']['gliederungCreate']['data'], + landingSettings: + landingSettings.value as unknown as RouterInput['unterveranstaltung']['gliederungCreate']['landingSettings'], }) } router.back() @@ -123,6 +151,7 @@ const { updateDocuments, deleteDocumentIds: deletedDocumentIds.value, } as RouterInput['unterveranstaltung']['verwaltungPatch']['data'], + landingSettings: landingSettings.value, }) } else { delete unterveranstaltungCopy.value.gliederungId @@ -139,6 +168,7 @@ const { updateDocuments, deleteDocumentIds: deletedDocumentIds.value, } as RouterInput['unterveranstaltung']['gliederungPatch']['data'], + landingSettings: landingSettings.value, }) } @@ -392,7 +422,10 @@ function deleteDocument(document, index) { - +
+
{{ model }}
@@ -56,7 +92,7 @@ const landingSettings = ref({
@@ -118,16 +154,16 @@ const landingSettings = ref({
Date: Mon, 3 Feb 2025 16:57:48 +0100 Subject: [PATCH 02/25] feat: faq --- .../20250201222528_faqs/migration.sql | 43 ------ .../migration.sql | 61 ++++++-- api/prisma/migrations/migration_lock.toml | 2 +- api/prisma/schema/Faq.prisma | 22 ++- api/prisma/schema/Unterveranstaltung.prisma | 3 +- api/prisma/seeders/veranstaltung.ts | 28 +++- api/src/services/faqs/faqCreateProcedure.ts | 41 ++++++ api/src/services/faqs/faqDeleteProcecure.ts | 27 ++++ api/src/services/faqs/faqListProcedure.ts | 32 ++++- api/src/services/faqs/faqUpdateProcedure.ts | 39 ++++++ api/src/services/faqs/faqs.router.ts | 11 +- api/src/services/faqs/faqs.schema.ts | 9 ++ api/src/services/index.ts | 2 + .../unterveranstaltung/helpers/create | 0 .../schema/unterveranstaltung.schema.ts | 8 -- .../unterveranstaltungGliederungCreate.ts | 47 +++---- .../unterveranstaltungVerwaltungCreate.ts | 13 -- frontend/src/assets/main.scss | 4 + frontend/src/components/Abbr.vue | 27 ++++ .../BasicInputs/components/Typeahead.vue | 10 +- .../src/components/UIComponents/Modal.vue | 4 +- .../views/Anmeldung/components/PublicFAQ.vue | 67 +++++---- frontend/src/views/FAQs/FAQFormModal.vue | 132 ++++++++++++++++++ frontend/src/views/FAQs/FAQList.vue | 61 ++++++++ .../UnterveranstaltungDetail.vue | 46 ++++-- 25 files changed, 575 insertions(+), 164 deletions(-) delete mode 100644 api/prisma/migrations/20250201222528_faqs/migration.sql rename api/prisma/migrations/{20250202193845_add_landing_settings => 20250203160020_landing_settings}/migration.sql (55%) create mode 100644 api/src/services/faqs/faqCreateProcedure.ts create mode 100644 api/src/services/faqs/faqDeleteProcecure.ts create mode 100644 api/src/services/faqs/faqUpdateProcedure.ts create mode 100644 api/src/services/faqs/faqs.schema.ts delete mode 100644 api/src/services/unterveranstaltung/helpers/create create mode 100644 frontend/src/components/Abbr.vue create mode 100644 frontend/src/views/FAQs/FAQFormModal.vue create mode 100644 frontend/src/views/FAQs/FAQList.vue diff --git a/api/prisma/migrations/20250201222528_faqs/migration.sql b/api/prisma/migrations/20250201222528_faqs/migration.sql deleted file mode 100644 index 50fd77bc..00000000 --- a/api/prisma/migrations/20250201222528_faqs/migration.sql +++ /dev/null @@ -1,43 +0,0 @@ --- DropForeignKey -ALTER TABLE "Person" DROP CONSTRAINT "Person_photoId_fkey"; - --- CreateTable -CREATE TABLE "FaqCategory" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - - CONSTRAINT "FaqCategory_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Faq" ( - "id" SERIAL NOT NULL, - "categoryId" INTEGER NOT NULL, - "question" TEXT NOT NULL, - "answer" TEXT NOT NULL, - - CONSTRAINT "Faq_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "_FaqToUnterveranstaltung" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL, - - CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateIndex -CREATE INDEX "_FaqToUnterveranstaltung_B_index" ON "_FaqToUnterveranstaltung"("B"); - --- AddForeignKey -ALTER TABLE "Faq" ADD CONSTRAINT "Faq_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "FaqCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Person" ADD CONSTRAINT "Person_photoId_fkey" FOREIGN KEY ("photoId") REFERENCES "File"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_A_fkey" FOREIGN KEY ("A") REFERENCES "Faq"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_B_fkey" FOREIGN KEY ("B") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20250202193845_add_landing_settings/migration.sql b/api/prisma/migrations/20250203160020_landing_settings/migration.sql similarity index 55% rename from api/prisma/migrations/20250202193845_add_landing_settings/migration.sql rename to api/prisma/migrations/20250203160020_landing_settings/migration.sql index bf6064f8..233cb603 100644 --- a/api/prisma/migrations/20250202193845_add_landing_settings/migration.sql +++ b/api/prisma/migrations/20250203160020_landing_settings/migration.sql @@ -1,17 +1,24 @@ -/* - Warnings: +-- DropForeignKey +ALTER TABLE "Person" DROP CONSTRAINT "Person_photoId_fkey"; - - The primary key for the `_AnmeldungToMahlzeit` table will be changed. If it partially fails, the table could be left without primary key constraint. - - The primary key for the `_FaqToUnterveranstaltung` table will be changed. If it partially fails, the table could be left without primary key constraint. - - A unique constraint covering the columns `[A,B]` on the table `_AnmeldungToMahlzeit` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[A,B]` on the table `_FaqToUnterveranstaltung` will be added. If there are existing duplicate values, this will fail. +-- CreateTable +CREATE TABLE "faq_categories" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "unterveranstaltungId" INTEGER NOT NULL, -*/ --- AlterTable -ALTER TABLE "_AnmeldungToMahlzeit" DROP CONSTRAINT "_AnmeldungToMahlzeit_AB_pkey"; + CONSTRAINT "faq_categories_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "faqs" ( + "id" SERIAL NOT NULL, + "question" TEXT NOT NULL, + "answer" TEXT NOT NULL, + "categoryId" INTEGER NOT NULL, --- AlterTable -ALTER TABLE "_FaqToUnterveranstaltung" DROP CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey"; + CONSTRAINT "faqs_pkey" PRIMARY KEY ("id") +); -- CreateTable CREATE TABLE "UnterveranstaltungLandingSettings" ( @@ -24,6 +31,7 @@ CREATE TABLE "UnterveranstaltungLandingSettings" ( "miscellaneousVisible" BOOLEAN, "miscellaneousTitle" TEXT, "faqVisible" BOOLEAN, + "faqEmail" TEXT, "instagramVisible" BOOLEAN, "instagramUrl" TEXT, "facebookVisible" BOOLEAN, @@ -46,20 +54,37 @@ CREATE TABLE "UnterveranstaltungLandingImages" ( CREATE TABLE "UnterveranstaltungLandingMiscellaneous" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, - "conent" TEXT NOT NULL, + "content" TEXT NOT NULL, "unterveranstaltungLandingSettingsId" INTEGER, CONSTRAINT "UnterveranstaltungLandingMiscellaneous_pkey" PRIMARY KEY ("id") ); +-- CreateTable +CREATE TABLE "_FaqToUnterveranstaltung" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL, + + CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey" PRIMARY KEY ("A","B") +); + -- CreateIndex -CREATE UNIQUE INDEX "UnterveranstaltungLandingImages_fileId_key" ON "UnterveranstaltungLandingImages"("fileId"); +CREATE UNIQUE INDEX "faq_categories_name_unterveranstaltungId_key" ON "faq_categories"("name", "unterveranstaltungId"); -- CreateIndex -CREATE UNIQUE INDEX "_AnmeldungToMahlzeit_AB_unique" ON "_AnmeldungToMahlzeit"("A", "B"); +CREATE UNIQUE INDEX "UnterveranstaltungLandingImages_fileId_key" ON "UnterveranstaltungLandingImages"("fileId"); -- CreateIndex -CREATE UNIQUE INDEX "_FaqToUnterveranstaltung_AB_unique" ON "_FaqToUnterveranstaltung"("A", "B"); +CREATE INDEX "_FaqToUnterveranstaltung_B_index" ON "_FaqToUnterveranstaltung"("B"); + +-- AddForeignKey +ALTER TABLE "faq_categories" ADD CONSTRAINT "faq_categories_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "faqs" ADD CONSTRAINT "faqs_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "faq_categories"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Person" ADD CONSTRAINT "Person_photoId_fkey" FOREIGN KEY ("photoId") REFERENCES "File"("id") ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "UnterveranstaltungLandingSettings" ADD CONSTRAINT "UnterveranstaltungLandingSettings_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; @@ -72,3 +97,9 @@ ALTER TABLE "UnterveranstaltungLandingImages" ADD CONSTRAINT "Unterveranstaltung -- AddForeignKey ALTER TABLE "UnterveranstaltungLandingMiscellaneous" ADD CONSTRAINT "UnterveranstaltungLandingMiscellaneous_unterveranstaltungL_fkey" FOREIGN KEY ("unterveranstaltungLandingSettingsId") REFERENCES "UnterveranstaltungLandingSettings"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_A_fkey" FOREIGN KEY ("A") REFERENCES "faqs"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_B_fkey" FOREIGN KEY ("B") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/migration_lock.toml b/api/prisma/migrations/migration_lock.toml index fbffa92c..648c57fd 100644 --- a/api/prisma/migrations/migration_lock.toml +++ b/api/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# It should be added in your version-control system (e.g., Git) provider = "postgresql" \ No newline at end of file diff --git a/api/prisma/schema/Faq.prisma b/api/prisma/schema/Faq.prisma index 9bd4186b..1968ba92 100644 --- a/api/prisma/schema/Faq.prisma +++ b/api/prisma/schema/Faq.prisma @@ -1,14 +1,24 @@ model FaqCategory { id Int @id @default(autoincrement()) name String - FAQ Faq[] + + unterveranstaltungId Int + unterveranstaltung Unterveranstaltung @relation(fields: [unterveranstaltungId], references: [id]) + faqs Faq[] + + @@unique([name, unterveranstaltungId]) + @@map("faq_categories") } model Faq { - id Int @id @default(autoincrement()) - question String - answer String + id Int @id @default(autoincrement()) + question String + answer String + + categoryId Int + category FaqCategory @relation(fields: [categoryId], references: [id]) + unterveranstaltung Unterveranstaltung[] - FaqCategory FaqCategory? @relation(fields: [faqCategoryId], references: [id]) - faqCategoryId Int? + + @@map("faqs") } diff --git a/api/prisma/schema/Unterveranstaltung.prisma b/api/prisma/schema/Unterveranstaltung.prisma index 735d4e44..d76b51e9 100644 --- a/api/prisma/schema/Unterveranstaltung.prisma +++ b/api/prisma/schema/Unterveranstaltung.prisma @@ -19,8 +19,9 @@ model Unterveranstaltung { type UnterveranstaltungType @default(GLIEDERUNG) customFields CustomField[] documents UnterveranstaltungDocument[] - faq Faq[] landingSettings UnterveranstaltungLandingSettings[] + faqs Faq[] + faqCategories FaqCategory[] } model UnterveranstaltungDocument { diff --git a/api/prisma/seeders/veranstaltung.ts b/api/prisma/seeders/veranstaltung.ts index 4aa4c608..f99a9bb7 100644 --- a/api/prisma/seeders/veranstaltung.ts +++ b/api/prisma/seeders/veranstaltung.ts @@ -4,7 +4,7 @@ import { PrismaClient } from '@prisma/client' import logActivity from '../../src/util/activity.js' import type { Seeder } from './index.js' -import {dayjs} from '@codeanker/helpers' +import { dayjs } from '@codeanker/helpers' const createVeranstaltung: Seeder = async (prisma: PrismaClient) => { const beginn = dayjs().add(1, 'month') @@ -58,6 +58,32 @@ const createVeranstaltung: Seeder = async (prisma: PrismaClient) => { }, }, }, + select: { + id: true, + unterveranstaltungen: { + select: { + id: true, + }, + }, + }, + }) + + const faqCategories = await prisma.faqCategory.createManyAndReturn({ + data: Array(3) + .fill(0) + .map(() => ({ + name: faker.color.human(), + unterveranstaltungId: veranstaltung.unterveranstaltungen[0]!.id, + })), + }) + await prisma.faq.createMany({ + data: Array(9) + .fill(0) + .map(() => ({ + question: faker.commerce.productAdjective(), + answer: faker.commerce.productDescription(), + categoryId: faqCategories[Math.floor(Math.random() * faqCategories.length)]!.id, + })), }) await prisma.customField.create({ diff --git a/api/src/services/faqs/faqCreateProcedure.ts b/api/src/services/faqs/faqCreateProcedure.ts new file mode 100644 index 00000000..2f682493 --- /dev/null +++ b/api/src/services/faqs/faqCreateProcedure.ts @@ -0,0 +1,41 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { faqSchema } from './faqs.schema.js' + +export const faqCreateProcedure = defineProtectedMutateProcedure({ + key: 'create', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.strictObject({ + unterveranstaltungId: z.number().int(), + faq: faqSchema, + }), + handler: async ({ input: { faq, unterveranstaltungId } }) => { + await prisma.faq.create({ + data: { + question: faq.question, + answer: faq.answer, + unterveranstaltung: { + connect: { + id: unterveranstaltungId, + }, + }, + category: { + connectOrCreate: { + create: { + name: faq.category, + unterveranstaltungId, + }, + where: { + name_unterveranstaltungId: { + name: faq.category, + unterveranstaltungId, + }, + }, + }, + }, + }, + }) + }, +}) diff --git a/api/src/services/faqs/faqDeleteProcecure.ts b/api/src/services/faqs/faqDeleteProcecure.ts new file mode 100644 index 00000000..2def92fa --- /dev/null +++ b/api/src/services/faqs/faqDeleteProcecure.ts @@ -0,0 +1,27 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' + +export const faqDeleteProcedure = definePublicMutateProcedure({ + key: 'delete', + inputSchema: z.number().int(), + handler: async ({ input }) => { + await prisma.faq.delete({ + where: { + id: input, + }, + }) + + // delete empty categories + await prisma.faqCategory.deleteMany({ + where: { + faqs: { + every: { + id: input, + }, + }, + }, + }) + }, +}) diff --git a/api/src/services/faqs/faqListProcedure.ts b/api/src/services/faqs/faqListProcedure.ts index afd3b78c..81b100d0 100644 --- a/api/src/services/faqs/faqListProcedure.ts +++ b/api/src/services/faqs/faqListProcedure.ts @@ -24,7 +24,12 @@ export async function listFaqs(unterveranstaltungId: number) { }, }) - return groupBy(list, ({ category }) => category.name) + const groups = groupBy( + list.map((v) => ({ ...v, category: v.category.name })), + ({ category }) => category + ) + + return Object.fromEntries(Object.entries(groups).sort(([a], [b]) => a.localeCompare(b))) } export const faqListProcedure = definePublicQueryProcedure({ @@ -34,3 +39,28 @@ export const faqListProcedure = definePublicQueryProcedure({ }), handler: ({ input }) => listFaqs(input.unterveranstaltungId), }) + +export const faqCategorySearchProcedure = definePublicQueryProcedure({ + key: 'searchCategory', + inputSchema: z.strictObject({ + term: z.string().optional(), + }), + handler: async ({ input: { term } }) => { + const result = await prisma.faqCategory.findMany({ + take: 10, + orderBy: { + name: 'asc', + }, + where: { + name: { + contains: term, + }, + }, + select: { + name: true, + }, + }) + + return result.map((c) => c.name) + }, +}) diff --git a/api/src/services/faqs/faqUpdateProcedure.ts b/api/src/services/faqs/faqUpdateProcedure.ts new file mode 100644 index 00000000..d789795d --- /dev/null +++ b/api/src/services/faqs/faqUpdateProcedure.ts @@ -0,0 +1,39 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' +import { faqSchema } from './faqs.schema.js' + +export const faqUpdateProcedure = definePublicMutateProcedure({ + key: 'update', + inputSchema: z.strictObject({ + id: z.number().int(), + unterveranstaltungId: z.number().int(), + faq: faqSchema, + }), + handler: async ({ input: { id, unterveranstaltungId, faq } }) => { + await prisma.faq.update({ + where: { + id, + }, + data: { + question: faq.question, + answer: faq.answer, + category: { + connectOrCreate: { + create: { + name: faq.category, + unterveranstaltungId, + }, + where: { + name_unterveranstaltungId: { + name: faq.category, + unterveranstaltungId, + }, + }, + }, + }, + }, + }) + }, +}) diff --git a/api/src/services/faqs/faqs.router.ts b/api/src/services/faqs/faqs.router.ts index cd6981fd..29e6dd1e 100644 --- a/api/src/services/faqs/faqs.router.ts +++ b/api/src/services/faqs/faqs.router.ts @@ -1,10 +1,17 @@ // Prettier ignored is because this file is generated import { mergeRouters } from '../../trpc.js' -import { faqListProcedure } from './faqListProcedure.js' +import { faqCreateProcedure } from './faqCreateProcedure.js' +import { faqDeleteProcedure } from './faqDeleteProcecure.js' +import { faqCategorySearchProcedure, faqListProcedure } from './faqListProcedure.js' +import { faqUpdateProcedure } from './faqUpdateProcedure.js' // Import Routes here - do not delete this line export const faqsRouter = mergeRouters( - faqListProcedure.router + faqListProcedure.router, + faqCategorySearchProcedure.router, + faqCreateProcedure.router, + faqUpdateProcedure.router, + faqDeleteProcedure.router // Add Routes here - do not delete this line ) diff --git a/api/src/services/faqs/faqs.schema.ts b/api/src/services/faqs/faqs.schema.ts new file mode 100644 index 00000000..5a5fada5 --- /dev/null +++ b/api/src/services/faqs/faqs.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +export const faqSchema = z.strictObject({ + question: z.string(), + answer: z.string(), + category: z.string(), +}) + +export const faqUpdateSchema = faqSchema.partial() diff --git a/api/src/services/index.ts b/api/src/services/index.ts index 4d3d5fbe..b8184e22 100644 --- a/api/src/services/index.ts +++ b/api/src/services/index.ts @@ -6,6 +6,7 @@ import { addressRouter } from './address/address.router.js' import { anmeldungRouter } from './anmeldung/anmeldung.router.js' import { authenticationRouter } from './authentication/authentication.router.js' import { customFieldsRouter } from './customFields/customFields.router.js' +import { faqsRouter } from './faqs/faqs.router.js' import { fileRouter } from './file/file.router.js' import { gliederungRouter } from './gliederung/gliederung.router.js' import { ortRouter } from './ort/ort.router.js' @@ -31,5 +32,6 @@ export const serviceRouter = router({ customFields: customFieldsRouter, file: fileRouter, address: addressRouter, + faq: faqsRouter, // Add Routers here - do not delete this line }) diff --git a/api/src/services/unterveranstaltung/helpers/create b/api/src/services/unterveranstaltung/helpers/create deleted file mode 100644 index e69de29b..00000000 diff --git a/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts b/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts index 4edee0f3..ec9478d6 100644 --- a/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts +++ b/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts @@ -48,14 +48,6 @@ export const unterveranstaltungLandingSchema = z.strictObject({ .strictObject({ visible: z.boolean(), email: z.string().email().optional(), - items: z - .array( - z.strictObject({ - question: z.string(), - answer: z.string(), - }) - ) - .optional(), }) .optional(), }) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts b/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts index 5778d8e9..0bf96c21 100644 --- a/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts +++ b/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts @@ -14,12 +14,12 @@ export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutate data: unterveranstaltungCreateSchema, landingSettings: unterveranstaltungLandingSchema, }), - async handler(options) { + async handler({ ctx, input }) { // check logged in user is admin of gliederung - const gliederung = await getGliederungRequireAdmin(options.ctx.accountId) + const gliederung = await getGliederungRequireAdmin(ctx.accountId) const veranstaltung = await prisma.veranstaltung.findUniqueOrThrow({ where: { - id: options.input.data.veranstaltungId, + id: input.data.veranstaltungId, }, select: { meldebeginn: true, @@ -27,14 +27,14 @@ export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutate }, }) // check meldebegin is after parent meldebeginn - if (new Date(options.input.data.meldebeginn) < veranstaltung.meldebeginn) { + if (new Date(input.data.meldebeginn) < veranstaltung.meldebeginn) { throw new TRPCError({ message: 'Der Meldebeginn darf nicht vor dem Meldebeginn der übergeordneten Veranstaltung liegen', code: 'BAD_REQUEST', }) } // check meldeschluss is before parent meldeschluss - if (new Date(options.input.data.meldeschluss) > veranstaltung.meldeschluss) { + if (new Date(input.data.meldeschluss) > veranstaltung.meldeschluss) { throw new TRPCError({ message: 'Der Meldeschluss darf nicht nach dem Meldeschluss der übergeordneten Veranstaltung liegen', code: 'BAD_REQUEST', @@ -43,7 +43,7 @@ export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutate const unterveranstaltung = await prisma.unterveranstaltung.create({ data: { - ...options.input.data, + ...input.data, type: 'GLIEDERUNG', gliederungId: gliederung.id, }, @@ -52,45 +52,32 @@ export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutate }, }) - const landingSettings = await prisma.unterveranstaltungLandingSettings.create({ + await prisma.unterveranstaltungLandingSettings.create({ data: { unterveranstaltungId: unterveranstaltung.id, - heroTitle: options.input.landingSettings.hero.title, - heroSubtitle: options.input.landingSettings.hero.subtitle, - eventDetailsTitle: options.input.landingSettings.eventDetails.title, - eventDetailsContent: options.input.landingSettings.eventDetails.content, + heroTitle: input.landingSettings.hero.title, + heroSubtitle: input.landingSettings.hero.subtitle, + eventDetailsTitle: input.landingSettings.eventDetails.title, + eventDetailsContent: input.landingSettings.eventDetails.content, - miscellaneousVisible: options.input.landingSettings.miscellaneous?.visible ?? false, - miscellaneousTitle: options.input.landingSettings.miscellaneous?.title, - miscellaneousItems: options.input.landingSettings.miscellaneous?.items + miscellaneousVisible: input.landingSettings.miscellaneous?.visible ?? false, + miscellaneousTitle: input.landingSettings.miscellaneous?.title, + miscellaneousItems: input.landingSettings.miscellaneous?.items ? { createMany: { - data: options.input.landingSettings.miscellaneous.items.map((item) => ({ + data: input.landingSettings.miscellaneous.items.map((item) => ({ title: item.title, content: item.content, })), }, } : undefined, - faqVisible: options.input.landingSettings.faq?.visible ?? false, - faqEmail: options.input.landingSettings.faq?.email, + faqVisible: input.landingSettings.faq?.visible ?? false, + faqEmail: input.landingSettings.faq?.email, }, select: { id: true, }, }) - - const faq = await prisma.faq.createMany({ - data: options.input.landingSettings.faq?.items - ? options.input.landingSettings.faq.items.map((item) => ({ - unterveranstaltungId: unterveranstaltung.id, - question: item.question, - answer: item.answer, - category: 'DEFAULT', - })) - : [], - }) - - return { unterveranstaltung, landingSettings, faq } }, }) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts index eac21255..40e234f5 100644 --- a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts +++ b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts @@ -52,18 +52,5 @@ export const unterveranstaltungVerwaltungCreateProcedure = defineProtectedMutate id: true, }, }) - - await prisma.faq.createMany({ - data: options.input.landingSettings.faq?.items - ? options.input.landingSettings.faq.items.map((item) => ({ - unterveranstaltungId: unterveranstaltung.id, - question: item.question, - answer: item.answer, - category: 'DEFAULT', - })) - : [], - }) - - return unterveranstaltung }, }) diff --git a/frontend/src/assets/main.scss b/frontend/src/assets/main.scss index b4ce6868..9ac61135 100644 --- a/frontend/src/assets/main.scss +++ b/frontend/src/assets/main.scss @@ -23,3 +23,7 @@ body, body { @apply transition-colors duration-200; } + +abbr { + cursor: help; +} diff --git a/frontend/src/components/Abbr.vue b/frontend/src/components/Abbr.vue new file mode 100644 index 00000000..91f5d0ee --- /dev/null +++ b/frontend/src/components/Abbr.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/components/BasicInputs/components/Typeahead.vue b/frontend/src/components/BasicInputs/components/Typeahead.vue index c276d45c..a4452954 100644 --- a/frontend/src/components/BasicInputs/components/Typeahead.vue +++ b/frontend/src/components/BasicInputs/components/Typeahead.vue @@ -1,6 +1,6 @@