diff --git a/db/migrations/20231127155246_surveyresponse_rename_surveypart/migration.sql b/db/migrations/20231127155246_surveyresponse_rename_surveypart/migration.sql new file mode 100644 index 000000000..d55fbed46 --- /dev/null +++ b/db/migrations/20231127155246_surveyresponse_rename_surveypart/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "SurveyResponse" RENAME COLUMN "surveyId" TO "surveyPart"; diff --git a/db/migrations/20231220104810_add_subsubsection_task/migration.sql b/db/migrations/20231220104810_add_subsubsection_task/migration.sql new file mode 100644 index 000000000..b7ac12cab --- /dev/null +++ b/db/migrations/20231220104810_add_subsubsection_task/migration.sql @@ -0,0 +1,23 @@ +-- AlterTable +ALTER TABLE "Subsubsection" ADD COLUMN "subsubsectionTaskId" INTEGER; + +-- CreateTable +CREATE TABLE "SubsubsectionTask" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "slug" TEXT NOT NULL, + "title" TEXT NOT NULL, + "projectId" INTEGER NOT NULL, + + CONSTRAINT "SubsubsectionTask_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "SubsubsectionTask_projectId_slug_key" ON "SubsubsectionTask"("projectId", "slug"); + +-- AddForeignKey +ALTER TABLE "Subsubsection" ADD CONSTRAINT "Subsubsection_subsubsectionTaskId_fkey" FOREIGN KEY ("subsubsectionTaskId") REFERENCES "SubsubsectionTask"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SubsubsectionTask" ADD CONSTRAINT "SubsubsectionTask_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/db/migrations/20231220133742_add_model_subsubsection_infra/migration.sql b/db/migrations/20231220133742_add_model_subsubsection_infra/migration.sql new file mode 100644 index 000000000..b14c17036 --- /dev/null +++ b/db/migrations/20231220133742_add_model_subsubsection_infra/migration.sql @@ -0,0 +1,23 @@ +-- AlterTable +ALTER TABLE "Subsubsection" ADD COLUMN "subsubsectionInfraId" INTEGER; + +-- CreateTable +CREATE TABLE "SubsubsectionInfra" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "slug" TEXT NOT NULL, + "title" TEXT NOT NULL, + "projectId" INTEGER NOT NULL, + + CONSTRAINT "SubsubsectionInfra_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "SubsubsectionInfra_projectId_slug_key" ON "SubsubsectionInfra"("projectId", "slug"); + +-- AddForeignKey +ALTER TABLE "Subsubsection" ADD CONSTRAINT "Subsubsection_subsubsectionInfraId_fkey" FOREIGN KEY ("subsubsectionInfraId") REFERENCES "SubsubsectionInfra"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SubsubsectionInfra" ADD CONSTRAINT "SubsubsectionInfra_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/db/migrations/20231220161044_add_field_width_existing/migration.sql b/db/migrations/20231220161044_add_field_width_existing/migration.sql new file mode 100644 index 000000000..49d5f20e7 --- /dev/null +++ b/db/migrations/20231220161044_add_field_width_existing/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Subsubsection" ADD COLUMN "widthExisting" DOUBLE PRECISION; diff --git a/db/migrations/20231220161616_add_field_is_existing_infra/migration.sql b/db/migrations/20231220161616_add_field_is_existing_infra/migration.sql new file mode 100644 index 000000000..18eeee6ac --- /dev/null +++ b/db/migrations/20231220161616_add_field_is_existing_infra/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Subsubsection" ADD COLUMN "isExistingInfra" BOOLEAN DEFAULT false; diff --git a/db/migrations/20231222132752_add_model_subsubsection_special/migration.sql b/db/migrations/20231222132752_add_model_subsubsection_special/migration.sql new file mode 100644 index 000000000..ee7458194 --- /dev/null +++ b/db/migrations/20231222132752_add_model_subsubsection_special/migration.sql @@ -0,0 +1,35 @@ +-- CreateTable +CREATE TABLE "SubsubsectionSpecial" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "slug" TEXT NOT NULL, + "title" TEXT NOT NULL, + "projectId" INTEGER NOT NULL, + + CONSTRAINT "SubsubsectionSpecial_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_SubsubsectionToSubsubsectionSpecial" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "SubsubsectionSpecial_projectId_slug_key" ON "SubsubsectionSpecial"("projectId", "slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "_SubsubsectionToSubsubsectionSpecial_AB_unique" ON "_SubsubsectionToSubsubsectionSpecial"("A", "B"); + +-- CreateIndex +CREATE INDEX "_SubsubsectionToSubsubsectionSpecial_B_index" ON "_SubsubsectionToSubsubsectionSpecial"("B"); + +-- AddForeignKey +ALTER TABLE "SubsubsectionSpecial" ADD CONSTRAINT "SubsubsectionSpecial_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_SubsubsectionToSubsubsectionSpecial" ADD CONSTRAINT "_SubsubsectionToSubsubsectionSpecial_A_fkey" FOREIGN KEY ("A") REFERENCES "Subsubsection"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_SubsubsectionToSubsubsectionSpecial" ADD CONSTRAINT "_SubsubsectionToSubsubsectionSpecial_B_fkey" FOREIGN KEY ("B") REFERENCES "SubsubsectionSpecial"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/db/migrations/20231222160004_migrate_task_to_subsubsection_task_id/migration.sql b/db/migrations/20231222160004_migrate_task_to_subsubsection_task_id/migration.sql new file mode 100644 index 000000000..5df45d709 --- /dev/null +++ b/db/migrations/20231222160004_migrate_task_to_subsubsection_task_id/migration.sql @@ -0,0 +1,44 @@ +CREATE OR REPLACE FUNCTION slugify(input_text TEXT) + RETURNS TEXT AS +$$ +DECLARE + slug TEXT; +BEGIN + slug := input_text; + slug := lower(slug); + slug := translate(slug, 'ä,ö,ü,ß', 'a,o,u,s'); + slug := regexp_replace(slug, '[^a-z0-9]+', '-', 'g'); + slug := regexp_replace(slug, '^[-]+|[-]+$', '', 'g'); + RETURN slug; +END; +$$ LANGUAGE plpgsql; + +DO +$$ + DECLARE + v_project_id INTEGER; + v_task TEXT; + BEGIN + FOR v_project_id IN SELECT id FROM "Project" + LOOP + RAISE NOTICE 'Project: %', v_project_id; + FOR v_task IN SELECT DISTINCT task + FROM "Subsubsection" + WHERE "subsectionId" IN + (SELECT id FROM "Subsection" WHERE "projectId" = v_project_id) + LOOP + RAISE NOTICE ' Task: % %s', v_task, slugify(v_task); + INSERT INTO "SubsubsectionTask" ("updatedAt", slug, title, "projectId") + VALUES ('2023-12-20 19:31:21.496', slugify(v_task), v_task, v_project_id); + UPDATE "Subsubsection" + SET "subsubsectionTaskId" = (SELECT id + FROM "SubsubsectionTask" + WHERE "projectId" = v_project_id + AND title = v_task) + WHERE "subsectionId" IN + (SELECT id FROM "Subsection" WHERE "projectId" = v_project_id) + AND task = v_task; + END LOOP; + END LOOP; + END ; +$$; diff --git a/db/migrations/20240102102343_field_task_optional/migration.sql b/db/migrations/20240102102343_field_task_optional/migration.sql new file mode 100644 index 000000000..555dc27ac --- /dev/null +++ b/db/migrations/20240102102343_field_task_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Subsubsection" ALTER COLUMN "task" DROP NOT NULL; diff --git a/db/migrations/20240104131012_subsection_add_length_km/migration.sql b/db/migrations/20240104131012_subsection_add_length_km/migration.sql new file mode 100644 index 000000000..0fe686fca --- /dev/null +++ b/db/migrations/20240104131012_subsection_add_length_km/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Subsection" ADD COLUMN "lengthKm" DOUBLE PRECISION; diff --git a/db/migrations/20240104164750_subsubsection_rename_length/migration.sql b/db/migrations/20240104164750_subsubsection_rename_length/migration.sql new file mode 100644 index 000000000..c5758fe9c --- /dev/null +++ b/db/migrations/20240104164750_subsubsection_rename_length/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE "Subsubsection" +RENAME COLUMN "length" TO "lengthKm"; diff --git a/db/schema.prisma b/db/schema.prisma index 14a9d5809..557036e46 100644 --- a/db/schema.prisma +++ b/db/schema.prisma @@ -121,6 +121,9 @@ model Project { Survey Survey[] qualityLevels QualityLevel[] SubsubsectionStatus SubsubsectionStatus[] + SubsubsectionTask SubsubsectionTask[] + SubsubsectionInfra SubsubsectionInfra[] + SubsubsectionSpecial SubsubsectionSpecial[] } model Membership { @@ -207,6 +210,7 @@ model Subsection { geometry Json labelPos LabelPositionEnum @default(top) description String? + lengthKm Float? // project Project @relation(fields: [projectId], references: [id]) projectId Int @@ -224,21 +228,23 @@ model Subsection { } model Subsubsection { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - // - slug String // shortTitle and URL Slug - subTitle String? - type SubsubsectionTypeEnum @default(ROUTE) // Führungform - geometry Json - labelPos LabelPositionEnum @default(top) - task String // Maßnahmentyp - length Float? // Kilometer - width Float? // Meter - costEstimate Float? // Euro - description String? // Anmerkungen - mapillaryKey String? + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + // + slug String // shortTitle and URL Slug + subTitle String? + type SubsubsectionTypeEnum @default(ROUTE) // Führungform + geometry Json + labelPos LabelPositionEnum @default(top) + task String? // Maßnahmentyp - UNUSED + lengthKm Float? // Kilometer + width Float? // Meter + widthExisting Float? // Meter + costEstimate Float? // Euro + description String? // Anmerkungen + mapillaryKey String? + isExistingInfra Boolean? @default(false) // Bestandsführung maxSpeed Float? // Maximalgeschwindigkeit trafficLoad Int? // Verkehrsbelastung @@ -257,18 +263,27 @@ model Subsubsection { ownFunds Float? // Einsatz Eigenmittel // - qualityLevel QualityLevel? @relation(fields: [qualityLevelId], references: [id]) + qualityLevel QualityLevel? @relation(fields: [qualityLevelId], references: [id]) qualityLevelId Int? // - manager User? @relation(fields: [managerId], references: [id]) + manager User? @relation(fields: [managerId], references: [id]) managerId Int? // - subsection Subsection @relation(fields: [subsectionId], references: [id]) + subsection Subsection @relation(fields: [subsectionId], references: [id]) subsectionId Int // uploads Upload[] - SubsubsectionStatus SubsubsectionStatus? @relation(fields: [subsubsectionStatusId], references: [id]) + // + SubsubsectionStatus SubsubsectionStatus? @relation(fields: [subsubsectionStatusId], references: [id]) subsubsectionStatusId Int? + // + SubsubsectionTask SubsubsectionTask? @relation(fields: [subsubsectionTaskId], references: [id]) + subsubsectionTaskId Int? + // + SubsubsectionInfra SubsubsectionInfra? @relation(fields: [subsubsectionInfraId], references: [id]) + subsubsectionInfraId Int? + // + specialFeatures SubsubsectionSpecial[] @@unique([subsectionId, slug]) } @@ -308,6 +323,52 @@ model SubsubsectionStatus { @@unique([projectId, slug]) } +model SubsubsectionTask { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + // + slug String // shortTitle and URL Slug + title String + // + project Project @relation(fields: [projectId], references: [id]) + projectId Int + Subsubsection Subsubsection[] + + @@unique([projectId, slug]) +} + +model SubsubsectionInfra { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + // + slug String // shortTitle and URL Slug + title String + // + project Project @relation(fields: [projectId], references: [id]) + projectId Int + Subsubsection Subsubsection[] + + @@unique([projectId, slug]) +} + +model SubsubsectionSpecial { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + // + slug String // shortTitle and URL Slug + title String + // + project Project @relation(fields: [projectId], references: [id]) + projectId Int + // + Subsubsection Subsubsection[] + + @@unique([projectId, slug]) +} + model Stakeholdernote { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) @@ -372,7 +433,7 @@ model SurveySession { model SurveyResponse { id Int @id @default(autoincrement()) - surveyId Int // surveyParts as specified by the survey JSON files + surveyPart Int data String status SurveyResponseStatusEnum? @default(PENDING) note String? diff --git a/db/seeds.ts b/db/seeds.ts index 72fd9aca2..3ed4349a8 100644 --- a/db/seeds.ts +++ b/db/seeds.ts @@ -7,10 +7,14 @@ import seedQualityLevels from "./seeds/qualityLevels" import seedStakeholdernotes from "./seeds/stakeholdernotes" import seedSubsections from "./seeds/subsections" import seedSubsubsectionStatus from "./seeds/subsubsectionStatus" +import seedSubsubsectionTask from "./seeds/subsubsectionTask" +import seedSubsubsectionInfra from "./seeds/subsubsectionInfra" +import seedSubsubsectionSpecial from "./seeds/subsubsectionSpecial" import seedSurveyResponseTopics from "./seeds/surveyresponsetopics" import seedSurveys from "./seeds/surveys" import seedUploads from "./seeds/uploads" import seedUsers from "./seeds/users" +import seedDevData from "./seeds/devData" /* * This seed function is executed when you run `blitz db seed`. @@ -29,6 +33,10 @@ const seed = async () => { await seedSurveys() await seedSurveyResponseTopics() await seedSubsubsectionStatus() + await seedSubsubsectionTask() + await seedSubsubsectionInfra() + await seedSubsubsectionSpecial() + await seedDevData() } export default seed diff --git a/db/seeds/devData.ts b/db/seeds/devData.ts new file mode 100644 index 000000000..07a200c43 --- /dev/null +++ b/db/seeds/devData.ts @@ -0,0 +1,20 @@ +import { PrismaClient } from "@prisma/client" + +/* +# TODO: Document why this exists and could be useful +source .env.local +pg_dump $DATABASE_URL --data-only --column-inserts -t 'public."SubsubsectionSpecial"' | grep 'INSERT INTO' +*/ + +const sql = ` +INSERT INTO public."SubsubsectionSpecial" (id, "createdAt", "updatedAt", slug, title, "projectId") VALUES (1, '2023-12-20 19:31:21.496', '2023-12-20 19:31:21.496', 's1', 'Special 1', 1); +INSERT INTO public."SubsubsectionSpecial" (id, "createdAt", "updatedAt", slug, title, "projectId") VALUES (2, '2023-12-20 19:31:29.162', '2023-12-20 19:31:29.162', 's2', 'Special 22', 1); +INSERT INTO public."SubsubsectionSpecial" (id, "createdAt", "updatedAt", slug, title, "projectId") VALUES (3, '2023-12-20 19:31:36.93', '2023-12-20 19:31:36.93', 's3', 'Special 333', 1); +` + +const seedDevData = async () => { + // const prismaClient = new PrismaClient() + // sql.split(';').forEach(statement => prismaClient.$executeRawUnsafe(statement)) +} + +export default seedDevData diff --git a/db/seeds/subsection_subsubsections.ts b/db/seeds/subsection_subsubsections.ts index 38a0b96b6..23f51c59f 100644 --- a/db/seeds/subsection_subsubsections.ts +++ b/db/seeds/subsection_subsubsections.ts @@ -12,7 +12,7 @@ export const subsubsections: Omit { + const seedFiles: Prisma.SubsubsectionInfraUncheckedCreateInput[] = [ + { + projectId: 1, + slug: "ff1", + title: "Eigenständig geführte Radschnellverbindung im Zweirichtungsverkehr", + }, + { + projectId: 1, + slug: "ff2", + title: "Straßenbegleitende Radschnellverbindung im Zweirichtungsverkehr", + }, + { + projectId: 1, + slug: "ff3", + title: "Straßenbegleitende Radschnellverbindung im Einrichtungsverkehr", + }, + { + projectId: 1, + slug: "ff4", + title: "Straßenbegleitende Radschnellverbindung als Radfahrstreifen im Einrichtungsverkehr", + }, + { projectId: 1, slug: "ff5", title: "Radschnellverbindung als Fahrradstraße" }, + ] + + for (let i = 0; i < seedFiles.length; i++) { + const data = seedFiles[i] + if (data) { + await db.subsubsectionInfra.create({ data }) + } + } +} + +export default seedSubsubsectionInfra diff --git a/db/seeds/subsubsectionSpecial.ts b/db/seeds/subsubsectionSpecial.ts new file mode 100644 index 000000000..1f3fa5b65 --- /dev/null +++ b/db/seeds/subsubsectionSpecial.ts @@ -0,0 +1,18 @@ +import db, { Prisma } from "../index" + +const seedSubsubsectionSpecial = async () => { + const seedFiles: Prisma.SubsubsectionSpecialUncheckedCreateInput[] = [ + { projectId: 1, slug: "b1", title: "Besonderheit 1" }, + { projectId: 1, slug: "b2", title: "Besonderheit 2" }, + { projectId: 1, slug: "b3", title: "Besonderheit 3" }, + ] + + for (let i = 0; i < seedFiles.length; i++) { + const data = seedFiles[i] + if (data) { + await db.subsubsectionSpecial.create({ data }) + } + } +} + +export default seedSubsubsectionSpecial diff --git a/db/seeds/subsubsectionTask.ts b/db/seeds/subsubsectionTask.ts new file mode 100644 index 000000000..3910372e3 --- /dev/null +++ b/db/seeds/subsubsectionTask.ts @@ -0,0 +1,18 @@ +import db, { Prisma } from "../index" + +const seedSubsubsectionTask = async () => { + const seedFiles: Prisma.SubsubsectionTaskUncheckedCreateInput[] = [ + { projectId: 1, slug: "footway", title: "Ausbau Fußweg" }, + { projectId: 1, slug: "marking", title: "Fahrbahnmarkierung" }, + { projectId: 1, slug: "conversion", title: "Vollumbau" }, + ] + + for (let i = 0; i < seedFiles.length; i++) { + const data = seedFiles[i] + if (data) { + await db.subsubsectionTask.create({ data }) + } + } +} + +export default seedSubsubsectionTask diff --git a/next.config.js b/next.config.js index 068ea5a73..60d2fd84a 100644 --- a/next.config.js +++ b/next.config.js @@ -14,6 +14,8 @@ const config = { "staging.trassenscout.de", "trassenscout.de", "radschnellweg8-lb-wn.de", + "develop--frm-7-landingpage.netlify.app", + "radschnellweg-frm7.de", ], // allow fetching images from those domains – https://nextjs.org/docs/api-reference/next/image#domains }, } diff --git a/package-lock.json b/package-lock.json index f1da96d6d..5af56cf0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "secure-password": "4.0.0", "tailwindcss": "3.3.3", "uuid": "9.0.0", - "zod": "3.21.4" + "zod": "3.22.3" }, "devDependencies": { "@next/bundle-analyzer": "13.5.2", @@ -63,7 +63,7 @@ "@types/preview-email": "3.0.1", "@types/react": "18.2.15", "@types/uuid": "9.0.2", - "@typescript-eslint/eslint-plugin": "6.1.0", + "@typescript-eslint/eslint-plugin": "6.13.0", "@vitejs/plugin-react": "4.0.3", "eslint": "8.45.0", "eslint-config-next": "13.5.2", @@ -74,7 +74,7 @@ "prettier-plugin-prisma": "5.0.0", "prettier-plugin-tailwindcss": "0.4.1", "preview-email": "3.0.19", - "typescript": "^5.1.6", + "typescript": "5.3.2", "vite-tsconfig-paths": "4.2.0", "vitest": "0.33.0", "zod-prisma": "0.5.4" @@ -942,11 +942,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -1063,11 +1063,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", - "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -1184,12 +1184,12 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -1331,9 +1331,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { "node": ">=6.9.0" } @@ -1381,9 +1381,9 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -1458,9 +1458,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2590,18 +2590,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", - "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.7", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/types": "^7.22.5", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2610,11 +2610,11 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -3383,6 +3383,126 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", + "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", + "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", + "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", + "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", + "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", + "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", + "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", + "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6305,9 +6425,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -6488,9 +6608,9 @@ } }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@types/serve-static": { @@ -6549,21 +6669,20 @@ "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz", - "integrity": "sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.0.tgz", + "integrity": "sha512-HTvbSd0JceI2GW5DHS3R9zbarOqjkM9XDR7zL8eCsBUO/eSiHcoNE7kSL5sjGXmVa9fjH5LCfHDXNnH4QLp7tQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.1.0", - "@typescript-eslint/type-utils": "6.1.0", - "@typescript-eslint/utils": "6.1.0", - "@typescript-eslint/visitor-keys": "6.1.0", + "@typescript-eslint/scope-manager": "6.13.0", + "@typescript-eslint/type-utils": "6.13.0", + "@typescript-eslint/utils": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", - "natural-compare-lite": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -6635,82 +6754,26 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", - "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.0.tgz", + "integrity": "sha512-VpG+M7GNhHLI/aTDctqAV0XbzB16vf+qDX9DXuMZSe/0bahzDA9AKZB15NDbd+D9M4cDsJvfkbGOA7qiZ/bWJw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.47.0", - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/typescript-estree": "5.47.0", + "@typescript-eslint/scope-manager": "6.13.0", + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/typescript-estree": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.0.tgz", - "integrity": "sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.0.tgz", - "integrity": "sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.0.tgz", - "integrity": "sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -6718,23 +6781,6 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.0.tgz", - "integrity": "sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.47.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/parser/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -6753,13 +6799,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz", - "integrity": "sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.0.tgz", + "integrity": "sha512-2x0K2/CujsokIv+LN2T0l5FVDMtsCjkUyYtlcY4xxnxLAW+x41LXr16duoicHpGtLhmtN7kqvuFJ3zbz00Ikhw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.1.0", - "@typescript-eslint/visitor-keys": "6.1.0" + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6770,13 +6816,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz", - "integrity": "sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.0.tgz", + "integrity": "sha512-YHufAmZd/yP2XdoD3YeFEjq+/Tl+myhzv+GJHSOz+ro/NFGS84mIIuLU3pVwUcauSmwlCrVXbBclkn1HfjY0qQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.1.0", - "@typescript-eslint/utils": "6.1.0", + "@typescript-eslint/typescript-estree": "6.13.0", + "@typescript-eslint/utils": "6.13.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -6814,9 +6860,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.1.0.tgz", - "integrity": "sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.0.tgz", + "integrity": "sha512-oXg7DFxx/GmTrKXKKLSoR2rwiutOC7jCQ5nDH5p5VS6cmHE1TcPTaYQ0VPSSUvj7BnNqCgQ/NXcTBxn59pfPTQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6827,13 +6873,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz", - "integrity": "sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.0.tgz", + "integrity": "sha512-IT4O/YKJDoiy/mPEDsfOfp+473A9GVqXlBKckfrAOuVbTqM8xbc0LuqyFCcgeFWpqu3WjQexolgqN2CuWBYbog==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.1.0", - "@typescript-eslint/visitor-keys": "6.1.0", + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6904,17 +6950,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.1.0.tgz", - "integrity": "sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.0.tgz", + "integrity": "sha512-V+txaxARI8yznDkcQ6FNRXxG+T37qT3+2NsDTZ/nKLxv6VfGrRhTnuvxPUxpVuWWr+eVeIxU53PioOXbz8ratQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.1.0", - "@typescript-eslint/types": "6.1.0", - "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/scope-manager": "6.13.0", + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/typescript-estree": "6.13.0", "semver": "^7.5.4" }, "engines": { @@ -6962,12 +7008,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz", - "integrity": "sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.0.tgz", + "integrity": "sha512-UQklteCEMCRoq/1UhKFZsHv5E4dN1wQSzJoxTfABasWk1HgJRdg1xNUve/Kv/Sdymt4x+iEzpESOqRFlQr/9Aw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/types": "6.13.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -14598,12 +14644,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -17078,9 +17118,9 @@ "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==" }, "node_modules/rollup": { - "version": "3.26.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", - "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -17164,9 +17204,9 @@ } }, "node_modules/run-applescript/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -18554,9 +18594,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "dependencies": { "psl": "^1.1.33", @@ -18599,9 +18639,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "dev": true, "engines": { "node": ">=16.13.0" @@ -18734,27 +18774,6 @@ "node": ">=0.6.x" } }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/turf-jsts": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", @@ -18858,9 +18877,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -19396,14 +19415,14 @@ } }, "node_modules/vite": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.4.tgz", - "integrity": "sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", + "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", "dev": true, "dependencies": { "esbuild": "^0.18.10", - "postcss": "^8.4.25", - "rollup": "^3.25.2" + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -20141,9 +20160,9 @@ } }, "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -20174,126 +20193,6 @@ "optional": true } } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", - "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", - "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", - "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", - "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", - "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", - "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", - "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", - "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } }, "dependencies": { @@ -21043,11 +20942,11 @@ } }, "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "requires": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "dependencies": { @@ -21137,11 +21036,11 @@ } }, "@babel/generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", - "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", "requires": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -21228,12 +21127,12 @@ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { @@ -21330,9 +21229,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" }, "@babel/helper-validator-identifier": { "version": "7.22.20", @@ -21365,9 +21264,9 @@ } }, "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "requires": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -21426,9 +21325,9 @@ } }, "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==" }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.20.7", @@ -22156,28 +22055,28 @@ } }, "@babel/traverse": { - "version": "7.22.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", - "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.7", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/types": "^7.22.5", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "requires": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } @@ -22750,6 +22649,54 @@ "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==", "optional": true }, + "@next/swc-darwin-x64": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", + "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", + "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", + "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", + "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", + "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", + "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", + "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", + "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -25088,9 +25035,9 @@ } }, "@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/json5": { @@ -25271,9 +25218,9 @@ } }, "@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "@types/serve-static": { @@ -25332,21 +25279,20 @@ "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" }, "@typescript-eslint/eslint-plugin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz", - "integrity": "sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.0.tgz", + "integrity": "sha512-HTvbSd0JceI2GW5DHS3R9zbarOqjkM9XDR7zL8eCsBUO/eSiHcoNE7kSL5sjGXmVa9fjH5LCfHDXNnH4QLp7tQ==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.1.0", - "@typescript-eslint/type-utils": "6.1.0", - "@typescript-eslint/utils": "6.1.0", - "@typescript-eslint/visitor-keys": "6.1.0", + "@typescript-eslint/scope-manager": "6.13.0", + "@typescript-eslint/type-utils": "6.13.0", + "@typescript-eslint/utils": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", - "natural-compare-lite": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -25387,58 +25333,18 @@ } }, "@typescript-eslint/parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", - "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.0.tgz", + "integrity": "sha512-VpG+M7GNhHLI/aTDctqAV0XbzB16vf+qDX9DXuMZSe/0bahzDA9AKZB15NDbd+D9M4cDsJvfkbGOA7qiZ/bWJw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.47.0", - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/typescript-estree": "5.47.0", + "@typescript-eslint/scope-manager": "6.13.0", + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/typescript-estree": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0", "debug": "^4.3.4" }, "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.0.tgz", - "integrity": "sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0" - } - }, - "@typescript-eslint/types": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.0.tgz", - "integrity": "sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.0.tgz", - "integrity": "sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.0.tgz", - "integrity": "sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.47.0", - "eslint-visitor-keys": "^3.3.0" - } - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -25451,23 +25357,23 @@ } }, "@typescript-eslint/scope-manager": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz", - "integrity": "sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.0.tgz", + "integrity": "sha512-2x0K2/CujsokIv+LN2T0l5FVDMtsCjkUyYtlcY4xxnxLAW+x41LXr16duoicHpGtLhmtN7kqvuFJ3zbz00Ikhw==", "dev": true, "requires": { - "@typescript-eslint/types": "6.1.0", - "@typescript-eslint/visitor-keys": "6.1.0" + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0" } }, "@typescript-eslint/type-utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz", - "integrity": "sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.0.tgz", + "integrity": "sha512-YHufAmZd/yP2XdoD3YeFEjq+/Tl+myhzv+GJHSOz+ro/NFGS84mIIuLU3pVwUcauSmwlCrVXbBclkn1HfjY0qQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.1.0", - "@typescript-eslint/utils": "6.1.0", + "@typescript-eslint/typescript-estree": "6.13.0", + "@typescript-eslint/utils": "6.13.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -25484,19 +25390,19 @@ } }, "@typescript-eslint/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.1.0.tgz", - "integrity": "sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.0.tgz", + "integrity": "sha512-oXg7DFxx/GmTrKXKKLSoR2rwiutOC7jCQ5nDH5p5VS6cmHE1TcPTaYQ0VPSSUvj7BnNqCgQ/NXcTBxn59pfPTQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz", - "integrity": "sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.0.tgz", + "integrity": "sha512-IT4O/YKJDoiy/mPEDsfOfp+473A9GVqXlBKckfrAOuVbTqM8xbc0LuqyFCcgeFWpqu3WjQexolgqN2CuWBYbog==", "dev": true, "requires": { - "@typescript-eslint/types": "6.1.0", - "@typescript-eslint/visitor-keys": "6.1.0", + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/visitor-keys": "6.13.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -25540,17 +25446,17 @@ } }, "@typescript-eslint/utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.1.0.tgz", - "integrity": "sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.0.tgz", + "integrity": "sha512-V+txaxARI8yznDkcQ6FNRXxG+T37qT3+2NsDTZ/nKLxv6VfGrRhTnuvxPUxpVuWWr+eVeIxU53PioOXbz8ratQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.1.0", - "@typescript-eslint/types": "6.1.0", - "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/scope-manager": "6.13.0", + "@typescript-eslint/types": "6.13.0", + "@typescript-eslint/typescript-estree": "6.13.0", "semver": "^7.5.4" }, "dependencies": { @@ -25581,12 +25487,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz", - "integrity": "sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.0.tgz", + "integrity": "sha512-UQklteCEMCRoq/1UhKFZsHv5E4dN1wQSzJoxTfABasWk1HgJRdg1xNUve/Kv/Sdymt4x+iEzpESOqRFlQr/9Aw==", "dev": true, "requires": { - "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/types": "6.13.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -31254,12 +31160,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -32960,9 +32860,9 @@ "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==" }, "rollup": { - "version": "3.26.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", - "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -33024,9 +32924,9 @@ "dev": true }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "shebang-command": { @@ -34077,9 +33977,9 @@ "dev": true }, "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "requires": { "psl": "^1.1.33", @@ -34111,9 +34011,9 @@ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" }, "ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "dev": true }, "ts-interface-checker": { @@ -34194,23 +34094,6 @@ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "turf-jsts": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", @@ -34284,9 +34167,9 @@ } }, "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true }, "typewise": { @@ -34683,15 +34566,15 @@ } }, "vite": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.4.tgz", - "integrity": "sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", + "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", "dev": true, "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", - "postcss": "^8.4.25", - "rollup": "^3.25.2" + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "dependencies": { "esbuild": { @@ -35149,9 +35032,9 @@ } }, "zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==" }, "zod-prisma": { "version": "0.5.4", @@ -35163,54 +35046,6 @@ "parenthesis": "^3.1.8", "ts-morph": "^13.0.2" } - }, - "@next/swc-darwin-x64": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", - "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", - "optional": true - }, - "@next/swc-linux-arm64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", - "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", - "optional": true - }, - "@next/swc-linux-arm64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", - "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", - "optional": true - }, - "@next/swc-linux-x64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", - "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", - "optional": true - }, - "@next/swc-linux-x64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", - "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", - "optional": true - }, - "@next/swc-win32-arm64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", - "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", - "optional": true - }, - "@next/swc-win32-ia32-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", - "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", - "optional": true - }, - "@next/swc-win32-x64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", - "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", - "optional": true } } } diff --git a/package.json b/package.json index d91c207f5..2a20538f4 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "secure-password": "4.0.0", "tailwindcss": "3.3.3", "uuid": "9.0.0", - "zod": "3.21.4" + "zod": "3.22.3" }, "devDependencies": { "@next/bundle-analyzer": "13.5.2", @@ -92,7 +92,7 @@ "@types/preview-email": "3.0.1", "@types/react": "18.2.15", "@types/uuid": "9.0.2", - "@typescript-eslint/eslint-plugin": "6.1.0", + "@typescript-eslint/eslint-plugin": "6.13.0", "@vitejs/plugin-react": "4.0.3", "eslint": "8.45.0", "eslint-config-next": "13.5.2", @@ -103,7 +103,7 @@ "prettier-plugin-prisma": "5.0.0", "prettier-plugin-tailwindcss": "0.4.1", "preview-email": "3.0.19", - "typescript": "^5.1.6", + "typescript": "5.3.2", "vite-tsconfig-paths": "4.2.0", "vitest": "0.33.0", "zod-prisma": "0.5.4" diff --git a/src/core/components/Map/ProjectMap.tsx b/src/core/components/Map/ProjectMap.tsx index 3ccd8e941..0f09ba3fe 100644 --- a/src/core/components/Map/ProjectMap.tsx +++ b/src/core/components/Map/ProjectMap.tsx @@ -42,6 +42,7 @@ export const ProjectMap: React.FC = ({ subsections }) => { // @ts-expect-error mainMap?.fitBounds(boundingBox, { padding: 60 }) // @ts-expect-error + // eslint-disable-next-line react-hooks/exhaustive-deps }, [mainMap, ...boundingBox]) type HandleSelectProps = { subsectionSlug: string; edit: boolean } diff --git a/src/participation/components/maps/StaticPin.tsx b/src/core/components/Map/SurveyStaticPin.tsx similarity index 84% rename from src/participation/components/maps/StaticPin.tsx rename to src/core/components/Map/SurveyStaticPin.tsx index 0c4155355..62c1a0b7e 100644 --- a/src/participation/components/maps/StaticPin.tsx +++ b/src/core/components/Map/SurveyStaticPin.tsx @@ -1,13 +1,16 @@ import * as React from "react" +type Props = { + color: string +} -const StaticPin = () => { +const SurveyStaticPin: React.FC = ({ color }) => { return ( { ) } -export default React.memo(StaticPin) +export default React.memo(SurveyStaticPin) diff --git a/src/core/components/forms/LabeledCheckbox.tsx b/src/core/components/forms/LabeledCheckbox.tsx index a6014c250..5c0d6b9c5 100644 --- a/src/core/components/forms/LabeledCheckbox.tsx +++ b/src/core/components/forms/LabeledCheckbox.tsx @@ -6,7 +6,10 @@ import { useFormContext } from "react-hook-form" export interface LabeledCheckboxProps extends PropsWithoutRef { /** Checkbox scope. */ scope: string - /** The field value must be a string. If the value is a number in the DB, it needs to be parsed to a string to be used as `initialValues`. When passed to the mutation, the value needs to be parsed back to a number using `parseInt`. This requires corresponding modifications to the ZOD schemas. */ + /* The field value must be a string. If the value is a number in the DB, + * it needs to be parsed to a string to be used as `initialValues`. + * When passed to the mutation, the value needs to be parsed back to a number using `parseInt`. + * This requires corresponding modifications to the ZOD schemas. */ value: string /** Field label */ label: string | React.ReactNode diff --git a/src/core/components/forms/LabeledFormatNumberField.tsx b/src/core/components/forms/LabeledFormatNumberField.tsx index ba05a36cf..4979ba70b 100644 --- a/src/core/components/forms/LabeledFormatNumberField.tsx +++ b/src/core/components/forms/LabeledFormatNumberField.tsx @@ -52,6 +52,7 @@ export const LabeledFormatNumberField = forwardRef { @@ -66,6 +67,8 @@ export const LabeledFormatNumberField = forwardRef = ( + props, +) => { + const { setValue, getValues } = useFormContext() + + function isPoint(geometry: any[]) { + return ( + geometry.length === 2 && typeof geometry[0] === "number" && typeof geometry[1] === "number" + ) + } + + const calculateLength = () => { + const geometry = getValues("geometry") + const calculatedLength = length(lineString(geometry)) + setValue(props.name, calculatedLength) + } + + let helpText: string + + if (props.readOnly) { + helpText = + "Dieser Wert wird aus den Geometrien (Felt) berechnet und kann nicht manuell editiert werden." + } else if (!getValues("geometry")) { + helpText = "Es ist keine Geometrie vorhanden" + } else if (isPoint(getValues("geometry"))) { + helpText = + "Die Geometrie ist ein Punkt. Die Länge kann nicht berechnet, sondern nur manuell eingetragen werden. " + } else { + helpText = + "Dieser Wert kann manuell eingetragen oder aus den vorhandenen Geometrien berechnet werden." + } + + return ( +
+ + + +
+ ) +} diff --git a/src/core/layouts/Layout.tsx b/src/core/layouts/Layout.tsx index 3bcafa274..0a8e5344d 100644 --- a/src/core/layouts/Layout.tsx +++ b/src/core/layouts/Layout.tsx @@ -7,10 +7,10 @@ import { NavigationGeneral, NavigationProject } from "./Navigation" import { TailwindResponsiveHelper } from "./TailwindResponsiveHelper/TailwindResponsiveHelper" type Props = { - navigation: "general" | "project" | "none" | "participation" - footer: "general" | "project" | "minimal" | "participation" - fullWidth?: boolean + navigation: "general" | "project" | "none" + footer: "general" | "project" | "minimal" children?: React.ReactNode + fullWidth?: boolean } export const Layout: BlitzLayout = ({ diff --git a/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/[subsubsectionSlug]/edit.tsx b/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/[subsubsectionSlug]/edit.tsx index 3cdc5eb89..9d0864498 100644 --- a/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/[subsubsectionSlug]/edit.tsx +++ b/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/[subsubsectionSlug]/edit.tsx @@ -17,6 +17,7 @@ import deleteSubsubsection from "src/subsubsections/mutations/deleteSubsubsectio import updateSubsubsection from "src/subsubsections/mutations/updateSubsubsection" import getSubsubsection from "src/subsubsections/queries/getSubsubsection" import { SubsubsectionFormSchema } from "src/subsubsections/schema" +import m2mFields from "src/subsubsections/m2mFields" const EditSubsubsection = () => { const router = useRouter() @@ -69,6 +70,17 @@ const EditSubsubsection = () => { } } + let m2mFieldsInitialValues = {} + m2mFields.forEach((fieldName) => { + if (fieldName in subsubsection) { + // @ts-ignore + m2mFieldsInitialValues[fieldName] = Array.from(subsubsection[fieldName].values(), (obj) => + // @ts-ignore + String(obj.id), + ) + } + }) + return ( <> @@ -78,8 +90,10 @@ const EditSubsubsection = () => { className="mt-10" submitText="Speichern" schema={SubsubsectionFormSchema} + // @ts-ignore initialValues={{ ...subsubsection, + ...m2mFieldsInitialValues, trafficLoadDate: subsubsection.trafficLoadDate ? getDate(subsubsection.trafficLoadDate) : "", diff --git a/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/new.tsx b/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/new.tsx index 0207bb0fb..f10740be9 100644 --- a/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/new.tsx +++ b/src/pages/[projectSlug]/abschnitte/[subsectionSlug]/fuehrung/new.tsx @@ -33,7 +33,9 @@ const NewSubsubsection = () => { try { const subsubsection = await createSubsubsectionMutation({ ...values, + // @ts-ignore subsectionId: subsection!.id, + // @ts-ignore trafficLoadDate: values.trafficLoadDate === "" ? null : new Date(values.trafficLoadDate), }) await router.push( @@ -58,6 +60,7 @@ const NewSubsubsection = () => { /> { { - + + + Mehrere Planungsabschnitte erstellen + + + Felt Import für Planungsabschnitte + + ) diff --git a/src/pages/[projectSlug]/quality-levels/[qualityLevelId]/edit.tsx b/src/pages/[projectSlug]/quality-levels/[qualityLevelId]/edit.tsx index 74c60bdf6..789916a66 100644 --- a/src/pages/[projectSlug]/quality-levels/[qualityLevelId]/edit.tsx +++ b/src/pages/[projectSlug]/quality-levels/[qualityLevelId]/edit.tsx @@ -67,7 +67,7 @@ const EditQualityLevelWithQuery = () => { const EditQualityLevelPage: BlitzPage = () => { return ( - + }> diff --git a/src/pages/[projectSlug]/quality-levels/new.tsx b/src/pages/[projectSlug]/quality-levels/new.tsx index 8aee894b3..f342481ed 100644 --- a/src/pages/[projectSlug]/quality-levels/new.tsx +++ b/src/pages/[projectSlug]/quality-levels/new.tsx @@ -30,7 +30,7 @@ const NewQualityLevelPageWithQuery = () => { return ( <> - + { + const router = useRouter() + const subsubsectionInfraId = useParam("subsubsectionInfraId", "number") + const projectSlug = useParam("projectSlug", "string") + + const [subsubsectionInfra, { setQueryData }] = useQuery( + getSubsubsectionInfra, + { id: subsubsectionInfraId }, + { + // This ensures the query never refreshes and overwrites the form data while the user is editing. + staleTime: Infinity, + }, + ) + const [updateSubsubsectionInfraMutation] = useMutation(updateSubsubsectionInfra) + + type HandleSubmit = any // TODO + const handleSubmit = async (values: HandleSubmit) => { + try { + const updated = await updateSubsubsectionInfraMutation({ + id: subsubsectionInfra.id, + ...values, + }) + await setQueryData(updated) + await router.push(Routes.SubsubsectionInfrasPage({ projectSlug: projectSlug! })) + } catch (error: any) { + return improveErrorMessage(error, FORM_ERROR, ["slug"]) + } + } + + return ( + <> + + +

+ + Zurück zur Übersicht + +

+ + + + ) +} + +const EditSubsubsectionInfraPage: BlitzPage = () => { + return ( + + + + + }> + + + + ) +} + +EditSubsubsectionInfraPage.authenticate = true + +export default EditSubsubsectionInfraPage diff --git a/src/pages/[projectSlug]/subsubsection-infra/index.tsx b/src/pages/[projectSlug]/subsubsection-infra/index.tsx new file mode 100644 index 000000000..3e9010e52 --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-infra/index.tsx @@ -0,0 +1,146 @@ +import { BlitzPage, Routes, useParam } from "@blitzjs/next" +import { useMutation, usePaginatedQuery } from "@blitzjs/rpc" +import clsx from "clsx" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { SuperAdminLogData } from "src/core/components/AdminBox/SuperAdminLogData" +import { Link, linkIcons, linkStyles } from "src/core/components/links" +import { ButtonWrapper } from "src/core/components/links/ButtonWrapper" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { Pagination } from "src/core/components/Pagination" +import { Spinner } from "src/core/components/Spinner" +import { TableWrapper } from "src/core/components/Table/TableWrapper" +import { quote, shortTitle } from "src/core/components/text" +import { LayoutRs, MetaTags } from "src/core/layouts" +import deleteSubsubsectionInfra from "src/subsubsectionInfra/mutations/deleteSubsubsectionInfra" +import getSubsubsectionInfrasWithCount from "src/subsubsectionInfra/queries/getSubsubsectionInfrasWithCount" + +const ITEMS_PER_PAGE = 100 + +export const SubsubsectionInfrasWithData = () => { + const projectSlug = useParam("projectSlug", "string") + const router = useRouter() + const page = Number(router.query.page) || 0 + const [{ subsubsectionInfras, hasMore }] = usePaginatedQuery(getSubsubsectionInfrasWithCount, { + projectSlug: projectSlug!, + skip: ITEMS_PER_PAGE * page, + take: ITEMS_PER_PAGE, + }) + + const goToPreviousPage = () => router.push({ query: { page: page - 1 } }) + const goToNextPage = () => router.push({ query: { page: page + 1 } }) + + const [deleteSubsubsectionInfraMutation] = useMutation(deleteSubsubsectionInfra) + const handleDelete = async (subsubsectionInfraId: number) => { + if (window.confirm(`Den Eintrag mit ID ${subsubsectionInfraId} unwiderruflich löschen?`)) { + await deleteSubsubsectionInfraMutation({ id: subsubsectionInfraId }) + await router.push(Routes.SubsubsectionInfrasPage({ projectSlug: projectSlug! })) + } + } + + return ( + <> + + + + + + + + + + + + {subsubsectionInfras.map((Infra) => { + return ( + + + + + + + ) + })} + +
+ Kurz-Titel + + Titel + + Anzahl Führungen mit diesem Infra + + Aktionen +
+ {shortTitle(Infra.slug)} + + {Infra.title} + + {Infra.subsubsectionCount} Führungen mit der Form {quote(Infra.title)} + + + + Bearbeiten + + + +
+
+ + Neue Führungsform + + + + + + + ) +} + +const SubsubsectionInfrasPage: BlitzPage = () => { + return ( + + + + + }> + + + + ) +} + +SubsubsectionInfrasPage.authenticate = true + +export default SubsubsectionInfrasPage diff --git a/src/pages/[projectSlug]/subsubsection-infra/new.tsx b/src/pages/[projectSlug]/subsubsection-infra/new.tsx new file mode 100644 index 000000000..0bdd0575b --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-infra/new.tsx @@ -0,0 +1,58 @@ +import { Routes } from "@blitzjs/next" +import { useMutation } from "@blitzjs/rpc" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { Spinner } from "src/core/components/Spinner" +import { improveErrorMessage } from "src/core/components/forms/improveErrorMessage" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { seoNewTitle } from "src/core/components/text" +import { useSlugs } from "src/core/hooks" +import { LayoutRs, MetaTags } from "src/core/layouts" +import { SubsubsectionInfraForm } from "src/subsubsectionInfra/components/SubsubsectionInfraForm" +import createSubsubsectionInfra from "src/subsubsectionInfra/mutations/createSubsubsectionInfra" +import { SubsubsectionInfra } from "src/subsubsectionInfra/schema" +import { FORM_ERROR } from "src/subsubsections/components/SubsubsectionForm" + +const NewSubsubsectionInfraPageWithQuery = () => { + const router = useRouter() + const { projectSlug } = useSlugs() + const [createSubsubsectionInfraMutation] = useMutation(createSubsubsectionInfra) + + type HandleSubmit = any // TODO + const handleSubmit = async (values: HandleSubmit) => { + try { + await createSubsubsectionInfraMutation({ ...values, projectSlug: projectSlug! }) + await router.push(Routes.SubsubsectionInfrasPage({ projectSlug: projectSlug! })) + } catch (error: any) { + return improveErrorMessage(error, FORM_ERROR, ["slug"]) + } + } + + return ( + <> + + + + + + ) +} + +const NewSubsubsectionInfraPage = () => { + return ( + + }> + + + + ) +} + +NewSubsubsectionInfraPage.authenticate = true + +export default NewSubsubsectionInfraPage diff --git a/src/pages/[projectSlug]/subsubsection-special/[subsubsectionSpecialId]/edit.tsx b/src/pages/[projectSlug]/subsubsection-special/[subsubsectionSpecialId]/edit.tsx new file mode 100644 index 000000000..d831e5006 --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-special/[subsubsectionSpecialId]/edit.tsx @@ -0,0 +1,85 @@ +import { BlitzPage, Routes, useParam } from "@blitzjs/next" +import { useMutation, useQuery } from "@blitzjs/rpc" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { SuperAdminLogData } from "src/core/components/AdminBox/SuperAdminLogData" +import { Spinner } from "src/core/components/Spinner" +import { improveErrorMessage } from "src/core/components/forms/improveErrorMessage" +import { Link } from "src/core/components/links" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { seoEditTitle } from "src/core/components/text" +import { LayoutRs, MetaTags } from "src/core/layouts" +import { + FORM_ERROR, + SubsubsectionSpecialForm, +} from "src/subsubsectionSpecial/components/SubsubsectionSpecialForm" +import updateSubsubsectionSpecial from "src/subsubsectionSpecial/mutations/updateSubsubsectionSpecial" +import getSubsubsectionSpecial from "src/subsubsectionSpecial/queries/getSubsubsectionSpecial" +import { SubsubsectionSpecial } from "src/subsubsectionSpecial/schema" + +const EditSubsubsectionsSpecialWithQuery = () => { + const router = useRouter() + const subsubsectionSpecialId = useParam("subsubsectionSpecialId", "number") + const projectSlug = useParam("projectSlug", "string") + + const [subsubsectionSpecial, { setQueryData }] = useQuery( + getSubsubsectionSpecial, + { id: subsubsectionSpecialId }, + { + // This ensures the query never refreshes and overwrites the form data while the user is editing. + staleTime: Infinity, + }, + ) + const [updateSubsubsectionSpecialMutation] = useMutation(updateSubsubsectionSpecial) + + type HandleSubmit = any // TODO + const handleSubmit = async (values: HandleSubmit) => { + try { + const updated = await updateSubsubsectionSpecialMutation({ + id: subsubsectionSpecial.id, + ...values, + }) + await setQueryData(updated) + await router.push(Routes.SubsubsectionSpecialsPage({ projectSlug: projectSlug! })) + } catch (error: any) { + return improveErrorMessage(error, FORM_ERROR, ["slug"]) + } + } + + return ( + <> + + +

+ + Zurück zur Übersicht + +

+ + + + ) +} + +const EditSubsubsectionSpecialPage: BlitzPage = () => { + return ( + + + + + }> + + + + ) +} + +EditSubsubsectionSpecialPage.authenticate = true + +export default EditSubsubsectionSpecialPage diff --git a/src/pages/[projectSlug]/subsubsection-special/index.tsx b/src/pages/[projectSlug]/subsubsection-special/index.tsx new file mode 100644 index 000000000..070b29b58 --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-special/index.tsx @@ -0,0 +1,150 @@ +import { BlitzPage, Routes, useParam } from "@blitzjs/next" +import { useMutation, usePaginatedQuery } from "@blitzjs/rpc" +import clsx from "clsx" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { SuperAdminLogData } from "src/core/components/AdminBox/SuperAdminLogData" +import { Link, linkIcons, linkStyles } from "src/core/components/links" +import { ButtonWrapper } from "src/core/components/links/ButtonWrapper" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { Pagination } from "src/core/components/Pagination" +import { Spinner } from "src/core/components/Spinner" +import { TableWrapper } from "src/core/components/Table/TableWrapper" +import { quote, shortTitle } from "src/core/components/text" +import { LayoutRs, MetaTags } from "src/core/layouts" +import deleteSubsubsectionSpecial from "src/subsubsectionSpecial/mutations/deleteSubsubsectionSpecial" +import getSubsubsectionSpecialsWithCount from "src/subsubsectionSpecial/queries/getSubsubsectionSpecialsWithCount" + +const ITEMS_PER_PAGE = 100 + +export const SubsubsectionSpecialsWithData = () => { + const projectSlug = useParam("projectSlug", "string") + const router = useRouter() + const page = Number(router.query.page) || 0 + const [{ subsubsectionSpecials, hasMore }] = usePaginatedQuery( + getSubsubsectionSpecialsWithCount, + { + projectSlug: projectSlug!, + skip: ITEMS_PER_PAGE * page, + take: ITEMS_PER_PAGE, + }, + ) + + const goToPreviousPage = () => router.push({ query: { page: page - 1 } }) + const goToNextPage = () => router.push({ query: { page: page + 1 } }) + + const [deleteSubsubsectionSpecialMutation] = useMutation(deleteSubsubsectionSpecial) + const handleDelete = async (subsubsectionSpecialId: number) => { + if (window.confirm(`Den Eintrag mit ID ${subsubsectionSpecialId} unwiderruflich löschen?`)) { + await deleteSubsubsectionSpecialMutation({ id: subsubsectionSpecialId }) + await router.push(Routes.SubsubsectionSpecialsPage({ projectSlug: projectSlug! })) + } + } + + return ( + <> + + + + + + + + + + + + {subsubsectionSpecials.map((Special) => { + return ( + + + + + + + ) + })} + +
+ Kurz-Titel + + Titel + + Anzahl Führungen mit dieser Besonderheit + + Aktionen +
+ {shortTitle(Special.slug)} + + {Special.title} + + {Special.subsubsectionCount} Führungen mit der Besonderheit{" "} + {quote(Special.title)} + + + + Bearbeiten + + + +
+
+ + Neue Besonderheit + + + + + + + ) +} + +const SubsubsectionSpecialsPage: BlitzPage = () => { + return ( + + + + + }> + + + + ) +} + +SubsubsectionSpecialsPage.authenticate = true + +export default SubsubsectionSpecialsPage diff --git a/src/pages/[projectSlug]/subsubsection-special/new.tsx b/src/pages/[projectSlug]/subsubsection-special/new.tsx new file mode 100644 index 000000000..5f3491b2f --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-special/new.tsx @@ -0,0 +1,58 @@ +import { Routes } from "@blitzjs/next" +import { useMutation } from "@blitzjs/rpc" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { Spinner } from "src/core/components/Spinner" +import { improveErrorMessage } from "src/core/components/forms/improveErrorMessage" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { seoNewTitle } from "src/core/components/text" +import { useSlugs } from "src/core/hooks" +import { LayoutRs, MetaTags } from "src/core/layouts" +import { SubsubsectionSpecialForm } from "src/subsubsectionSpecial/components/SubsubsectionSpecialForm" +import createSubsubsectionSpecial from "src/subsubsectionSpecial/mutations/createSubsubsectionSpecial" +import { SubsubsectionSpecial } from "src/subsubsectionSpecial/schema" +import { FORM_ERROR } from "src/subsubsections/components/SubsubsectionForm" + +const NewSubsubsectionSpecialPageWithQuery = () => { + const router = useRouter() + const { projectSlug } = useSlugs() + const [createSubsubsectionSpecialMutation] = useMutation(createSubsubsectionSpecial) + + type HandleSubmit = any // TODO + const handleSubmit = async (values: HandleSubmit) => { + try { + await createSubsubsectionSpecialMutation({ ...values, projectSlug: projectSlug! }) + await router.push(Routes.SubsubsectionSpecialsPage({ projectSlug: projectSlug! })) + } catch (error: any) { + return improveErrorMessage(error, FORM_ERROR, ["slug"]) + } + } + + return ( + <> + + + + + + ) +} + +const NewSubsubsectionSpecialPage = () => { + return ( + + }> + + + + ) +} + +NewSubsubsectionSpecialPage.authenticate = true + +export default NewSubsubsectionSpecialPage diff --git a/src/pages/[projectSlug]/subsubsection-status/[subsubsectionStatusId]/edit.tsx b/src/pages/[projectSlug]/subsubsection-status/[subsubsectionStatusId]/edit.tsx index 4fb67c82e..f594f7db3 100644 --- a/src/pages/[projectSlug]/subsubsection-status/[subsubsectionStatusId]/edit.tsx +++ b/src/pages/[projectSlug]/subsubsection-status/[subsubsectionStatusId]/edit.tsx @@ -70,8 +70,8 @@ const EditSubsubsectionsStatusWithQuery = () => { const EditSubsubsectionStatusPage: BlitzPage = () => { return ( - - + + }> diff --git a/src/pages/[projectSlug]/subsubsection-status/index.tsx b/src/pages/[projectSlug]/subsubsection-status/index.tsx index 832c2094f..0962ce13d 100644 --- a/src/pages/[projectSlug]/subsubsection-status/index.tsx +++ b/src/pages/[projectSlug]/subsubsection-status/index.tsx @@ -75,7 +75,7 @@ export const SubsubsectionStatussWithData = () => { {status.title} - {status.subsubsectionCount} Führungen mit dem Staus {quote(status.title)} + {status.subsubsectionCount} Führungen mit dem Status {quote(status.title)} @@ -131,8 +131,8 @@ export const SubsubsectionStatussWithData = () => { const SubsubsectionStatussPage: BlitzPage = () => { return ( - - + + }> diff --git a/src/pages/[projectSlug]/subsubsection-status/new.tsx b/src/pages/[projectSlug]/subsubsection-status/new.tsx index fa36670f9..69ea25528 100644 --- a/src/pages/[projectSlug]/subsubsection-status/new.tsx +++ b/src/pages/[projectSlug]/subsubsection-status/new.tsx @@ -30,8 +30,8 @@ const NewSubsubsectionStatusPageWithQuery = () => { return ( <> - - + + { + const router = useRouter() + const subsubsectionTaskId = useParam("subsubsectionTaskId", "number") + const projectSlug = useParam("projectSlug", "string") + + const [subsubsectionTask, { setQueryData }] = useQuery( + getSubsubsectionTask, + { id: subsubsectionTaskId }, + { + // This ensures the query never refreshes and overwrites the form data while the user is editing. + staleTime: Infinity, + }, + ) + const [updateSubsubsectionTaskMutation] = useMutation(updateSubsubsectionTask) + + type HandleSubmit = any // TODO + const handleSubmit = async (values: HandleSubmit) => { + try { + const updated = await updateSubsubsectionTaskMutation({ + id: subsubsectionTask.id, + ...values, + }) + await setQueryData(updated) + await router.push(Routes.SubsubsectionTasksPage({ projectSlug: projectSlug! })) + } catch (error: any) { + return improveErrorMessage(error, FORM_ERROR, ["slug"]) + } + } + + return ( + <> + + +

+ + Zurück zur Übersicht + +

+ + + + ) +} + +const EditSubsubsectionTaskPage: BlitzPage = () => { + return ( + + + + + }> + + + + ) +} + +EditSubsubsectionTaskPage.authenticate = true + +export default EditSubsubsectionTaskPage diff --git a/src/pages/[projectSlug]/subsubsection-task/index.tsx b/src/pages/[projectSlug]/subsubsection-task/index.tsx new file mode 100644 index 000000000..00be3a9c5 --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-task/index.tsx @@ -0,0 +1,146 @@ +import { BlitzPage, Routes, useParam } from "@blitzjs/next" +import { useMutation, usePaginatedQuery } from "@blitzjs/rpc" +import clsx from "clsx" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { SuperAdminLogData } from "src/core/components/AdminBox/SuperAdminLogData" +import { Link, linkIcons, linkStyles } from "src/core/components/links" +import { ButtonWrapper } from "src/core/components/links/ButtonWrapper" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { Pagination } from "src/core/components/Pagination" +import { Spinner } from "src/core/components/Spinner" +import { TableWrapper } from "src/core/components/Table/TableWrapper" +import { quote, shortTitle } from "src/core/components/text" +import { LayoutRs, MetaTags } from "src/core/layouts" +import deleteSubsubsectionTask from "src/subsubsectionTask/mutations/deleteSubsubsectionTask" +import getSubsubsectionTasksWithCount from "src/subsubsectionTask/queries/getSubsubsectionTasksWithCount" + +const ITEMS_PER_PAGE = 100 + +export const SubsubsectionTasksWithData = () => { + const projectSlug = useParam("projectSlug", "string") + const router = useRouter() + const page = Number(router.query.page) || 0 + const [{ subsubsectionTasks, hasMore }] = usePaginatedQuery(getSubsubsectionTasksWithCount, { + projectSlug: projectSlug!, + skip: ITEMS_PER_PAGE * page, + take: ITEMS_PER_PAGE, + }) + + const goToPreviousPage = () => router.push({ query: { page: page - 1 } }) + const goToNextPage = () => router.push({ query: { page: page + 1 } }) + + const [deleteSubsubsectionTaskMutation] = useMutation(deleteSubsubsectionTask) + const handleDelete = async (subsubsectionTaskId: number) => { + if (window.confirm(`Den Eintrag mit ID ${subsubsectionTaskId} unwiderruflich löschen?`)) { + await deleteSubsubsectionTaskMutation({ id: subsubsectionTaskId }) + await router.push(Routes.SubsubsectionTasksPage({ projectSlug: projectSlug! })) + } + } + + return ( + <> + + + + + + + + + + + + {subsubsectionTasks.map((Task) => { + return ( + + + + + + + ) + })} + +
+ Kurz-Titel + + Titel + + Anzahl Führungen mit dieser Maßnahme + + Aktionen +
+ {shortTitle(Task.slug)} + + {Task.title} + + {Task.subsubsectionCount} Führungen mit der Maßnahme {quote(Task.title)} + + + + Bearbeiten + + + +
+
+ + Neue Maßnahme + + + + + + + ) +} + +const SubsubsectionTasksPage: BlitzPage = () => { + return ( + + + + + }> + + + + ) +} + +SubsubsectionTasksPage.authenticate = true + +export default SubsubsectionTasksPage diff --git a/src/pages/[projectSlug]/subsubsection-task/new.tsx b/src/pages/[projectSlug]/subsubsection-task/new.tsx new file mode 100644 index 000000000..9218f7e73 --- /dev/null +++ b/src/pages/[projectSlug]/subsubsection-task/new.tsx @@ -0,0 +1,58 @@ +import { Routes } from "@blitzjs/next" +import { useMutation } from "@blitzjs/rpc" +import { useRouter } from "next/router" +import { Suspense } from "react" +import { Spinner } from "src/core/components/Spinner" +import { improveErrorMessage } from "src/core/components/forms/improveErrorMessage" +import { PageHeader } from "src/core/components/pages/PageHeader" +import { seoNewTitle } from "src/core/components/text" +import { useSlugs } from "src/core/hooks" +import { LayoutRs, MetaTags } from "src/core/layouts" +import { SubsubsectionTaskForm } from "src/subsubsectionTask/components/SubsubsectionTaskForm" +import createSubsubsectionTask from "src/subsubsectionTask/mutations/createSubsubsectionTask" +import { SubsubsectionTask } from "src/subsubsectionTask/schema" +import { FORM_ERROR } from "src/subsubsections/components/SubsubsectionForm" + +const NewSubsubsectionTaskPageWithQuery = () => { + const router = useRouter() + const { projectSlug } = useSlugs() + const [createSubsubsectionTaskMutation] = useMutation(createSubsubsectionTask) + + type HandleSubmit = any // TODO + const handleSubmit = async (values: HandleSubmit) => { + try { + await createSubsubsectionTaskMutation({ ...values, projectSlug: projectSlug! }) + await router.push(Routes.SubsubsectionTasksPage({ projectSlug: projectSlug! })) + } catch (error: any) { + return improveErrorMessage(error, FORM_ERROR, ["slug"]) + } + } + + return ( + <> + + + + + + ) +} + +const NewSubsubsectionTaskPage = () => { + return ( + + }> + + + + ) +} + +NewSubsubsectionTaskPage.authenticate = true + +export default NewSubsubsectionTaskPage diff --git a/src/pages/[projectSlug]/surveys/[surveyId]/index.tsx b/src/pages/[projectSlug]/surveys/[surveyId]/index.tsx index 5e17e461c..e6aaf805d 100644 --- a/src/pages/[projectSlug]/surveys/[surveyId]/index.tsx +++ b/src/pages/[projectSlug]/surveys/[surveyId]/index.tsx @@ -10,8 +10,12 @@ import { PageHeader } from "src/core/components/pages/PageHeader" import { H2 } from "src/core/components/text" import { useSlugs } from "src/core/hooks" import { LayoutRs, MetaTags } from "src/core/layouts" -import surveyDefinition from "src/participation/data/survey.json" -import { Survey as TSurvey } from "src/participation/data/types" +import { TSurvey } from "src/survey-public/components/types" +import { + getFeedbackDefinitionBySurveySlug, + getResponseConfigBySurveySlug, + getSurveyDefinitionBySurveySlug, +} from "src/survey-public/utils/getConfigBySurveySlug" import { GroupedSurveyResponseItem } from "src/survey-responses/components/analysis/GroupedSurveyResponseItem" import getGroupedSurveyResponses from "src/survey-responses/queries/getGroupedSurveyResponses" import { getFormatDistanceInDays } from "src/survey-responses/utils/getFormatDistanceInDays" @@ -64,9 +68,21 @@ export const Survey = () => { return transformedArray } + const feedbackDefinition = getFeedbackDefinitionBySurveySlug(survey.slug) + const surveyDefinition = getSurveyDefinitionBySurveySlug(survey.slug) + const responseConfig = getResponseConfigBySurveySlug(survey.slug) + + const feedbackQuestions = [] + + for (let page of feedbackDefinition.pages) { + page.questions && feedbackQuestions.push(...page.questions) + } + + const userLocationQuestionId = responseConfig?.evaluationRefs["feedback-location"] + const surveyResponsesFeedbackPartWithLocation = surveyResponsesFeedbackPart.filter( // @ts-expect-error - (r) => JSON.parse(r.data)["23"], + (r) => JSON.parse(r.data)[userLocationQuestionId], ) const isSurveyPast = survey.endDate && isPast(survey.endDate) diff --git a/src/pages/api/survey/[surveyId]/_shared.ts b/src/pages/api/survey/[surveyId]/_shared.ts deleted file mode 100644 index d4bd2a75d..000000000 --- a/src/pages/api/survey/[surveyId]/_shared.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { createObjectCsvStringifier } from "csv-writer" -import { NextApiRequest, NextApiResponse } from "next" -import { api } from "src/blitz-server" -import { getSession } from "@blitzjs/auth" -import { AuthorizationError } from "blitz" -import { ZodError } from "zod" -import dbGetSurvey from "src/surveys/queries/getSurvey" -import { Survey } from "db" - -const DEBUG = false - -export const getSurvey = async ( - req: NextApiRequest, - res: NextApiResponse, -): Promise => { - await api(() => null) - - const err = (status: number, message: string) => { - res.status(status).json({ error: true, status: status, message }) - res.end() - } - - const session = await getSession(req, res) - - let survey - try { - // @ts-ignore - survey = await dbGetSurvey({ id: Number(req.query.surveyId) }, { session }) - } catch (e) { - if (e instanceof AuthorizationError) { - err(403, "Forbidden") - } - // @ts-ignore - if (e.code === "P2025" || e instanceof ZodError) { - err(404, "Not Found") - } - - console.error(e) - err(500, "Internal Server Error") - - return - } - - return survey -} - -export const sendCsv = ( - res: NextApiResponse, - headers: { id: string; title: string }[], - data: Record[], - filename: string, -) => { - const csvStringifier = createObjectCsvStringifier({ - header: headers, - fieldDelimiter: ";", - alwaysQuote: true, - }) - const csvString = csvStringifier.getHeaderString() + csvStringifier.stringifyRecords(data) - if (DEBUG) { - res.setHeader("Content-Type", "text/plain") - } else { - res.setHeader("Content-Type", "text/csv") - res.setHeader("Content-Disposition", `attachment; filename=${filename}`) - } - res.send(csvString) -} diff --git a/src/pages/api/survey/[surveyId]/questions.ts b/src/pages/api/survey/[surveyId]/questions.ts deleted file mode 100644 index aee2302e6..000000000 --- a/src/pages/api/survey/[surveyId]/questions.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" -import surveyDefinition from "src/participation/data/survey.json" -import feedbackDefinition from "src/participation/data/feedback.json" -import { Survey } from "src/participation/data/types" -import { getSurvey, sendCsv } from "./_shared" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const survey = await getSurvey(req, res) - if (!survey) return - - const headers = [ - { id: "id", title: "id" }, - { id: "type", title: "type" }, - { id: "question", title: "text" }, - ] - - const types = { - singleResponse: "single", - multipleResponse: "multi", - } - - type Question = { id: number | string; type: string; question: string } - let data: Question[] = [] - const addQuestions = (definition: Survey) => { - definition.pages.forEach((page) => { - if (!page.questions) return - page.questions.forEach(({ id, component, label }) => { - // @ts-ignore - data.push({ id, type: types[component] || component, question: label.de }) - }) - }) - } - // @ts-ignore - addQuestions(surveyDefinition) - // @ts-ignore - addQuestions(feedbackDefinition) - data = data.filter(({ id }) => ![22, 31, 32, 33].includes(Number(id))) - - sendCsv(res, headers, data, "questions.csv") -} diff --git a/src/pages/api/survey/[surveyId]/responses.ts b/src/pages/api/survey/[surveyId]/responses.ts deleted file mode 100644 index 745eb0783..000000000 --- a/src/pages/api/survey/[surveyId]/responses.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" -import surveyDefinition from "src/participation/data/survey.json" -import feedbackDefinition from "src/participation/data/feedback.json" -import { Survey } from "src/participation/data/types" -import { getSurvey, sendCsv } from "./_shared" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const survey = await getSurvey(req, res) - if (!survey) return - - const headers = [ - { id: "questionId", title: "questionId" }, - { id: "responseId", title: "responseId" }, - { id: "text", title: "text" }, - ] - - type Question = { questionId: number | string; responseId: number | string; text: string } - let data: Question[] = [] - const addQuestions = (definition: Survey) => { - definition.pages.forEach((page) => { - if (!page.questions) return - page.questions.forEach((question) => { - if (!["singleResponse", "multipleResponse"].includes(question.component)) return - // @ts-ignore - question.props.responses.forEach((response) => { - data.push({ - questionId: question.id, - responseId: response.id, - text: response.text.de, - }) - }) - }) - }) - } - - // @ts-ignore - addQuestions(surveyDefinition) - // @ts-ignore - addQuestions(feedbackDefinition) - data = data.filter(({ questionId }) => questionId !== 22) - - sendCsv(res, headers, data, "responses.csv") -} diff --git a/src/pages/api/survey/[surveyId]/results.ts b/src/pages/api/survey/[surveyId]/results.ts deleted file mode 100644 index 5aa8fa898..000000000 --- a/src/pages/api/survey/[surveyId]/results.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" -import db from "db" -import surveyDefinition from "src/participation/data/survey.json" -import feedbackDefinition from "src/participation/data/feedback.json" -import { getSurvey, sendCsv } from "./_shared" - -const surveys = Object.fromEntries([surveyDefinition, feedbackDefinition].map((o) => [o.id, o])) -const questions = {} -Object.values(surveys).forEach((survey) => { - survey.pages.forEach((page) => { - if (!page.questions) return - page.questions.forEach((question) => { - // @ts-ignore - if (["singleResponse", "multipleResponse"].includes(question.component)) { - // @ts-ignore - question.responses = Object.fromEntries(question.props.responses.map((r) => [r.id, r])) - } - // @ts-ignore - questions[question.id] = question - }) - }) -}) - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const survey = await getSurvey(req, res) - if (!survey) return - - const surveySessions = await db.surveySession.findMany({ - where: { surveyId: survey.id }, - include: { responses: true }, - }) - - const headers = [ - { id: "createdAt", title: "createdAt" }, - { id: "sessionId", title: "sessionId" }, - { id: "questionId", title: "questionId" }, - { id: "responseId", title: "responseId" }, - { id: "responseIds", title: "responseIds" }, - { id: "responseText", title: "responseText" }, - { id: "responseData", title: "responseData" }, - ] - - type Result = { - createdAt: string - sessionId: string - questionId: string - responseId?: string - responseIds?: string - responseText?: string - responseData?: string - } - - const csvData: Result[] = [] - - surveySessions.forEach((surveySession) => { - const { id, createdAt, responses } = surveySession - console.log(id, createdAt.toISOString(), responses) - responses.forEach(({ data }) => { - // @ts-ignore - data = JSON.parse(data) - Object.entries(data).map(([questionId, responseData]) => { - // @ts-ignore - const question = questions[questionId] - let row: Result = { - createdAt: createdAt.toISOString(), - sessionId: String(id), - questionId: "n/a", - } - if (question.component === "singleResponse") { - const responseId = responseData - row = { ...row, questionId, responseId } - } else if (question.component === "multipleResponse") { - const responseIds = responseData - row = { ...row, questionId, responseIds } - } else if (question.component === "text") { - const responseText = responseData - row = { ...row, questionId, responseText } - } else { - row = { ...row, questionId, responseData: JSON.stringify(responseData) } - } - csvData.push(row) - }) - }) - }) - // - sendCsv(res, headers, csvData, "results.csv") -} diff --git a/src/pages/beteiligung/[surveySlug]/index.tsx b/src/pages/beteiligung/[surveySlug]/index.tsx index f6f8fbcb1..9a97b5a33 100644 --- a/src/pages/beteiligung/[surveySlug]/index.tsx +++ b/src/pages/beteiligung/[surveySlug]/index.tsx @@ -1,21 +1,40 @@ +import { BlitzPage } from "@blitzjs/auth" import { useParam } from "@blitzjs/next" import { useQuery } from "@blitzjs/rpc" import { Suspense } from "react" import { Spinner } from "src/core/components/Spinner" + +import { surveyDefinition as surveyDefinitionRS8 } from "src/survey-public/rs8/data/survey" +import { surveyDefinition as surveyDefinitionFRM7 } from "src/survey-public/frm7/data/survey" + +import SurveyInactivePage from "src/survey-public/components/SurveyInactivePage" +import { SurveyRS8 } from "src/survey-public/rs8/SurveyRS8" import getPublicSurveyBySlug from "src/surveys/queries/getPublicSurveyBySlug" -import ParticipationMainPage from "src/participation/components/rs8" -import ParticipationInactivePage from "src/participation/components/rs8-inactive" +import { SurveyFRM7 } from "src/survey-public/frm7/SurveyFRM7" -export const Survey = () => { +const PublicSurveyPageWithQuery = () => { const surveySlug = useParam("surveySlug", "string") const [survey] = useQuery(getPublicSurveyBySlug, { slug: surveySlug! }) - return survey.active ? : + // only returns something if there is a 'Survey' in the DB with the slug (url params) and the slug is either rs8 or frm7 + if (!survey) return null + if (surveySlug === "rs8") + return survey.active ? ( + + ) : ( + + ) + if (surveySlug === "frm7") + return survey.active ? ( + + ) : ( + + ) + return null } - -const PublicSurveyPage = () => { +const PublicSurveyPage: BlitzPage = () => { return ( }> - + ) } diff --git a/src/pages/survey-sessions/[surveySessionId].tsx b/src/pages/survey-sessions/[surveySessionId].tsx deleted file mode 100644 index 685230082..000000000 --- a/src/pages/survey-sessions/[surveySessionId].tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Routes, useParam } from "@blitzjs/next" -import { useQuery } from "@blitzjs/rpc" -import { Suspense } from "react" -import { Spinner } from "src/core/components/Spinner" -import { SuperAdminBox } from "src/core/components/AdminBox" -import { Link } from "src/core/components/links" -import { LayoutArticle, MetaTags } from "src/core/layouts" -import getSurveySessionWithResponses from "../../survey-sessions/queries/getSurveySessionWithResponses" -import { NotFoundError } from "blitz" - -export const SurveySession = () => { - const surveySessionId = useParam("surveySessionId", "number") - if (surveySessionId === undefined) throw new NotFoundError() - const [surveySession] = useQuery(getSurveySessionWithResponses, { id: surveySessionId }) - - return ( - <> - - -

SurveySession {surveySession.id}

- -
{JSON.stringify(surveySession, null, 2)}
-
- - ) -} - -const ShowSurveySessionPage = () => { - return ( - - }> - - - -
-

- Alle SurveySessions -

-
- ) -} - -ShowSurveySessionPage.authenticate = true - -export default ShowSurveySessionPage diff --git a/src/pages/survey-sessions/index.tsx b/src/pages/survey-sessions/index.tsx deleted file mode 100644 index 3beb91aca..000000000 --- a/src/pages/survey-sessions/index.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { Routes } from "@blitzjs/next" -import { usePaginatedQuery } from "@blitzjs/rpc" -import { SurveyResponse } from "db" -import { useRouter } from "next/router" -import { Suspense } from "react" - -import { Pagination } from "src/core/components/Pagination" -import { Spinner } from "src/core/components/Spinner" -import { Link } from "src/core/components/links" -import { LayoutArticle, MetaTags } from "src/core/layouts" -import feedbackDefinition from "src/participation/data/feedback.json" -import surveyDefinition from "src/participation/data/survey.json" -import getSurveySessionsWithResponses from "src/survey-sessions/queries/getSurveySessionsWithResponses" - -const surveys = Object.fromEntries([surveyDefinition, feedbackDefinition].map((o) => [o.id, o])) -const questions = {} -Object.values(surveys).forEach((survey) => { - survey.pages.forEach((page) => { - if (!page.questions) return - page.questions.forEach((question) => { - // @ts-ignore - if (["singleResponse", "multipleResponse"].includes(question.component)) { - // @ts-ignore - question.responses = Object.fromEntries(question.props.responses.map((r) => [r.id, r])) - } - // @ts-ignore - questions[question.id] = question - }) - }) -}) - -const ITEMS_PER_PAGE = 10 - -export const SurveySessionsList = () => { - const router = useRouter() - const page = Number(router.query.page) || 0 - const [{ surveySessions, hasMore, count }] = usePaginatedQuery(getSurveySessionsWithResponses, { - skip: ITEMS_PER_PAGE * page, - take: ITEMS_PER_PAGE, - }) - - const goToPreviousPage = () => router.push({ query: { page: page - 1 } }) - const goToNextPage = () => router.push({ query: { page: page + 1 } }) - - return ( - <> -

SurveySessions {count}

- -
    - {surveySessions.map((surveySession) => { - const { id, createdAt, responses } = surveySession - return ( -
  • - - - SurveySession {id} - {createdAt.toISOString()} - - - -
    - {responses.map(({ id, surveyId, data }: SurveyResponse) => { - const survey = surveys[surveyId] - data = JSON.parse(data) as any // TODO are there types somewhere for {[questionId]: responseData} ? - return ( - -
    -

    {survey!.title.de}

    - {Object.entries(data).map(([questionId, responseData]) => { - // @ts-ignore - const question = questions[questionId] - return ( -
    - - [{question.id}] {question.label.de} - {" "} - =  - {(() => { - if (question.component === "singleResponse") { - // @ts-ignore - const responseId = responseData as number | null - return responseId === null - ? "null" - : `[${responseId}] ${question.responses[responseId].text.de}` - } - if (question.component === "multipleResponse") { - // @ts-ignore - const responseIds = responseData as number[] - return responseIds - .map( - (responseId) => - `[${responseId}] ${question.responses[responseId].text.de}`, - ) - .join(", ") - } - if (question.component === "text") { - // @ts-ignore - const responseText = responseData as string | null - return JSON.stringify(responseText) - } - return JSON.stringify(responseData) - })()} -
    - ) - })} -
    -
    - ) - })} -
    -
  • - ) - })} -
- - - - ) -} - -const SurveySessionsPage = () => { - return ( - - - - }> - - - - ) -} - -SurveySessionsPage.authenticate = { role: "ADMIN" } - -export default SurveySessionsPage diff --git a/src/participation/components/Done.tsx b/src/participation/components/Done.tsx deleted file mode 100644 index a3e2eecd4..000000000 --- a/src/participation/components/Done.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export { FORM_ERROR } from "src/core/components/forms" -import { ParticipationLink } from "./core/links/ParticipationLink" -import { ScreenHeaderParticipation } from "./layout/ScreenHeaderParticipation" - -export const Done = () => { - return ( -
- - - Zurück Startseite - -
- ) -} diff --git a/src/participation/components/Email.tsx b/src/participation/components/Email.tsx deleted file mode 100644 index 1628abee4..000000000 --- a/src/participation/components/Email.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useCallback, useEffect, useState } from "react" -import { iframeResizer } from "iframe-resizer" - -export { FORM_ERROR } from "src/core/components/forms" -import { ParticipationH2, ParticipationP } from "./core/Text" -import { ParticipationLink } from "./core/links/ParticipationLink" -import SurveyForm from "./form/SurveyForm" -import { ScreenHeaderParticipation } from "./layout/ScreenHeaderParticipation" - -type Props = { - onSubmit: any - email: any // TODO -} - -export const Email: React.FC = ({ onSubmit, email }) => { - const [consent, setConsent] = useState(false) - - const handleSubmit = (values: Record) => { - onSubmit(values.email) - } - - const handleChange = useCallback((values: Record) => { - setConsent(values.consent && values.email) - }, []) - - const page = email.pages[0] - - useEffect(() => { - iframeResizer({}, "#mailjet-widget") - }, []) - - return ( -
- - - {page.questions[0].label.de} - {page.questions[0].props.text.de} - -
- `, - }} - /> - -
- - Zurück zur Startseite - -
- -
- ) -} diff --git a/src/participation/components/More.tsx b/src/participation/components/More.tsx deleted file mode 100644 index be0a5dcf9..000000000 --- a/src/participation/components/More.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ParticipationH2 } from "./core/Text" -import { ParticipationButton } from "./core/buttons/ParticipationButton" -import { ParticipationButtonWrapper } from "./core/buttons/ParticipationButtonWrapper" -import SurveyForm from "./form/SurveyForm" -import { ScreenHeaderParticipation } from "./layout/ScreenHeaderParticipation" - -export { FORM_ERROR } from "src/core/components/forms" - -type Props = { - onClickMore: any - onClickFinish: any - more: any // TODO -} - -export const More: React.FC = ({ more, onClickMore, onClickFinish }) => { - const { title, description, questions, buttons } = more.pages[0] - const question = questions[0] - - return ( - <> - - {question.label.de} - - - {buttons[0].label.de} - - - {buttons[1].label.de} - - - - ) -} diff --git a/src/participation/components/core/buttons/ParticipationButton.tsx b/src/participation/components/core/buttons/ParticipationButton.tsx deleted file mode 100644 index c38f062c1..000000000 --- a/src/participation/components/core/buttons/ParticipationButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import clsx from "clsx" -import { ReactNode } from "react" -import { participationPinkButtonStyles, participationWhiteButtonStyles } from "../links/styles" -export { FORM_ERROR } from "src/core/components/forms" - -type Props = { - color?: string - disabled?: boolean - children: string | ReactNode -} & React.ButtonHTMLAttributes - -export const ParticipationButton: React.FC = ({ - disabled, - color = "pink", - children, - ...props -}) => { - const buttonStyles = clsx( - "px-12", - color === "white" ? participationWhiteButtonStyles : participationPinkButtonStyles, - ) - - return ( - - ) -} diff --git a/src/participation/components/core/links/ParticipationLink.tsx b/src/participation/components/core/links/ParticipationLink.tsx deleted file mode 100644 index f157ea5e7..000000000 --- a/src/participation/components/core/links/ParticipationLink.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { RouteUrlObject } from "blitz" -import clsx from "clsx" -import NextLink from "next/link" -import { forwardRef } from "react" -import { selectParticipationLinkStyle } from "./styles" - -// the link component is duplicated to avoid dependencies between TS and 'Beteiligung' - -export type ParticipationLinkProps = { - href: RouteUrlObject | string - className?: string - classNameOverwrites?: string - /** @default `false` */ - blank?: boolean - /** @desc Style Link as Button */ - button?: true | "white" | "pink" - children: React.ReactNode -} & Omit, "href"> - -export const ParticipationLink: React.FC = forwardRef< - HTMLAnchorElement, - ParticipationLinkProps ->(({ href, className, classNameOverwrites, children, blank = false, button, ...props }, ref) => { - const classNames = clsx(classNameOverwrites ?? selectParticipationLinkStyle(button, className)) - - // external link - if (typeof href === "string") { - return ( - - {children} - - ) - } - - return ( - - {children} - - ) -}) diff --git a/src/participation/components/feedback/FeedbackFirstPage.tsx b/src/participation/components/feedback/FeedbackFirstPage.tsx deleted file mode 100644 index 3881140a2..000000000 --- a/src/participation/components/feedback/FeedbackFirstPage.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { MapProvider } from "react-map-gl/maplibre" -import { ParticipationButton } from "../core/buttons/ParticipationButton" -import { ScreenHeaderParticipation } from "../layout/ScreenHeaderParticipation" -import { ParticipationMap } from "../maps/ParticipationMap" -import { Question } from "../survey/Question" -import { ParticipationButtonWrapper } from "../core/buttons/ParticipationButtonWrapper" - -export { FORM_ERROR } from "src/core/components/forms" - -type Props = { - page: any // TODO - isMap: boolean - onButtonClick: any // TODO - isCompleted: boolean - mapIsDirtyProps: any -} - -export const FeedbackFirstPage: React.FC = ({ - isCompleted, - page, - isMap, - onButtonClick, - mapIsDirtyProps, -}) => { - const { title, description, questions, buttons } = page - - const mapProps = questions.find( - (question: Record) => question.component === "map", - ).props - - return ( - <> - - - - - {isMap && ( - - - - )} - {/* TODO Disabled */} - - - {buttons[0].label.de} - - - - ) -} diff --git a/src/participation/components/feedback/FeedbackSecondPage.tsx b/src/participation/components/feedback/FeedbackSecondPage.tsx deleted file mode 100644 index e6ef495ed..000000000 --- a/src/participation/components/feedback/FeedbackSecondPage.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useContext } from "react" -import { PinContext } from "src/participation/context/contexts" -import { ParticipationButton } from "../core/buttons/ParticipationButton" -import { ParticipationButtonWrapper } from "../core/buttons/ParticipationButtonWrapper" -import { ScreenHeaderParticipation } from "../layout/ScreenHeaderParticipation" -import { ParticipationH2, ParticipationH3, ParticipationP } from "../core/Text" -import { ParticipationStaticMap } from "../maps/ParticipationStaticMap" -import { Question } from "../survey/Question" -import { MultiLineString } from "@turf/helpers" - -export { FORM_ERROR } from "src/core/components/forms" - -type Props = { - page: any // TODO - onButtonClick: any // TODO - staticMapProps: { - projectGeometry: MultiLineString - layerStyles: Record - } - feedbackCategory: string - isCompleted: boolean -} - -export const FeedbackSecondPage: React.FC = ({ - page, - isCompleted, - onButtonClick, - staticMapProps, - feedbackCategory, -}) => { - const { pinPosition } = useContext(PinContext) - const { title, description, questions, buttons } = page - - const textAreaQuestions = questions.filter((q: Record) => q.component === "text") - - return ( - <> - - {questions[0].label.de} - {feedbackCategory} - - {pinPosition && ( - <> - {questions[1].label.de} - - - )} -
- - -
- - - - {buttons[0].label.de} - - - {buttons[1].label.de} - - - - Zurück - - - ) -} diff --git a/src/participation/components/form/ParticipationLabeledCheckbox.tsx b/src/participation/components/form/ParticipationLabeledCheckbox.tsx deleted file mode 100644 index 4f236a3f3..000000000 --- a/src/participation/components/form/ParticipationLabeledCheckbox.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { ErrorMessage } from "@hookform/error-message" -import clsx from "clsx" -import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef, ReactNode } from "react" -import { useFormContext } from "react-hook-form" - -export interface TParticipationLabeledCheckbox - extends PropsWithoutRef { - /** Field name. */ - name: string - /** Field label. */ - label: string | ReactNode - /** Help text below field label. */ - help?: string - outerProps?: PropsWithoutRef - labelProps?: ComponentPropsWithoutRef<"label"> -} - -export const ParticipationLabeledCheckbox = forwardRef< - HTMLInputElement, - TParticipationLabeledCheckbox ->(({ name, label, help, outerProps, labelProps, ...props }, ref) => { - const { - register, - formState: { isSubmitting, errors }, - } = useFormContext() - - const hasError = Boolean(errors[name]) - - return ( -
-
- -
- -
- ) -}) diff --git a/src/participation/components/form/ParticipationLabeledCheckboxGroup.tsx b/src/participation/components/form/ParticipationLabeledCheckboxGroup.tsx deleted file mode 100644 index f0de88cf9..000000000 --- a/src/participation/components/form/ParticipationLabeledCheckboxGroup.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import clsx from "clsx" -import React from "react" -import { - ParticipationLabeledCheckbox, - TParticipationLabeledCheckbox, -} from "./ParticipationLabeledCheckbox" - -type Props = { - items: TParticipationLabeledCheckbox[] - className?: string -} - -export const ParticipationLabeledCheckboxGroup: React.FC = ({ items, className }) => { - return ( -
- {items.map((item, index) => { - return - })} -
- ) -} diff --git a/src/participation/components/form/ParticipationLabeledRadiobutton.tsx b/src/participation/components/form/ParticipationLabeledRadiobutton.tsx deleted file mode 100644 index b06677204..000000000 --- a/src/participation/components/form/ParticipationLabeledRadiobutton.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { ErrorMessage } from "@hookform/error-message" -import clsx from "clsx" -import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react" -import { useFormContext } from "react-hook-form" - -export interface ParticipationLabeledRadiobuttonProps - extends PropsWithoutRef { - /** Radiobutton scope. */ - scope: string - /** Field name. */ - name: string - /** Field label. */ - label: string - /** Field value. */ - value: string - /** Help text below field label. */ - help?: string - outerProps?: PropsWithoutRef - labelProps?: ComponentPropsWithoutRef<"label"> -} - -export const ParticipationLabeledRadiobutton = forwardRef< - HTMLInputElement, - ParticipationLabeledRadiobuttonProps ->(({ scope, name, label, value, help, outerProps, labelProps, ...props }, ref) => { - const { - register, - formState: { isSubmitting, errors }, - } = useFormContext() - - const hasError = Boolean(errors[name]) - - return ( -
-
- -
- -
- ) -}) diff --git a/src/participation/components/form/ParticipationLabeledRadiobuttonGroup.tsx b/src/participation/components/form/ParticipationLabeledRadiobuttonGroup.tsx deleted file mode 100644 index b5074f0ff..000000000 --- a/src/participation/components/form/ParticipationLabeledRadiobuttonGroup.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import clsx from "clsx" -import React from "react" -import { - ParticipationLabeledRadiobutton, - ParticipationLabeledRadiobuttonProps, -} from "./ParticipationLabeledRadiobutton" - -type Props = { - items: ParticipationLabeledRadiobuttonProps[] - className?: string -} - -export const ParticipationLabeledRadiobuttonGroup: React.FC = ({ items, className }) => { - return ( -
- {items.map((item) => { - return - })} -
- ) -} diff --git a/src/participation/components/layout/HeaderParticipation.tsx b/src/participation/components/layout/HeaderParticipation.tsx deleted file mode 100644 index 4070ea1ea..000000000 --- a/src/participation/components/layout/HeaderParticipation.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { XMarkIcon } from "@heroicons/react/20/solid" -import Image from "next/image" -import { ProgressBar } from "src/participation/components/layout/ProgressBar" -import { ParticipationLink } from "../core/links/ParticipationLink" - -type Props = { - logoSrc: string -} - -export const HeaderParticipation: React.FC = ({ logoSrc }) => { - return ( - - ) -} diff --git a/src/participation/components/layout/LayoutParticipation.tsx b/src/participation/components/layout/LayoutParticipation.tsx deleted file mode 100644 index 8d89b7346..000000000 --- a/src/participation/components/layout/LayoutParticipation.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { BlitzLayout } from "@blitzjs/next" -import Head from "next/head" -import { MetaTags } from "src/core/layouts" -import { TailwindResponsiveHelper } from "src/core/layouts/TailwindResponsiveHelper/TailwindResponsiveHelper" -import { ContainerParticipation } from "./ContainerParticipation" -import { FooterParticipation } from "./FooterParticipation" -import { HeaderParticipation } from "./HeaderParticipation" - -type Props = { - logoUrl: string - children?: React.ReactNode - canonicalUrl?: string -} - -export const LayoutParticipation: BlitzLayout = ({ logoUrl, children, canonicalUrl }) => { - const extension = new URL(logoUrl).pathname.split(".").at(-1) - const mimetype = - { ico: "image/x-icon", svg: "image/svg+xml", jpg: "image/jpeg", png: "image/png" }[ - extension! - ] || `image/${extension}` - - return ( - <> - - - {canonicalUrl && } - - - -
- -
- {children} -
-
- - - - - ) -} diff --git a/src/participation/components/maps/ParticipationStaticMap.tsx b/src/participation/components/maps/ParticipationStaticMap.tsx deleted file mode 100644 index adfcb145d..000000000 --- a/src/participation/components/maps/ParticipationStaticMap.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { MultiLineString } from "@turf/helpers" -import clsx from "clsx" -import "maplibre-gl/dist/maplibre-gl.css" -import React, { useEffect } from "react" -import Map, { Layer, Marker, Source, useMap } from "react-map-gl/maplibre" -import StaticPin from "./StaticPin" - -type Props = { - className?: string - children?: React.ReactNode - marker: { lng: number; lat: number } - projectGeometry: MultiLineString - layerStyles: Record -} - -export const ParticipationStaticMap: React.FC = ({ - marker, - className, - children, - projectGeometry, - layerStyles, -}) => { - const { mainMap } = useMap() - - const maptilerApiKey = "ECOoUBmpqklzSCASXxcu" - const vectorStyle = `https://api.maptiler.com/maps/a4824657-3edd-4fbd-925e-1af40ab06e9c/style.json?key=${maptilerApiKey}` - - useEffect(() => { - if (!mainMap) return - }, [mainMap]) - - return ( -
- - {children} - - - - {layerStyles.map((layer: any) => { - return - })} - - - -
- ) -} diff --git a/src/participation/components/rs8-inactive.tsx b/src/participation/components/rs8-inactive.tsx deleted file mode 100644 index 549413308..000000000 --- a/src/participation/components/rs8-inactive.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { BlitzPage } from "@blitzjs/next" -import { LayoutParticipation } from "src/participation/components/layout/LayoutParticipation" -import surveyDefinition from "src/participation/data/survey.json" -import { ScreenHeaderParticipation } from "./layout/ScreenHeaderParticipation" -import { ParticipationLink } from "./core/links/ParticipationLink" - -const ParticipationInactivePage: BlitzPage = () => { - return ( - -
- - - Zur Projektwebseite - -
-
- ) -} - -export default ParticipationInactivePage diff --git a/src/participation/data/README.md b/src/participation/data/README.md deleted file mode 100644 index 928bad13d..000000000 --- a/src/participation/data/README.md +++ /dev/null @@ -1,23 +0,0 @@ -## How to generate type definitions from the json schema - -1. Install [jq](https://stedolan.github.io/jq/) - - Linux: - - curl https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -o ~/bin/jq - chmod u+x ~/bin/jq - - - OS X with Homebrew: - - brew install jq - -2. Install [json2ts](https://github.com/bcherny/json-schema-to-typescript) - - npm install -global json-schema-to-typescript - -3. Generate type definitions - - DIR=src/participation/data - cat $DIR/schema.json | - jq 'delpaths([["properties", "pages", "items", "properties", "questions", "items", "allOf"]])' | - json2ts > $DIR/types.ts - npx prettier $DIR/types.ts --write diff --git a/src/participation/data/email.json b/src/participation/data/email.json deleted file mode 100644 index 17189ae06..000000000 --- a/src/participation/data/email.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "id": 1, - "title": { "de": "Email" }, - "createdAt": "2023-03-22T14:46:45.712Z", - "version": 1, - "pages": [ - { - "id": 1, - "title": { "de": "Vielen Dank für Ihre Teilnahme" }, - "description": { "de": "" }, - "questions": [ - { - "id": 1, - "label": { "de": "Was passiert jetzt?" }, - "component": "custom", - "props": { - "text": { - "de": "Nach Abschluss der Beteiligung (20.08.2023) werden Ihre Anregungen vom Planungsteam ausgewertet und geprüft, ob und inwieweit sie in die weitere Entwurfsplanung einfließen können. Wir bitten im Vorhinein um Verständnis, dass wir nicht jeden Hinweis kommentieren können. Nach der Auswertung werden wir gebündelt Rückmeldung zu den angesprochenen Themen geben." - } - } - }, - { - "id": 2, - "label": { - "de": "Möchten Sie weiter informiert werden?" - }, - "component": "custom", - "props": { - "text": { - "de": "Wenn Sie Ihre E-Mail-Adresse angeben, informieren wir Sie über die Veröffentlichung der Ergebnisse und weitere Fortschritte im Projekt RS 8." - }, - "emailPlaceholder": { "de": "you@example.com" }, - "agreementText": { - "de": "Ich stimme den Datenschutzbedingungen zu." - } - } - } - ], - "buttons": [ - { - "label": { "de": "Ja, halten Sie mich auf dem Laufenden" }, - "color": "pink", - "onClick": { "action": "nextPage" } - }, - { - "label": { "de": "Nein, zurück zur Startseite" }, - "color": "white", - "onClick": { "action": "previousPage" } - } - ] - } - ] -} diff --git a/src/participation/data/feedback.json b/src/participation/data/feedback.json deleted file mode 100644 index fedc3aa6f..000000000 --- a/src/participation/data/feedback.json +++ /dev/null @@ -1,884 +0,0 @@ -{ - "id": 2, - "title": { "de": "Feedback" }, - "createdAt": "2023-03-22T12:19:59.648Z", - "version": 1, - "pages": [ - { - "id": 1, - "title": { "de": "Wir sind gespannt auf Ihre Anmerkungen." }, - "description": { - "de": "Hier können Sie dem Planungsteam konkrete Ideen, Anregungen und Hinweise zum RS 8 mit auf den Weg geben. Sie können mehrere Anmerkungen abgeben, bitte geben Sie diese einzeln ab." - }, - "questions": [ - { - "id": 21, - "label": { "de": "Zu welchem Thema passt Ihr Feedback?" }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Nutzung" } }, - { "id": 2, "text": { "de": "Streckenführung" } }, - { "id": 3, "text": { "de": "Zubringer" } }, - { "id": 4, "text": { "de": "Mögliche Konflikte" } }, - { "id": 5, "text": { "de": "Umwelt- und Naturschutz" } }, - { "id": 6, "text": { "de": "Sonstiges" } } - ] - } - }, - { - "id": 22, - "label": { - "de": "Bezieht sich Ihr Feedback auf eine konkrete Stelle entlang der Route?" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Ja" } }, - { "id": 2, "text": { "de": "Nein" } } - ] - } - }, - { - "id": 23, - "label": { "de": "Markieren Sie die Stelle, zu der Sie etwas sagen möchten." }, - "component": "map", - "props": { - "marker": { - "lat": 48.87405710508672, - "lng": 9.271044583540359 - }, - "layerStyles": [ - { - "id": "RS8--allsections-luecke-copy", - "type": "line", - - "layout": { "visibility": "visible" }, - "paint": { - "line-color": "#2C62A9", - "line-width": 3, - "line-dasharray": [2, 2] - }, - "filter": ["all", ["==", "planungsabschnitt", "2A"]] - }, - { - "id": "RS8--allsections", - "type": "line", - - "layout": { "visibility": "visible" }, - "paint": { "line-color": "#2C62A9", "line-width": 3 }, - "filter": ["all", ["!=", "planungsabschnitt", "2A"]] - }, - { - "id": "RS8--section4", - "type": "line", - - "layout": { "visibility": "none" }, - "paint": { - "line-color": "#2C62A9", - "line-width": 7, - "line-opacity": 1 - }, - "filter": ["all", ["==", "teilstrecke", 4]] - }, - { - "id": "RS8--section3", - "type": "line", - - "layout": { "visibility": "none" }, - "paint": { "line-color": "#2C62A9", "line-width": 5 }, - "filter": ["all", ["==", "teilstrecke", 3]] - }, - { - "id": "RS8--section1", - "type": "line", - - "layout": { "visibility": "none" }, - "paint": { "line-color": "#2C62A9", "line-width": 5 }, - "filter": ["any", ["==", "teilstrecke", 1], ["==", "planungsabschnitt", "2B"]] - }, - { - "id": "RS8--section1-luecke", - "type": "line", - - "layout": { "visibility": "none" }, - "paint": { - "line-color": "#2c62a9", - "line-width": 7, - "line-dasharray": [2, 2] - }, - "filter": ["all", ["==", "planungsabschnitt", "2A"]] - }, - { - "id": "RS8--section2", - "type": "line", - - "layout": { "visibility": "none" }, - "paint": { "line-color": "#2c62a9", "line-width": 5 }, - "filter": ["all", ["==", "teilstrecke", 2], ["!=", "planungsabschnitt", "2A"]] - } - ], - "projectGeometry": { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.194367, 48.893241], - [9.204387, 48.893397], - [9.20694, 48.892932], - [9.207345, 48.892804], - [9.207572, 48.892527], - [9.209643, 48.892222], - [9.209518, 48.891871], - [9.211302, 48.891544], - [9.213306, 48.891336], - [9.214959, 48.891126], - [9.215124, 48.891105], - [9.215157, 48.891101], - [9.215175, 48.891098] - ] - ] - }, - "id": "b52d5fa5-56ee-40de-b854-575f66edcd93", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Ludwigsburg)", - "variante": "Trasse 2", - "teilstrecke": 1, - "baulasttraeger": "Stadt Ludwigsburg", - "planungsabschnitt": "1B" - } - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [9.240035, 48.887679], - [9.240504, 48.887561], - [9.243368, 48.886582], - [9.243368, 48.886582], - [9.243407, 48.88657], - [9.243447, 48.886559], - [9.243488, 48.88655], - [9.243529, 48.886543], - [9.243572, 48.886538], - [9.243615, 48.886534], - [9.243658, 48.886532], - [9.244556, 48.886511], - [9.244556, 48.886511], - [9.244647, 48.886508], - [9.244737, 48.886502], - [9.245701, 48.886422], - [9.245701, 48.886422], - [9.245719, 48.886421], - [9.245737, 48.886418], - [9.245754, 48.886414], - [9.245771, 48.88641], - [9.245788, 48.886405], - [9.245804, 48.8864], - [9.24582, 48.886393], - [9.245834, 48.886386], - [9.245848, 48.886378], - [9.245862, 48.88637], - [9.245874, 48.886361], - [9.245885, 48.886352], - [9.245896, 48.886342], - [9.245905, 48.886332], - [9.245914, 48.886321], - [9.245921, 48.88631], - [9.245927, 48.886299], - [9.245932, 48.886287], - [9.245936, 48.886275], - [9.245938, 48.886263], - [9.24594, 48.886251], - [9.24594, 48.886239], - [9.245939, 48.886227], - [9.245914, 48.886049], - [9.245914, 48.886049], - [9.245913, 48.886037], - [9.245913, 48.886024], - [9.245915, 48.886012], - [9.245917, 48.885999], - [9.245922, 48.885987], - [9.245927, 48.885975], - [9.245934, 48.885963], - [9.245942, 48.885952], - [9.245951, 48.885941], - [9.245961, 48.88593], - [9.245972, 48.88592], - [9.245984, 48.885911], - [9.245998, 48.885902], - [9.246012, 48.885894], - [9.246027, 48.885886], - [9.246043, 48.885879], - [9.246059, 48.885873], - [9.246076, 48.885868], - [9.246613, 48.885713], - [9.246613, 48.885713], - [9.246649, 48.885702], - [9.246684, 48.88569], - [9.246718, 48.885676], - [9.24675, 48.885661], - [9.246781, 48.885644], - [9.246811, 48.885627], - [9.246811, 48.885627], - [9.24684, 48.885609], - [9.246872, 48.885593], - [9.246904, 48.885578], - [9.246938, 48.885564], - [9.246941, 48.885563] - ] - }, - "id": "99b232e7-3c50-44ba-90ac-0f755c5b1836", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Remseck am Neckar)", - "variante": "Trasse 2", - "teilstrecke": 2, - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2C" - } - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [9.305, 48.838441], - [9.304997, 48.838436], - [9.304601, 48.838093], - [9.304812, 48.837885], - [9.304872, 48.83786], - [9.304872, 48.83786], - [9.304889, 48.837856], - [9.304906, 48.837851], - [9.304922, 48.837846], - [9.304937, 48.837839], - [9.304952, 48.837833], - [9.304967, 48.837825], - [9.30498, 48.837817], - [9.304993, 48.837809], - [9.305005, 48.837799], - [9.305015, 48.83779], - [9.305025, 48.83778], - [9.305034, 48.837769], - [9.305042, 48.837759], - [9.305048, 48.837747], - [9.305054, 48.837736], - [9.305058, 48.837724], - [9.305307, 48.837136], - [9.304794, 48.835689], - [9.304766, 48.835575] - ] - }, - "id": "695f87f5-80b6-498f-8de1-f9c86c62533f", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:Fellbach,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 3, - "baulasttraeger": "Rems-Murr-Kreis", - "planungsabschnitt": "3E" - } - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [9.267748, 48.878676], - [9.267825, 48.878569], - [9.268451, 48.877303], - [9.268517, 48.877032], - [9.268549, 48.876969], - [9.268691, 48.876831], - [9.268874, 48.876524], - [9.26915, 48.876158], - [9.269494, 48.875819], - [9.270131, 48.875271], - [9.270266, 48.875088], - [9.270296, 48.875005], - [9.270308, 48.874878], - [9.270241, 48.874672], - [9.270179, 48.87461], - [9.270085, 48.87457], - [9.269849, 48.87457], - [9.26978, 48.874544], - [9.269759, 48.874484], - [9.269839, 48.87434], - [9.270497, 48.874157], - [9.270955, 48.874057], - [9.271077, 48.874053], - [9.271311, 48.873734], - [9.271428, 48.873611], - [9.271868, 48.873704] - ] - }, - "id": "3cfc8c2d-e6ee-43f2-ac1d-e22e8833c9b0", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Remseck am Neckar)", - "variante": "Trasse 2", - "teilstrecke": 2, - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2E" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.29207, 48.858252], - [9.292078, 48.858219], - [9.291812, 48.858175], - [9.291812, 48.858175], - [9.292473, 48.856699], - [9.292982, 48.856188], - [9.293174, 48.855993], - [9.293184, 48.855969], - [9.293181, 48.855934], - [9.293181, 48.855934], - [9.293169, 48.855902], - [9.293154, 48.855871], - [9.293135, 48.855841], - [9.293114, 48.855811], - [9.29309, 48.855783], - [9.293064, 48.855755], - [9.293034, 48.855729], - [9.293003, 48.855704], - [9.292969, 48.85568], - [9.292871, 48.855605], - [9.293147, 48.855463], - [9.29311, 48.855429], - [9.29311, 48.855429], - [9.292896, 48.855241], - [9.292699, 48.855044], - [9.29252, 48.85484], - [9.29236, 48.85463], - [9.292219, 48.854413], - [9.291977, 48.854032], - [9.292263, 48.853953], - [9.291678, 48.853146], - [9.291121, 48.85261] - ] - ] - }, - "id": "075e7bea-cf2e-4f36-aa30-8ea2a4ea1949", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:Fellbach,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 3, - "baulasttraeger": "Rems-Murr-Kreis", - "planungsabschnitt": "3B" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.215175, 48.891098], - [9.237414, 48.889564], - [9.237415, 48.889563] - ] - ] - }, - "id": "89b36de1-a801-467c-adfb-221a444ffe1c", - "properties": { - "stroke": "#E8B500", - "gemeinde": "(1:Ludwigsburg)", - "variante": "Trasse 2", - "teilstrecke": "1", - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2A" - } - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [9.291121, 48.85261], - [9.293172, 48.851867], - [9.294817, 48.851281], - [9.298633, 48.850979], - [9.298644, 48.850966] - ] - }, - "id": "9e8a0353-84db-475c-a7ad-06c5d0f72ddd", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:Fellbach,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 3, - "baulasttraeger": "Rems-Murr-Kreis", - "planungsabschnitt": "3C" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.274665, 48.869429], - [9.274703, 48.86932], - [9.274746, 48.869152], - [9.274791, 48.868943], - [9.274809, 48.868783], - [9.274816, 48.868538], - [9.27481, 48.868336], - [9.274812, 48.868237], - [9.274825, 48.86811], - [9.274838, 48.868006], - [9.274863, 48.867903], - [9.274888, 48.867825], - [9.274923, 48.867734], - [9.274959, 48.867654], - [9.275009, 48.867559], - [9.275061, 48.86747], - [9.275154, 48.867324], - [9.275197, 48.867258], - [9.275258, 48.867174], - [9.275335, 48.867075], - [9.275409, 48.866986], - [9.275484, 48.866897], - [9.275548, 48.866831], - [9.275638, 48.86674], - [9.275746, 48.866631], - [9.275908, 48.866484], - [9.27604, 48.866377], - [9.276142, 48.866304], - [9.276242, 48.866237], - [9.276363, 48.866166], - [9.276498, 48.866093], - [9.276662, 48.866017], - [9.276886, 48.865926], - [9.277058, 48.865868], - [9.277234, 48.865816], - [9.277393, 48.865779], - [9.277628, 48.865725], - [9.277837, 48.86568], - [9.278076, 48.865629], - [9.278266, 48.865585], - [9.278439, 48.865546], - [9.278489, 48.865533], - [9.279574, 48.865193], - [9.280329, 48.864946], - [9.280836, 48.86482], - [9.281998, 48.864604], - [9.281998, 48.864604], - [9.282559, 48.864482], - [9.283109, 48.864338], - [9.283645, 48.864173], - [9.284165, 48.863988], - [9.284668, 48.863783], - [9.285767, 48.863323], - [9.285943, 48.863261] - ] - ] - }, - "id": "cc9ad2e2-d826-4e6a-a0ac-e4470efa0946", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Remseck am Neckar)", - "variante": "Trasse 2", - "teilstrecke": 2, - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2G" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.285943, 48.863261], - [9.286104, 48.863205], - [9.288148, 48.862564], - [9.288722, 48.862285], - [9.28939, 48.861891], - [9.28939, 48.861891], - [9.28968, 48.861722], - [9.289953, 48.861542], - [9.29021, 48.861352], - [9.290448, 48.861151], - [9.290666, 48.860941], - [9.290865, 48.860722], - [9.290865, 48.860722], - [9.291225, 48.860269], - [9.291541, 48.859801], - [9.291541, 48.859801], - [9.29174, 48.859372], - [9.291896, 48.858934], - [9.29207, 48.858252] - ] - ] - }, - "id": "8a1fdd42-4c5a-4499-a5ce-974913f69b60", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:Fellbach,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 3, - "baulasttraeger": "Rems-Murr-Kreis", - "planungsabschnitt": "3A" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.304766, 48.835575], - [9.304411, 48.834146], - [9.304069, 48.833158], - [9.303748, 48.832294], - [9.303352, 48.831367], - [9.302921, 48.830521], - [9.302641, 48.829937], - [9.302641, 48.829937], - [9.302638, 48.82992], - [9.302637, 48.829903], - [9.302637, 48.829886], - [9.30264, 48.829868], - [9.302644, 48.829851], - [9.30265, 48.829834], - [9.302657, 48.829818], - [9.302666, 48.829802], - [9.302677, 48.829786], - [9.30269, 48.829771], - [9.302704, 48.829756], - [9.30272, 48.829742], - [9.302737, 48.829729], - [9.302755, 48.829717], - [9.302774, 48.829705], - [9.302795, 48.829694], - [9.302817, 48.829685], - [9.302877, 48.829676] - ] - ] - }, - "id": "54c7093b-189d-4f11-b85d-3fc46575040c", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 4, - "baulasttraeger": "Stadt Waiblingen", - "planungsabschnitt": "4A" - } - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [9.302877, 48.829676], - [9.303212, 48.829625], - [9.303391, 48.829587], - [9.303589, 48.829502], - [9.303551, 48.829457], - [9.303368, 48.829264], - [9.303407, 48.828815], - [9.303313, 48.828644], - [9.303157, 48.82844], - [9.30268, 48.827772], - [9.302113, 48.827112], - [9.302117, 48.82691], - [9.301908, 48.826901], - [9.301908, 48.826901], - [9.301818, 48.826891], - [9.30173, 48.826878], - [9.301643, 48.826861], - [9.301558, 48.826841], - [9.301474, 48.826817] - ] - }, - "id": "82bf2e20-18d2-49eb-a5c9-efa633d8ea32", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 4, - "baulasttraeger": "Stadt Waiblingen", - "planungsabschnitt": "4B" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.298644, 48.850966], - [9.29891, 48.85067], - [9.299765, 48.849653], - [9.299765, 48.849653], - [9.301855, 48.846488], - [9.302465, 48.84551], - [9.302635, 48.844779], - [9.302836, 48.844115], - [9.302836, 48.844115], - [9.302973, 48.843063], - [9.302928, 48.842601], - [9.302928, 48.842601], - [9.302921, 48.842538], - [9.302921, 48.842474], - [9.302927, 48.842411], - [9.302939, 48.842348], - [9.302957, 48.842286], - [9.302982, 48.842225], - [9.303012, 48.842165], - [9.303049, 48.842107], - [9.303091, 48.84205], - [9.303139, 48.841995], - [9.303192, 48.841942], - [9.303876, 48.841324], - [9.304183, 48.841047], - [9.304614, 48.840707], - [9.304879, 48.84046], - [9.304879, 48.84046], - [9.30494, 48.840398], - [9.304994, 48.840333], - [9.305041, 48.840265], - [9.305081, 48.840196], - [9.305114, 48.840125], - [9.305139, 48.840053], - [9.305157, 48.83998], - [9.305167, 48.839906], - [9.30517, 48.839831], - [9.305165, 48.839757], - [9.305141, 48.83916], - [9.305064, 48.83856], - [9.305, 48.838441] - ] - ] - }, - "id": "af5c5de7-20aa-4fbd-9bd9-39aeba3b2720", - "properties": { - "stroke": "#C93535", - "gemeinde": "(2:Fellbach,Waiblingen)", - "variante": "Trasse 2", - "teilstrecke": 3, - "baulasttraeger": "Rems-Murr-Kreis", - "planungsabschnitt": "3D" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.271868, 48.873704], - [9.271879, 48.873707], - [9.272178, 48.873722], - [9.272465, 48.873753], - [9.272751, 48.873838], - [9.273848, 48.873071], - [9.274021, 48.872894], - [9.274076, 48.872669], - [9.274175, 48.872363], - [9.274243, 48.872168], - [9.274268, 48.872066], - [9.274362, 48.871616], - [9.274372, 48.871411], - [9.274362, 48.871017], - [9.274364, 48.870515], - [9.274371, 48.870443], - [9.274388, 48.870406], - [9.274412, 48.870101], - [9.274459, 48.869897], - [9.274517, 48.869772], - [9.27462, 48.869535], - [9.27465, 48.869474], - [9.274665, 48.869429] - ] - ] - }, - "id": "696da69b-f626-4837-8aa0-cc247e1c73c6", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Remseck am Neckar)", - "variante": "Trasse 2", - "teilstrecke": 2, - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2F" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.237415, 48.889563], - [9.237619, 48.889472], - [9.237941, 48.889298], - [9.238162, 48.889179], - [9.238432, 48.889015], - [9.238708, 48.88883], - [9.238913, 48.888682], - [9.239225, 48.888451], - [9.239484, 48.888282], - [9.239682, 48.888117], - [9.239889, 48.8879], - [9.240031, 48.88768], - [9.240035, 48.887679] - ] - ] - }, - "id": "6b895f43-e6cb-4aa2-a9c0-288dab27bd4c", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Ludwigsburg)", - "variante": "Trasse 2", - "teilstrecke": "1", - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2B" - } - }, - { - "type": "Feature", - "geometry": { - "type": "MultiLineString", - "coordinates": [ - [ - [9.246941, 48.885563], - [9.250006, 48.884619], - [9.252178, 48.884047], - [9.253776, 48.883445], - [9.256257, 48.882745], - [9.2574, 48.882486], - [9.25941, 48.882146], - [9.261844, 48.881599], - [9.261892, 48.881523], - [9.261685, 48.881258], - [9.261708, 48.881195], - [9.263049, 48.880817], - [9.263601, 48.880592], - [9.26394, 48.880341], - [9.263889, 48.878882], - [9.264032, 48.87873], - [9.267549, 48.878743], - [9.267748, 48.878676] - ] - ] - }, - "id": "10689c70-20f2-492b-879d-5ff6c80376d0", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Remseck am Neckar)", - "variante": "Trasse 2", - "teilstrecke": 2, - "baulasttraeger": "Kreis Ludwigsburg", - "planungsabschnitt": "2D" - } - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [9.185953, 48.892557], - [9.186506, 48.893118], - [9.194367, 48.893241] - ] - }, - "id": "adc65867-7da3-4015-aab3-af60a859e963", - "properties": { - "stroke": "#C93535", - "gemeinde": "(1:Ludwigsburg)", - "variante": "Trasse 2", - "teilstrecke": 1, - "baulasttraeger": "Stadt Ludwigsburg", - "planungsabschnitt": "1A" - } - } - ] - }, - "config": { - "zoom": 2, - "bounds": [ - 9.387312714501604, 48.90390202531458, 9.103949029818097, 48.81629635563661 - ], - "longitude": 13.5, - "latitude": 52.5, - "boundsPadding": 20 - } - } - } - ], - "buttons": [ - { "label": { "de": "Weiter" }, "color": "pink", "onClick": { "action": "nextPage" } }, - { "label": { "de": "Zurück" }, "color": "white", "onClick": { "action": "previousPage" } } - ] - }, - { - "id": 2, - "title": { "de": "Ihr Hinweis" }, - "description": { - "de": "Formulieren Sie hier Ihre Gedanken, Ideen, Anregungen oder Wünsche" - }, - "questions": [ - { - "id": 31, - "label": { "de": "Kategorie" }, - "component": "custom" - }, - { - "id": 32, - "label": { "de": "Ausgewählte Stelle" }, - "component": "custom" - }, - { - "id": 33, - "label": { "de": "Wählen Sie die Stelle für Ihr Feedback" }, - "component": "custom" - }, - { - "id": 34, - "label": { "de": "Was gefällt Ihnen hier besonders?" }, - "component": "text", - "props": { - "placeholder": { "de": "Beantworten Sie hier..." }, - "caption": { "de": "Max. 2000 Zeichen" } - } - }, - { - "id": 35, - "label": { "de": "Was wünschen Sie sich?" }, - "component": "text", - "props": { - "placeholder": { "de": "Beantworten Sie hier..." }, - "caption": { "de": "Max. 2000 Zeichen" } - } - } - ], - "buttons": [ - { - "label": { "de": "Absenden & Beteiligung abschließen" }, - "color": "pink", - "onClick": { "action": "submit" } - }, - { - "label": { "de": "Absenden & weiteren Hinweis geben" }, - "color": "white", - "onClick": { "action": "submit" } - } - ] - } - ] -} diff --git a/src/participation/data/more.json b/src/participation/data/more.json deleted file mode 100644 index 1c306847d..000000000 --- a/src/participation/data/more.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": 1, - "title": { "de": "Konkrete Hinweise" }, - "createdAt": "2023-03-22T13:22:23.123Z", - "version": 1, - "pages": [ - { - "id": 1, - "title": { "de": "Danke, Ihre Daten wurden gesendet." }, - "description": { - "de": "Wenn Sie möchten, können Sie uns noch weiteres Feedback z. B. zu einem konkreten Thema oder einer bestimmten Stelle zukommen lassen. Drücken Sie dazu bitte auf “Weitere Hinweise geben”. Dort haben Sie auch die Möglichkeit, Hinweise mit Pin auf einer interaktiven Karte zu verorten." - }, - "questions": [ - { - "id": 1, - "label": { "de": "Haben Sie noch konkrete Hinweise zu Themen vor Ort?" } - } - ], - "buttons": [ - { - "label": { "de": "Ja, ich habe noch Hinweise" }, - "color": "pink", - "onClick": { "action": "nextPage" } - }, - { - "label": { "de": "Nein, ich möchte die Umfrage beenden" }, - "color": "white", - "onClick": { "action": "submit" } - } - ] - } - ] -} diff --git a/src/participation/data/schema.json b/src/participation/data/schema.json deleted file mode 100644 index b05a77807..000000000 --- a/src/participation/data/schema.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "title": "Survey", - "version": 1, - "type": "object", - "required": ["id", "version", "title", "pages", "createdAt", "faviconUrl"], - "additionalProperties": false, - "properties": { - "id": { "type": "integer", "minimum": 1 }, - "title": { "$ref": "#/$defs/translatableText" }, - "version": { "type": "integer", "enum": [1] }, - "createdAt": { "type": "string", "format": "date" }, - "faviconUrl": { "type": "string" }, - "pages": { - "type": "array", - "items": { - "type": "object", - "required": ["id", "title", "description", "buttons"], - "additionalProperties": false, - "properties": { - "id": { "type": "integer", "minimum": 1 }, - "title": { "$ref": "#/$defs/translatableText" }, - "description": { "$ref": "#/$defs/translatableText" }, - "questions": { - "type": "array", - "items": { - "type": "object", - "required": ["id", "component", "label", "props"], - "additionalProperties": false, - "properties": { - "id": { "type": "integer", "minimum": 1 }, - "component": { - "type": "string", - "enum": ["singleResponse", "multipleResponse", "text"] - }, - "label": { "$ref": "#/$defs/translatableText" }, - "//* props.oneOf is here for generating typescript": "*//", - "props": { - "oneOf": [ - { "$ref": "#/$defs/SingleOrMultiResponseProps" }, - { "$ref": "#/$defs/TextResponseProps" } - ] - } - }, - "allOf": [ - { - "if": { - "properties": { - "component": { "enum": ["singleResponse", "multipleResponse"] } - } - }, - "then": { - "properties": { "props": { "$ref": "#/$defs/SingleOrMultiResponseProps" } } - } - } - ] - } - }, - "buttons": { - "type": "array", - "items": { - "type": "object", - "required": ["label", "color", "onClick"], - "additionalProperties": false, - "properties": { - "label": { "$ref": "#/$defs/translatableText" }, - "color": { "type": "string", "enum": ["white", "pink"] }, - "onClick": { - "type": "object", - "required": ["action"], - "additionalItems": false, - "properties": { - "action": { - "enum": ["nextPage", "previousPage", "submit"] - }, - "arguments": { "type": "array" } - }, - "additionalProperties": false - } - } - } - } - } - } - } - }, - "$defs": { - "translatableText": { - "type": "object", - "required": ["de"], - "additionalProperties": false, - "properties": { - "de": { "type": "string", "minLength": 2 } - } - }, - "Response": { - "type": "object", - "required": ["id", "text"], - "additionalProperties": false, - "properties": { - "id": { "type": "integer", "minimum": 1 }, - "text": { "$ref": "#/$defs/translatableText" } - } - }, - "SingleOrMultiResponseProps": { - "type": "object", - "required": ["responses"], - "additionalProperties": false, - "properties": { - "responses": { - "type": "array", - "minLength": 2, - "items": { "$ref": "#/$defs/Response" } - } - } - }, - "TextResponseProps": { - "type": "object", - "additionalProperties": false - } - } -} diff --git a/src/participation/data/survey.json b/src/participation/data/survey.json deleted file mode 100644 index 19617573f..000000000 --- a/src/participation/data/survey.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "id": 1, - "title": { "de": "Beteiligung" }, - "createdAt": "2023-03-15T20:19:43.815Z", - "version": 1, - "logoUrl": "https://radschnellweg8-lb-wn.de/logo.png", - "canonicalUrl": "https://radschnellweg8-lb-wn.de/beteiligung/", - "pages": [ - { - "id": 1, - "title": { "de": "Ihre Meinung ist gefragt" }, - "description": { - "de": "Auf dem Weg zur Schule, Sportstätte und Arbeitsplatz, beim Wocheneinkauf oder dem Familienausflug – unser Ziel ist, dass der Radschnellweg von vielen Menschen angenommen wird.\n\nDeshalb interessieren uns die Ideen, Anmerkungen und Hinweise von Alltagsexpertinnen und -experten. Sie kennen sich vor Ort aus. Unterstützen Sie das Planungsteam dabei, den RS 8 zum Erfolgsprojekt zu machen!\n\nDie Bürgerbeteiligung läuft noch bis zum 20.08.2023. Die Beantwortung dauert ca. 5-10 Minuten." - }, - "buttons": [ - { - "label": { "de": "Beteiligung starten" }, - "color": "pink", - "onClick": { "action": "nextPage" } - } - ] - }, - { - "id": 2, - "title": { "de": "Nutzung" }, - "description": { - "de": "Zuerst möchten wir Ihnen einige Fragen zur Nutzung des RS 8 Ludwigsburg–Waiblingen stellen." - }, - "questions": [ - { - "id": 1, - "label": { "de": "Würden Sie den RS 8 nutzen?" }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Ja" } }, - { - "id": 2, - "text": { "de": "Nein" }, - "help": { - "de": "Warum nicht? Wir freuen uns, wenn Sie das Freifeldtext im späteren Teil „Weiteres Feedback“ nutzen, um uns Ihre Gründe zu nennen. Besten Dank!" - } - }, - { - "id": 3, - "text": { - "de": "Ich bin ohnehin nicht zwischen Ludwigsburg und Waiblingen unterwegs." - } - } - ] - } - }, - { - "id": 2, - "label": { "de": "Wie häufig würden Sie den RS 8 nutzen?" }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Täglich" } }, - { "id": 2, "text": { "de": "Mehrmals pro Woche" } }, - { "id": 3, "text": { "de": "Mehrmals im Monat" } }, - { "id": 4, "text": { "de": "Seltener oder Nie" } } - ] - } - }, - { - "id": 3, - "label": { "de": "Für welche Zwecke würden Sie den RS 8 nutzen?" }, - "component": "multipleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Einkaufen" } }, - { "id": 2, "text": { "de": "Zur Arbeit/Schule pendeln" } }, - { "id": 3, "text": { "de": "Sport/Freizeit" } }, - { "id": 4, "text": { "de": "Anderes" } } - ] - } - }, - { - "id": 4, - "label": { "de": "Würden Sie durch den RS 8 häufiger aufs Auto verzichten?" }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Ja" } }, - { "id": 2, "text": { "de": "Nein" } }, - { "id": 3, "text": { "de": "Ich verzichte bereits aufs Auto." } }, - { "id": 4, "text": { "de": "Weiß nicht / Keine Angabe" } } - ] - } - }, - { - "id": 5, - "label": { - "de": "Glauben Sie, dass andere durch den RS 8 häufiger aufs Auto verzichten würden?" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Ja" } }, - { "id": 2, "text": { "de": "Nein" } }, - { "id": 3, "text": { "de": "Weiß nicht / Keine Angabe" } } - ] - } - } - ], - "buttons": [ - { "label": { "de": "Weiter" }, "color": "pink", "onClick": { "action": "nextPage" } }, - { "label": { "de": "Zurück" }, "color": "white", "onClick": { "action": "previousPage" } } - ] - }, - { - "id": 3, - "title": { "de": "Ausstattung" }, - "description": { - "de": "Wie wichtig sind Ihnen folgende Ausstattungsmerkmale bei Radschnellwegen?" - }, - "questions": [ - { - "id": 6, - "label": { "de": "Wie wichtig ist Ihnen die Beleuchtung des RS 8?" }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Eher wichtig" } }, - { "id": 2, "text": { "de": "Weniger wichtig" } }, - { "id": 3, "text": { "de": "Weiß nicht" } } - ] - } - }, - { - "id": 7, - "label": { "de": "Wie wichtig sind Ihnen Rastmöglichkeiten entlang der Strecke?" }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Eher wichtig" } }, - { "id": 2, "text": { "de": "Weniger wichtig" } }, - { "id": 3, "text": { "de": "Weiß nicht" } } - ] - } - }, - { - "id": 8, - "label": { - "de": "Wie wichtig sind Ihnen Reparatursäulen (Luftpumpe, Werkzeug) entlang der Strecke?" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "Eher wichtig" } }, - { "id": 2, "text": { "de": "Weniger wichtig" } }, - { "id": 3, "text": { "de": "Weiß nicht" } } - ] - } - } - ], - "buttons": [ - { "label": { "de": "Weiter" }, "color": "pink", "onClick": { "action": "nextPage" } }, - { "label": { "de": "Zurück" }, "color": "white", "onClick": { "action": "previousPage" } } - ] - }, - { - "id": 4, - "title": { "de": "Gemeinsame Wegeführung" }, - "description": { - "de": "Leider wird es nicht überall möglich sein, Wege zu bauen, die ausschließlich dem Radverkehr vorbehalten sind. Wir möchten gerne von Ihnen wissen, welche Folgen das für Ihre Nutzung des RS 8 hätte." - }, - "questions": [ - { - "id": 9, - "label": { - "de": "Einen Weg, auf dem auch Fußverkehr zugelassen ist, würde ich …" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "ohne Einschränkung mit dem Rad nutzen." } }, - { "id": 2, "text": { "de": "eher selten mit dem Rad nutzen." } }, - { "id": 3, "text": { "de": "nie mit dem Rad nutzen." } }, - { "id": 4, "text": { "de": "Weiß ich nicht." } } - ] - } - }, - { - "id": 10, - "label": { - "de": "Eine Fahrradstraße, die auch von Kfz befahren werden darf, würde ich …" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "ohne Einschränkung mit dem Rad nutzen." } }, - { "id": 2, "text": { "de": "eher selten mit dem Rad nutzen." } }, - { "id": 3, "text": { "de": "nie mit dem Rad nutzen." } }, - { "id": 4, "text": { "de": "Weiß ich nicht." } } - ] - } - }, - { - "id": 11, - "label": { - "de": "Einen Radweg, der auch von landwirtschaftlichen Fahrzeugen befahren werden darf, würde ich …" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "ohne Einschränkung mit dem Rad nutzen." } }, - { "id": 2, "text": { "de": "eher selten mit dem Rad nutzen." } }, - { "id": 3, "text": { "de": "nie mit dem Rad nutzen." } }, - { "id": 4, "text": { "de": "Weiß ich nicht." } } - ] - } - }, - { - "id": 12, - "label": { - "de": "Einen Radweg, der auch von Bussen befahren werden darf, würde ich …" - }, - "component": "singleResponse", - "props": { - "responses": [ - { "id": 1, "text": { "de": "ohne Einschränkung mit dem Rad nutzen." } }, - { "id": 2, "text": { "de": "eher selten mit dem Rad nutzen." } }, - { "id": 3, "text": { "de": "nie mit dem Rad nutzen." } }, - { "id": 4, "text": { "de": "Weiß ich nicht." } } - ] - } - } - ], - "buttons": [ - { "label": { "de": "Absenden" }, "color": "pink", "onClick": { "action": "submit" } }, - { "label": { "de": "Zurück" }, "color": "white", "onClick": { "action": "previousPage" } } - ] - } - ] -} diff --git a/src/participation/data/types.ts b/src/participation/data/types.ts deleted file mode 100644 index edb0d327c..000000000 --- a/src/participation/data/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable */ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -export interface Survey { - id: number - title: TranslatableText - version: 1 - createdAt: string - pages: Page[] -} - -export interface Page { - id: number - title: TranslatableText - description: TranslatableText - questions?: Question[] - buttons: Button[] -} -export interface Question { - id: number - component: "singleResponse" | "multipleResponse" | "text" - label: TranslatableText - "//* props.oneOf is here for generating typescript"?: "*//" - props: SingleOrMultiResponseProps | TextResponseProps -} - -export interface Button { - label: TranslatableText - color: "white" | "pink" - onClick: { - action: "nextPage" | "previousPage" | "submit" - arguments?: unknown[] - } -} -export interface TranslatableText { - de: string -} -export interface SingleOrMultiResponseProps { - responses: Response[] -} -export interface Response { - id: number - text: TranslatableText - help?: TranslatableText -} -export interface TextResponseProps {} diff --git a/src/subsections/components/SubsectionForm.tsx b/src/subsections/components/SubsectionForm.tsx index ac7a93110..e70ae1701 100644 --- a/src/subsections/components/SubsectionForm.tsx +++ b/src/subsections/components/SubsectionForm.tsx @@ -9,6 +9,7 @@ import { LabeledTextField, LabeledTextareaField, } from "src/core/components/forms" +import { LabeledFormatNumberFieldCalculateLength } from "src/core/components/forms/LabeledFormatNumberFieldCalculateLength" import { LabeledGeometryField } from "src/core/components/forms/LabeledGeometryField" import { Link } from "src/core/components/links" import { quote, shortTitle } from "src/core/components/text" @@ -82,6 +83,13 @@ function SubsectionFormWithQuery>({ name="geometry" label="Geometry der Achse (LineString)" /> + +
diff --git a/src/subsections/components/SubsubsectionMapSidebar.tsx b/src/subsections/components/SubsubsectionMapSidebar.tsx index 8eb504ff6..afe66229e 100644 --- a/src/subsections/components/SubsubsectionMapSidebar.tsx +++ b/src/subsections/components/SubsubsectionMapSidebar.tsx @@ -101,7 +101,7 @@ export const SubsubsectionMapSidebar: React.FC = ({ subsubsection, onClos Länge - {formattedLength(subsubsection.length)} + {formattedLength(subsubsection.lengthKm)} diff --git a/src/subsections/mutations/updateSubsectionsWithFeltData.ts b/src/subsections/mutations/updateSubsectionsWithFeltData.ts index cbe038d58..53a244c31 100644 --- a/src/subsections/mutations/updateSubsectionsWithFeltData.ts +++ b/src/subsections/mutations/updateSubsectionsWithFeltData.ts @@ -3,6 +3,7 @@ import db from "db" import { z } from "zod" import { multilinestringToLinestring } from "../components/utils/multilinestringToLinestring" import { FeltApiResponseSchema, SubsectionSchema } from "../schema" +import { length, lineString } from "@turf/turf" const UpdateSubsectionsWithFeltDataSchema = z.object({ subsections: z.array( @@ -65,6 +66,14 @@ export default resolver.pipe( end: matchingFeltSubsection.properties ? matchingFeltSubsection.properties["ts_pa_end"] : tsSubsection.end, + lengthKm: length( + lineString( + // @ts-expect-error + matchingFeltSubsection.geometry + ? multilinestringToLinestring(matchingFeltSubsection?.geometry["coordinates"]) + : tsSubsection.geometry, + ), + ), }, }) diff --git a/src/subsections/schema.ts b/src/subsections/schema.ts index d9f6c6344..17e72ab08 100644 --- a/src/subsections/schema.ts +++ b/src/subsections/schema.ts @@ -7,6 +7,7 @@ import { SlugSchema, InputNumberOrNullSchema } from "src/core/utils" export const SubsectionSchema = z.object({ slug: SlugSchema, order: z.coerce.number(), + lengthKm: InputNumberOrNullSchema, description: z.string().nullish(), start: z.string().min(1), end: z.string().min(1), diff --git a/src/subsubsectionInfra/components/SubsubsectionInfraForm.tsx b/src/subsubsectionInfra/components/SubsubsectionInfraForm.tsx new file mode 100644 index 000000000..20c321e9a --- /dev/null +++ b/src/subsubsectionInfra/components/SubsubsectionInfraForm.tsx @@ -0,0 +1,14 @@ +import { Form, FormProps, LabeledTextField } from "src/core/components/forms" +import { z } from "zod" +export { FORM_ERROR } from "src/core/components/forms" + +export function SubsubsectionInfraForm>(props: FormProps) { + const { ...formProps } = props + + return ( + {...formProps}> + + + + ) +} diff --git a/src/subsubsectionInfra/mutations/createSubsubsectionInfra.ts b/src/subsubsectionInfra/mutations/createSubsubsectionInfra.ts new file mode 100644 index 000000000..e23025a48 --- /dev/null +++ b/src/subsubsectionInfra/mutations/createSubsubsectionInfra.ts @@ -0,0 +1,25 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getProjectIdBySlug from "../../projects/queries/getProjectIdBySlug" +import { OperatorSchema } from "src/operators/schema" +import { SubsubsectionInfra } from "../schema" + +const CreateSubsubsectionInfraSchema = SubsubsectionInfra.omit({ projectId: true }).merge( + z.object({ + projectSlug: z.string(), + }), +) + +export default resolver.pipe( + resolver.zod(CreateSubsubsectionInfraSchema), + authorizeProjectAdmin(getProjectIdBySlug), + async ({ projectSlug, ...input }) => + await db.subsubsectionInfra.create({ + data: { + ...input, + projectId: await getProjectIdBySlug(projectSlug), + }, + }), +) diff --git a/src/subsubsectionInfra/mutations/deleteSubsubsectionInfra.ts b/src/subsubsectionInfra/mutations/deleteSubsubsectionInfra.ts new file mode 100644 index 000000000..3b4b787dd --- /dev/null +++ b/src/subsubsectionInfra/mutations/deleteSubsubsectionInfra.ts @@ -0,0 +1,15 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getSubsubsectionInfraProjectId from "../queries/getSubsubsectionInfraProjectId" + +const DeleteSubsubsectionInfraSchema = z.object({ + id: z.number(), +}) + +export default resolver.pipe( + resolver.zod(DeleteSubsubsectionInfraSchema), + authorizeProjectAdmin(getSubsubsectionInfraProjectId), + async ({ id }) => await db.subsubsectionInfra.deleteMany({ where: { id } }), +) diff --git a/src/subsubsectionInfra/mutations/updateSubsubsectionInfra.ts b/src/subsubsectionInfra/mutations/updateSubsubsectionInfra.ts new file mode 100644 index 000000000..9e248821a --- /dev/null +++ b/src/subsubsectionInfra/mutations/updateSubsubsectionInfra.ts @@ -0,0 +1,22 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getQualityLevelProjectId from "../queries/getSubsubsectionInfraProjectId" +import { SubsubsectionInfra } from "../schema" + +const UpdateSubsubsectionInfraSchema = SubsubsectionInfra.merge( + z.object({ + id: z.number(), + }), +) + +export default resolver.pipe( + resolver.zod(UpdateSubsubsectionInfraSchema), + authorizeProjectAdmin(getQualityLevelProjectId), + async ({ id, ...data }) => + await db.subsubsectionInfra.update({ + where: { id }, + data, + }), +) diff --git a/src/subsubsectionInfra/queries/getSubsubsectionInfra.ts b/src/subsubsectionInfra/queries/getSubsubsectionInfra.ts new file mode 100644 index 000000000..8053abddf --- /dev/null +++ b/src/subsubsectionInfra/queries/getSubsubsectionInfra.ts @@ -0,0 +1,20 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getQualityLevelProjectId from "./getSubsubsectionInfraProjectId" + +const GetSubsubsectionInfra = z.object({ + // This accepts type of undefined, but is required at runtime + id: z.number().optional().refine(Boolean, "Required"), +}) + +export default resolver.pipe( + resolver.zod(GetSubsubsectionInfra), + authorizeProjectAdmin(getQualityLevelProjectId), + async ({ id }) => { + return await db.subsubsectionInfra.findFirstOrThrow({ + where: { id }, + }) + }, +) diff --git a/src/subsubsectionInfra/queries/getSubsubsectionInfraProjectId.ts b/src/subsubsectionInfra/queries/getSubsubsectionInfraProjectId.ts new file mode 100644 index 000000000..1f2b29cce --- /dev/null +++ b/src/subsubsectionInfra/queries/getSubsubsectionInfraProjectId.ts @@ -0,0 +1,11 @@ +import db from "db" + +const getSubsubsectionInfraProjectId = async (input: Record) => + ( + await db.subsubsectionInfra.findFirstOrThrow({ + where: { id: input.id || null }, + select: { projectId: true }, + }) + ).projectId + +export default getSubsubsectionInfraProjectId diff --git a/src/subsubsectionInfra/queries/getSubsubsectionInfrasWithCount.ts b/src/subsubsectionInfra/queries/getSubsubsectionInfrasWithCount.ts new file mode 100644 index 000000000..a6ed5888e --- /dev/null +++ b/src/subsubsectionInfra/queries/getSubsubsectionInfrasWithCount.ts @@ -0,0 +1,58 @@ +import { resolver } from "@blitzjs/rpc" +import { paginate } from "blitz" +import db, { Prisma } from "db" +import { authorizeProjectAdmin } from "src/authorization" +import getProjectIdBySlug from "src/projects/queries/getProjectIdBySlug" + +type GetSubsubsectionInfraInput = { projectSlug: string } & Pick< + Prisma.SubsubsectionInfraFindManyArgs, + "where" | "orderBy" | "skip" | "take" +> + +export default resolver.pipe( + // @ts-ignore + authorizeProjectAdmin(getProjectIdBySlug), + async ({ + projectSlug, + where, + orderBy = { id: "asc" }, + skip = 0, + take = 100, + }: GetSubsubsectionInfraInput) => { + const safeWhere = { project: { slug: projectSlug }, ...where } + + const { + items: subsubsectionInfras, + hasMore, + nextPage, + count, + } = await paginate({ + skip, + take, + count: () => db.subsubsectionInfra.count({ where: safeWhere }), + query: (paginateArgs) => + db.subsubsectionInfra.findMany({ ...paginateArgs, where: safeWhere, orderBy }), + }) + + const subsubsectionInfraWithCount = await Promise.all( + subsubsectionInfras.map(async (subsubsectionInfra) => { + const subsubsectionCount = await db.subsubsection.count({ + where: { + subsection: { project: { slug: projectSlug } }, + subsubsectionInfraId: subsubsectionInfra.id, + }, + }) + return { + ...subsubsectionInfra, + subsubsectionCount, + } + }), + ) + + return { + subsubsectionInfras: subsubsectionInfraWithCount, + hasMore, + count, + } + }, +) diff --git a/src/subsubsectionInfra/schema.ts b/src/subsubsectionInfra/schema.ts new file mode 100644 index 000000000..a51c89e83 --- /dev/null +++ b/src/subsubsectionInfra/schema.ts @@ -0,0 +1,10 @@ +import { SlugSchema } from "src/core/utils" +import { z } from "zod" + +export const SubsubsectionInfra = z.object({ + slug: SlugSchema, + title: z.string().min(2, { message: "Pflichtfeld. Mindestens 2 Zeichen." }), + projectId: z.coerce.number(), +}) + +type TSubsubsectionInfra = z.infer diff --git a/src/subsubsectionSpecial/components/SubsubsectionSpecialForm.tsx b/src/subsubsectionSpecial/components/SubsubsectionSpecialForm.tsx new file mode 100644 index 000000000..84e5e4ad0 --- /dev/null +++ b/src/subsubsectionSpecial/components/SubsubsectionSpecialForm.tsx @@ -0,0 +1,14 @@ +import { Form, FormProps, LabeledTextField } from "src/core/components/forms" +import { z } from "zod" +export { FORM_ERROR } from "src/core/components/forms" + +export function SubsubsectionSpecialForm>(props: FormProps) { + const { ...formProps } = props + + return ( + {...formProps}> + + + + ) +} diff --git a/src/subsubsectionSpecial/mutations/createSubsubsectionSpecial.ts b/src/subsubsectionSpecial/mutations/createSubsubsectionSpecial.ts new file mode 100644 index 000000000..7c6d2c1cd --- /dev/null +++ b/src/subsubsectionSpecial/mutations/createSubsubsectionSpecial.ts @@ -0,0 +1,25 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getProjectIdBySlug from "../../projects/queries/getProjectIdBySlug" +import { OperatorSchema } from "src/operators/schema" +import { SubsubsectionSpecial } from "../schema" + +const CreateSubsubsectionSpecialSchema = SubsubsectionSpecial.omit({ projectId: true }).merge( + z.object({ + projectSlug: z.string(), + }), +) + +export default resolver.pipe( + resolver.zod(CreateSubsubsectionSpecialSchema), + authorizeProjectAdmin(getProjectIdBySlug), + async ({ projectSlug, ...input }) => + await db.subsubsectionSpecial.create({ + data: { + ...input, + projectId: await getProjectIdBySlug(projectSlug), + }, + }), +) diff --git a/src/subsubsectionSpecial/mutations/deleteSubsubsectionSpecial.ts b/src/subsubsectionSpecial/mutations/deleteSubsubsectionSpecial.ts new file mode 100644 index 000000000..6bc4a1819 --- /dev/null +++ b/src/subsubsectionSpecial/mutations/deleteSubsubsectionSpecial.ts @@ -0,0 +1,15 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getSubsubsectionSpecialProjectId from "../queries/getSubsubsectionSpecialProjectId" + +const DeleteSubsubsectionSpecialSchema = z.object({ + id: z.number(), +}) + +export default resolver.pipe( + resolver.zod(DeleteSubsubsectionSpecialSchema), + authorizeProjectAdmin(getSubsubsectionSpecialProjectId), + async ({ id }) => await db.subsubsectionSpecial.deleteMany({ where: { id } }), +) diff --git a/src/subsubsectionSpecial/mutations/updateSubsubsectionSpecial.ts b/src/subsubsectionSpecial/mutations/updateSubsubsectionSpecial.ts new file mode 100644 index 000000000..6486f4319 --- /dev/null +++ b/src/subsubsectionSpecial/mutations/updateSubsubsectionSpecial.ts @@ -0,0 +1,22 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getQualityLevelProjectId from "../queries/getSubsubsectionSpecialProjectId" +import { SubsubsectionSpecial } from "../schema" + +const UpdateSubsubsectionSpecialSchema = SubsubsectionSpecial.merge( + z.object({ + id: z.number(), + }), +) + +export default resolver.pipe( + resolver.zod(UpdateSubsubsectionSpecialSchema), + authorizeProjectAdmin(getQualityLevelProjectId), + async ({ id, ...data }) => + await db.subsubsectionSpecial.update({ + where: { id }, + data, + }), +) diff --git a/src/subsubsectionSpecial/queries/getSubsubsectionSpecial.ts b/src/subsubsectionSpecial/queries/getSubsubsectionSpecial.ts new file mode 100644 index 000000000..e6a69cab9 --- /dev/null +++ b/src/subsubsectionSpecial/queries/getSubsubsectionSpecial.ts @@ -0,0 +1,20 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getQualityLevelProjectId from "./getSubsubsectionSpecialProjectId" + +const GetSubsubsectionSpecial = z.object({ + // This accepts type of undefined, but is required at runtime + id: z.number().optional().refine(Boolean, "Required"), +}) + +export default resolver.pipe( + resolver.zod(GetSubsubsectionSpecial), + authorizeProjectAdmin(getQualityLevelProjectId), + async ({ id }) => { + return await db.subsubsectionSpecial.findFirstOrThrow({ + where: { id }, + }) + }, +) diff --git a/src/subsubsectionSpecial/queries/getSubsubsectionSpecialProjectId.ts b/src/subsubsectionSpecial/queries/getSubsubsectionSpecialProjectId.ts new file mode 100644 index 000000000..24d8361ba --- /dev/null +++ b/src/subsubsectionSpecial/queries/getSubsubsectionSpecialProjectId.ts @@ -0,0 +1,11 @@ +import db from "db" + +const getSubsubsectionSpecialProjectId = async (input: Record) => + ( + await db.subsubsectionSpecial.findFirstOrThrow({ + where: { id: input.id || null }, + select: { projectId: true }, + }) + ).projectId + +export default getSubsubsectionSpecialProjectId diff --git a/src/subsubsectionSpecial/queries/getSubsubsectionSpecialsWithCount.ts b/src/subsubsectionSpecial/queries/getSubsubsectionSpecialsWithCount.ts new file mode 100644 index 000000000..d6f7848d3 --- /dev/null +++ b/src/subsubsectionSpecial/queries/getSubsubsectionSpecialsWithCount.ts @@ -0,0 +1,62 @@ +import { resolver } from "@blitzjs/rpc" +import { paginate } from "blitz" +import db, { Prisma } from "db" +import { authorizeProjectAdmin } from "src/authorization" +import getProjectIdBySlug from "src/projects/queries/getProjectIdBySlug" + +type GetSubsubsectionSpecialInput = { projectSlug: string } & Pick< + Prisma.SubsubsectionSpecialFindManyArgs, + "where" | "orderBy" | "skip" | "take" +> + +export default resolver.pipe( + // @ts-ignore + authorizeProjectAdmin(getProjectIdBySlug), + async ({ + projectSlug, + where, + orderBy = { id: "asc" }, + skip = 0, + take = 100, + }: GetSubsubsectionSpecialInput) => { + const safeWhere = { project: { slug: projectSlug }, ...where } + + const { + items: subsubsectionSpecials, + hasMore, + nextPage, + count, + } = await paginate({ + skip, + take, + count: () => db.subsubsectionSpecial.count({ where: safeWhere }), + query: (paginateArgs) => + db.subsubsectionSpecial.findMany({ ...paginateArgs, where: safeWhere, orderBy }), + }) + + const subsubsectionSpecialWithCount = await Promise.all( + subsubsectionSpecials.map(async (subsubsectionSpecial) => { + const subsubsectionCount = await db.subsubsection.count({ + where: { + subsection: { project: { slug: projectSlug } }, + specialFeatures: { + some: { + id: subsubsectionSpecial.id, + }, + }, + }, + }) + return { + ...subsubsectionSpecial, + subsubsectionCount, + } + }), + ) + + return { + subsubsectionSpecials: subsubsectionSpecialWithCount, + hasMore, + count, + } + }, +) diff --git a/src/subsubsectionSpecial/schema.ts b/src/subsubsectionSpecial/schema.ts new file mode 100644 index 000000000..8475c685e --- /dev/null +++ b/src/subsubsectionSpecial/schema.ts @@ -0,0 +1,10 @@ +import { SlugSchema } from "src/core/utils" +import { z } from "zod" + +export const SubsubsectionSpecial = z.object({ + slug: SlugSchema, + title: z.string().min(2, { message: "Pflichtfeld. Mindestens 2 Zeichen." }), + projectId: z.coerce.number(), +}) + +type TSubsubsectionSpecial = z.infer diff --git a/src/subsubsectionTask/components/SubsubsectionTaskForm.tsx b/src/subsubsectionTask/components/SubsubsectionTaskForm.tsx new file mode 100644 index 000000000..3a2c3b31b --- /dev/null +++ b/src/subsubsectionTask/components/SubsubsectionTaskForm.tsx @@ -0,0 +1,14 @@ +import { Form, FormProps, LabeledTextField } from "src/core/components/forms" +import { z } from "zod" +export { FORM_ERROR } from "src/core/components/forms" + +export function SubsubsectionTaskForm>(props: FormProps) { + const { ...formProps } = props + + return ( + {...formProps}> + + + + ) +} diff --git a/src/subsubsectionTask/mutations/createSubsubsectionTask.ts b/src/subsubsectionTask/mutations/createSubsubsectionTask.ts new file mode 100644 index 000000000..2d20cbe82 --- /dev/null +++ b/src/subsubsectionTask/mutations/createSubsubsectionTask.ts @@ -0,0 +1,25 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getProjectIdBySlug from "../../projects/queries/getProjectIdBySlug" +import { OperatorSchema } from "src/operators/schema" +import { SubsubsectionTask } from "../schema" + +const CreateSubsubsectionTaskSchema = SubsubsectionTask.omit({ projectId: true }).merge( + z.object({ + projectSlug: z.string(), + }), +) + +export default resolver.pipe( + resolver.zod(CreateSubsubsectionTaskSchema), + authorizeProjectAdmin(getProjectIdBySlug), + async ({ projectSlug, ...input }) => + await db.subsubsectionTask.create({ + data: { + ...input, + projectId: await getProjectIdBySlug(projectSlug), + }, + }), +) diff --git a/src/subsubsectionTask/mutations/deleteSubsubsectionTask.ts b/src/subsubsectionTask/mutations/deleteSubsubsectionTask.ts new file mode 100644 index 000000000..e2959bf18 --- /dev/null +++ b/src/subsubsectionTask/mutations/deleteSubsubsectionTask.ts @@ -0,0 +1,15 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getSubsubsectionTaskProjectId from "../queries/getSubsubsectionTaskProjectId" + +const DeleteSubsubsectionTaskSchema = z.object({ + id: z.number(), +}) + +export default resolver.pipe( + resolver.zod(DeleteSubsubsectionTaskSchema), + authorizeProjectAdmin(getSubsubsectionTaskProjectId), + async ({ id }) => await db.subsubsectionTask.deleteMany({ where: { id } }), +) diff --git a/src/subsubsectionTask/mutations/updateSubsubsectionTask.ts b/src/subsubsectionTask/mutations/updateSubsubsectionTask.ts new file mode 100644 index 000000000..864ec384b --- /dev/null +++ b/src/subsubsectionTask/mutations/updateSubsubsectionTask.ts @@ -0,0 +1,22 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getQualityLevelProjectId from "../queries/getSubsubsectionTaskProjectId" +import { SubsubsectionTask } from "../schema" + +const UpdateSubsubsectionTaskSchema = SubsubsectionTask.merge( + z.object({ + id: z.number(), + }), +) + +export default resolver.pipe( + resolver.zod(UpdateSubsubsectionTaskSchema), + authorizeProjectAdmin(getQualityLevelProjectId), + async ({ id, ...data }) => + await db.subsubsectionTask.update({ + where: { id }, + data, + }), +) diff --git a/src/subsubsectionTask/queries/getSubsubsectionTask.ts b/src/subsubsectionTask/queries/getSubsubsectionTask.ts new file mode 100644 index 000000000..8b15846e6 --- /dev/null +++ b/src/subsubsectionTask/queries/getSubsubsectionTask.ts @@ -0,0 +1,20 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { authorizeProjectAdmin } from "src/authorization" +import { z } from "zod" +import getQualityLevelProjectId from "./getSubsubsectionTaskProjectId" + +const GetSubsubsectionTask = z.object({ + // This accepts type of undefined, but is required at runtime + id: z.number().optional().refine(Boolean, "Required"), +}) + +export default resolver.pipe( + resolver.zod(GetSubsubsectionTask), + authorizeProjectAdmin(getQualityLevelProjectId), + async ({ id }) => { + return await db.subsubsectionTask.findFirstOrThrow({ + where: { id }, + }) + }, +) diff --git a/src/subsubsectionTask/queries/getSubsubsectionTaskProjectId.ts b/src/subsubsectionTask/queries/getSubsubsectionTaskProjectId.ts new file mode 100644 index 000000000..5b9919a77 --- /dev/null +++ b/src/subsubsectionTask/queries/getSubsubsectionTaskProjectId.ts @@ -0,0 +1,11 @@ +import db from "db" + +const getSubsubsectionTaskProjectId = async (input: Record) => + ( + await db.subsubsectionTask.findFirstOrThrow({ + where: { id: input.id || null }, + select: { projectId: true }, + }) + ).projectId + +export default getSubsubsectionTaskProjectId diff --git a/src/subsubsectionTask/queries/getSubsubsectionTasksWithCount.ts b/src/subsubsectionTask/queries/getSubsubsectionTasksWithCount.ts new file mode 100644 index 000000000..a70f548bb --- /dev/null +++ b/src/subsubsectionTask/queries/getSubsubsectionTasksWithCount.ts @@ -0,0 +1,58 @@ +import { resolver } from "@blitzjs/rpc" +import { paginate } from "blitz" +import db, { Prisma } from "db" +import { authorizeProjectAdmin } from "src/authorization" +import getProjectIdBySlug from "src/projects/queries/getProjectIdBySlug" + +type GetSubsubsectionTaskInput = { projectSlug: string } & Pick< + Prisma.SubsubsectionTaskFindManyArgs, + "where" | "orderBy" | "skip" | "take" +> + +export default resolver.pipe( + // @ts-ignore + authorizeProjectAdmin(getProjectIdBySlug), + async ({ + projectSlug, + where, + orderBy = { id: "asc" }, + skip = 0, + take = 100, + }: GetSubsubsectionTaskInput) => { + const safeWhere = { project: { slug: projectSlug }, ...where } + + const { + items: subsubsectionTasks, + hasMore, + nextPage, + count, + } = await paginate({ + skip, + take, + count: () => db.subsubsectionTask.count({ where: safeWhere }), + query: (paginateArgs) => + db.subsubsectionTask.findMany({ ...paginateArgs, where: safeWhere, orderBy }), + }) + + const subsubsectionTaskWithCount = await Promise.all( + subsubsectionTasks.map(async (subsubsectionTask) => { + const subsubsectionCount = await db.subsubsection.count({ + where: { + subsection: { project: { slug: projectSlug } }, + subsubsectionTaskId: subsubsectionTask.id, + }, + }) + return { + ...subsubsectionTask, + subsubsectionCount, + } + }), + ) + + return { + subsubsectionTasks: subsubsectionTaskWithCount, + hasMore, + count, + } + }, +) diff --git a/src/subsubsectionTask/schema.ts b/src/subsubsectionTask/schema.ts new file mode 100644 index 000000000..91b88e901 --- /dev/null +++ b/src/subsubsectionTask/schema.ts @@ -0,0 +1,10 @@ +import { SlugSchema } from "src/core/utils" +import { z } from "zod" + +export const SubsubsectionTask = z.object({ + slug: SlugSchema, + title: z.string().min(2, { message: "Pflichtfeld. Mindestens 2 Zeichen." }), + projectId: z.coerce.number(), +}) + +type TSubsubsectionTask = z.infer diff --git a/src/subsubsections/components/GeometryInput/GeometryInput.tsx b/src/subsubsections/components/GeometryInput/GeometryInput.tsx index b4f64f54f..dc48b6360 100644 --- a/src/subsubsections/components/GeometryInput/GeometryInput.tsx +++ b/src/subsubsections/components/GeometryInput/GeometryInput.tsx @@ -37,7 +37,7 @@ export const GeometryInput: React.FC = () => { return ( <> >(props: FormProps) { @@ -38,6 +45,30 @@ export function SubsubsectionForm>(props: FormProp return [status.id, status.title] as [number, string] }), ] + const [{ subsubsectionTasks }] = useQuery(getSubsubsectionTasksWithCount, { projectSlug }) + const subsubsectionTaskOptions: [number | string, string][] = [ + ["", "-"], + ...subsubsectionTasks.map((task) => { + return [task.id, task.title] as [number, string] + }), + ] + const [{ subsubsectionInfras }] = useQuery(getSubsubsectionInfrasWithCount, { projectSlug }) + const subsubsectionInfraOptions: [number | string, string][] = [ + ["", "-"], + ...subsubsectionInfras.map((infra) => { + return [infra.id, infra.title] as [number, string] + }), + ] + const [{ subsubsectionSpecials }] = useQuery(getSubsubsectionSpecialsWithCount, { projectSlug }) + // @ts-ignore + const subsubsectionSpecialOptions: [number | string, string][] = subsubsectionSpecials.map( + (special) => { + return { + value: String(special.id), + label: special.title, + } + }, + ) return ( {...props}> @@ -50,20 +81,62 @@ export function SubsubsectionForm>(props: FormProp )}. Primäre Auszeichnung der Führung. Wird immer in Großschreibung angezeigt aber in Kleinschreibung editiert. Nachträgliche Änderungen sorgen dafür, dass bisherige URLs (Bookmarks, in E-Mails) nicht mehr funktionieren.`} /> - - +
+ + + Maßnahmetypen verwalten… + +
+
+ + + Führungsformen verwalten… + +
+ {/* @ts-ignore */} +
+ +
+ + Besonderheiten verwalten… + +
+
+ + >(props: FormProp type="number" step="0.01" name="width" - label="Breite" + label="Breite Bestand" optional /> >(props: FormProp inlineLeadingAddon="€" type="number" name="revenuesEconomicIncome" - label="Erlöse und wirtschafltiche Einnahmen" + label="Erlöse und wirtschaftliche Einnahmen" optional /> = ({ subsubsections, compact }) "py-4 pl-4 pr-3 text-sm font-medium text-gray-900", )} > - {formattedLength(subsubsection.length)} + {formattedLength(subsubsection.lengthKm)} = ({ subsubsections, comp "pt-4 pb-2 pl-4 pr-3 text-sm font-medium text-gray-900 whitespace-nowrap", )} > - {formattedLength(subsubsections.reduce((acc, sub) => acc + (sub.length || 0), 0))} + {formattedLength(subsubsections.reduce((acc, sub) => acc + (sub.lengthKm || 0), 0))} = ({ subsubsections, comp )} > {formattedLength( - subsubsectionForQualityLevel.reduce((acc, sub) => acc + (sub.length || 0), 0), + subsubsectionForQualityLevel.reduce((acc, sub) => acc + (sub.lengthKm || 0), 0), )} await db.subsubsection.create({ data: input }), + async (data) => { + const connect = {} + m2mFields.forEach((fieldName) => { + // @ts-ignore + connect[fieldName] = { connect: data[fieldName].map((id) => ({ id })) } + // @ts-ignore + delete data[fieldName] + }) + // @ts-ignore + return await db.subsubsection.create({ data: { ...data, ...connect } }) + }, ) diff --git a/src/subsubsections/mutations/updateSubsubsection.ts b/src/subsubsections/mutations/updateSubsubsection.ts index ccb5e5d72..b2dfdcf7d 100644 --- a/src/subsubsections/mutations/updateSubsubsection.ts +++ b/src/subsubsections/mutations/updateSubsubsection.ts @@ -6,20 +6,45 @@ import { z } from "zod" import { SubsubsectionWithPosition } from "../queries/getSubsubsection" import { SubsubsectionSchema } from "../schema" import getSubsubsectionProjectId from "../queries/getSubsubsectionProjectId" +import m2mFields from "../m2mFields" -const UpdateSubsubsectionSchema = SubsubsectionSchema.merge( +let UpdateSubsubsectionSchema = SubsubsectionSchema.merge( z.object({ id: z.number(), }), ) +m2mFields.forEach((fieldName) => { + // @ts-ignore + UpdateSubsubsectionSchema = UpdateSubsubsectionSchema.merge( + z.object({ + [fieldName]: z.array(z.number()), + }), + ) +}) +// @ts-ignore export default resolver.pipe( resolver.zod(UpdateSubsubsectionSchema), authorizeProjectAdmin(getSubsubsectionProjectId), async ({ id, ...data }) => { + const disconnect = {} + const connect = {} + m2mFields.forEach((fieldName) => { + // @ts-ignore + disconnect[fieldName] = { set: [] } + // @ts-ignore + connect[fieldName] = { connect: data[fieldName].map((id) => ({ id })) } + // @ts-ignore + delete data[fieldName] + }) + + await db.subsubsection.update({ + where: { id }, + data: disconnect, + }) const subsubsection = await db.subsubsection.update({ where: { id }, - data, + data: { ...data, ...connect }, }) return subsubsection as SubsubsectionWithPosition // Tip: Validate type shape with `satisfies` }, diff --git a/src/subsubsections/queries/getSubsubsection.ts b/src/subsubsections/queries/getSubsubsection.ts index 247f31207..2927771ce 100644 --- a/src/subsubsections/queries/getSubsubsection.ts +++ b/src/subsubsections/queries/getSubsubsection.ts @@ -3,6 +3,7 @@ import db, { QualityLevel, Subsubsection, SubsubsectionTypeEnum, User } from "db import { authorizeProjectAdmin } from "src/authorization" import getProjectIdBySlug from "src/projects/queries/getProjectIdBySlug" import { z } from "zod" +import m2mFields from "../m2mFields" const GetSubsubsection = z.object({ projectSlug: z.string(), @@ -10,6 +11,10 @@ const GetSubsubsection = z.object({ subsubsectionSlug: z.string(), }) +const includeM2mFields = {} +// @ts-ignore +m2mFields.forEach((fieldName) => (includeM2mFields[fieldName] = { select: { id: true } })) + // We lie with TypeScript here, because we know better. All `geometry` fields are Position. We make sure of that in our Form. They are also required, so never empty. export type SubsubsectionWithPosition = Omit & ( @@ -43,6 +48,7 @@ export default resolver.pipe( manager: { select: { firstName: true, lastName: true } }, subsection: { select: { slug: true } }, qualityLevel: { select: { title: true, slug: true } }, + ...includeM2mFields, }, } diff --git a/src/subsubsections/schema.ts b/src/subsubsections/schema.ts index d5b4e1c6b..9939657f9 100644 --- a/src/subsubsections/schema.ts +++ b/src/subsubsections/schema.ts @@ -2,6 +2,7 @@ import { LabelPositionEnum, SubsubsectionTypeEnum } from "@prisma/client" import { Prettify } from "src/core/types" import { SlugSchema, InputNumberOrNullSchema } from "src/core/utils" import { z } from "zod" +import m2mFields from "./m2mFields" const PositionSchema = z.tuple([z.number(), z.number()]) // Position const PositionArraySchema = z.array(z.tuple([z.number(), z.number()])) // Position[] @@ -12,51 +13,63 @@ const PositionArraySchema = z.array(z.tuple([z.number(), z.number()])) // Positi // z.object({ type: z.literal("AREA"), geometry: PositionSchema }), // Point // ]) -export const SubsubsectionSchema = z.object({ - slug: SlugSchema, - subTitle: z.string().nullish(), - type: z.nativeEnum(SubsubsectionTypeEnum), - geometry: PositionSchema.or(PositionArraySchema), - labelPos: z.nativeEnum(LabelPositionEnum), - task: z.string().min(3, { - message: "Pflichtfeld. Mindestens 3 Zeichen.", - }), // Maßnahmentyp - length: InputNumberOrNullSchema, // km - width: InputNumberOrNullSchema, // m - costEstimate: InputNumberOrNullSchema, // € - description: z.string().nullish(), - mapillaryKey: z.string().nullish(), - qualityLevelId: InputNumberOrNullSchema, - managerId: InputNumberOrNullSchema, - subsectionId: z.coerce.number(), - subsubsectionStatusId: InputNumberOrNullSchema, - maxSpeed: InputNumberOrNullSchema, - trafficLoad: InputNumberOrNullSchema, - trafficLoadDate: z.union([ - z.coerce - .date({ - // `coerce` makes it that we need to work around a nontranslatable error - // Thanks to https://github.com/colinhacks/zod/discussions/1851#discussioncomment-4649675 - errorMap: ({ code }, { defaultError }) => { - if (code == "invalid_date") return { message: "Das Datum ist nicht richtig formatiert." } - return { message: defaultError } - }, - }) - .nullish(), - z.literal(""), - ]), - planningCosts: InputNumberOrNullSchema, - deliveryCosts: InputNumberOrNullSchema, - constructionCosts: InputNumberOrNullSchema, - landAcquisitionCosts: InputNumberOrNullSchema, - expensesOfficialOrders: InputNumberOrNullSchema, - expensesTechnicalVerification: InputNumberOrNullSchema, - nonEligibleExpenses: InputNumberOrNullSchema, - revenuesEconomicIncome: InputNumberOrNullSchema, - contributionsThirdParties: InputNumberOrNullSchema, - grantsOtherFunding: InputNumberOrNullSchema, - ownFunds: InputNumberOrNullSchema, -}) +export const SubsubsectionSchema = z + .object({ + slug: SlugSchema, + subTitle: z.string().nullish(), + type: z.nativeEnum(SubsubsectionTypeEnum), + geometry: PositionSchema.or(PositionArraySchema), + labelPos: z.nativeEnum(LabelPositionEnum), + lengthKm: InputNumberOrNullSchema, // km + width: InputNumberOrNullSchema, // m + costEstimate: InputNumberOrNullSchema, // € + description: z.string().nullish(), + mapillaryKey: z.string().nullish(), + isExistingInfra: z.boolean(), + qualityLevelId: InputNumberOrNullSchema, + managerId: InputNumberOrNullSchema, + subsectionId: z.coerce.number(), + subsubsectionStatusId: InputNumberOrNullSchema, + subsubsectionTaskId: InputNumberOrNullSchema, + subsubsectionInfraId: InputNumberOrNullSchema, + maxSpeed: InputNumberOrNullSchema, + trafficLoad: InputNumberOrNullSchema, + trafficLoadDate: z.union([ + z.coerce + .date({ + // `coerce` makes it that we need to work around a nontranslatable error + // Thanks to https://github.com/colinhacks/zod/discussions/1851#discussioncomment-4649675 + errorMap: ({ code }, { defaultError }) => { + if (code == "invalid_date") + return { message: "Das Datum ist nicht richtig formatiert." } + return { message: defaultError } + }, + }) + .nullish(), + z.literal(""), + ]), + planningCosts: InputNumberOrNullSchema, + deliveryCosts: InputNumberOrNullSchema, + constructionCosts: InputNumberOrNullSchema, + landAcquisitionCosts: InputNumberOrNullSchema, + expensesOfficialOrders: InputNumberOrNullSchema, + expensesTechnicalVerification: InputNumberOrNullSchema, + nonEligibleExpenses: InputNumberOrNullSchema, + revenuesEconomicIncome: InputNumberOrNullSchema, + contributionsThirdParties: InputNumberOrNullSchema, + grantsOtherFunding: InputNumberOrNullSchema, + ownFunds: InputNumberOrNullSchema, + }) + .merge( + z.object( + Object.fromEntries( + m2mFields.map((fieldName) => { + return [fieldName, z.array(z.number())] + }), + ), + ), + ) + export type TSubsubsectionSchema = Prettify> export const SubsubsectionTrafficLoadDateSchema = z.object({ @@ -65,6 +78,21 @@ export const SubsubsectionTrafficLoadDateSchema = z.object({ z.literal(""), ]), }) -export const SubsubsectionFormSchema = SubsubsectionSchema.omit({ trafficLoadDate: true }).merge( + +// @ts-ignore +let FormSchema = SubsubsectionSchema.omit({ trafficLoadDate: true }).merge( SubsubsectionTrafficLoadDateSchema, ) + +m2mFields.forEach((fieldName) => { + // @ts-ignore + FormSchema = FormSchema.merge( + z.object({ + [fieldName]: z + .union([z.undefined(), z.boolean(), z.array(z.coerce.number())]) + .transform((v) => v || []), + }), + ) +}) + +export const SubsubsectionFormSchema = FormSchema diff --git a/src/survey-public/README.md b/src/survey-public/README.md new file mode 100644 index 000000000..e8caa49d5 --- /dev/null +++ b/src/survey-public/README.md @@ -0,0 +1,13 @@ +## Referencing survey questions / evaluationRefs + +in [surveyslug]/data/survey.ts and ...feedback.ts the question and possible responses of the survey are defined. + +To reference specific questions/responses (that exist in all surveys but might have different ids), the constant '''evaluationRefs''' in [surveyslug]/data/responses-config.ts holds a record of references and question-ids, that we have to keep up to date manually: + +evaluationRefs: { + "feedback-category": 21, + "is-feedback-location": 22, + "feedback-location": 23, + "feedback-usertext-1": 34, + "feedback-usertext-2": 35 // (optional) + }, diff --git a/src/survey-public/components/Email.tsx b/src/survey-public/components/Email.tsx new file mode 100644 index 000000000..5d32d18ce --- /dev/null +++ b/src/survey-public/components/Email.tsx @@ -0,0 +1,67 @@ +import { iframeResizer } from "iframe-resizer" +import { useEffect } from "react" +import { SurveyH2, SurveyP } from "./core/Text" +import { SurveyScreenHeader } from "./core/layout/SurveyScreenHeader" +import { SurveyLink } from "./core/links/SurveyLink" +import { partcipationLinkStyles, partcipationRedLinkStyles } from "./core/links/styles" +import { TEmail } from "./types" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + email: TEmail + homeUrl: string +} + +export const Email: React.FC = ({ email, homeUrl }) => { + const { description, questionText, button, linkColor, title, mailjetWidgetUrl } = email + + useEffect(() => { + iframeResizer({}, "#mailjet-widget") + }, []) + + // todo frm7 + let colorClass: string + switch (linkColor) { + case "pink": + colorClass = partcipationLinkStyles + break + case "red": + colorClass = partcipationRedLinkStyles + break + default: + colorClass = partcipationLinkStyles + } + + return ( +
+ + {questionText.de} + {description.de} + Möchten Sie uns noch etwas mit auf den Weg geben? + + Wenn Sie noch weiteres Feedback zur Umfrage/zur Online-Beteiligung haben, können Sie dies + gerne an{" "} + + feedback@fixmycity.de + {" "} + senden.{" "} + + +
+ `, + }} + /> + +
+ + {button.label.de} + +
+
+ ) +} diff --git a/src/survey-public/components/More.tsx b/src/survey-public/components/More.tsx new file mode 100644 index 000000000..0ab1aa2a2 --- /dev/null +++ b/src/survey-public/components/More.tsx @@ -0,0 +1,36 @@ +import { SurveyH2 } from "./core/Text" +import { SurveyButton } from "./core/buttons/SurveyButton" +import { SurveyButtonWrapper } from "./core/buttons/SurveyButtonWrapper" +import { SurveyScreenHeader } from "./core/layout/SurveyScreenHeader" +import { TMore } from "./types" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + onClickMore: any + onClickFinish: any + more: TMore +} + +export const More: React.FC = ({ more, onClickMore, onClickFinish }) => { + const { title, description, questionText, buttons } = more + + return ( + <> + + {questionText.de} + + {buttons[0] && ( + + {buttons[0].label.de} + + )} + {buttons[1] && ( + + {buttons[1].label.de} + + )} + + + ) +} diff --git a/src/participation/components/survey/Page.tsx b/src/survey-public/components/Page.tsx similarity index 66% rename from src/participation/components/survey/Page.tsx rename to src/survey-public/components/Page.tsx index b33db80d8..b8dafe54c 100644 --- a/src/participation/components/survey/Page.tsx +++ b/src/survey-public/components/Page.tsx @@ -1,7 +1,7 @@ -import { ScreenHeaderParticipation } from "src/participation/components/layout/ScreenHeaderParticipation" -import { SurveyButton } from "./SurveyButton" -import { ParticipationButtonWrapper } from "../core/buttons/ParticipationButtonWrapper" -import type { Page as TPage } from "src/participation/data/types" +import { SurveyScreenHeader } from "src/survey-public/components/core/layout/SurveyScreenHeader" +import { SurveyButtonWithAction } from "./core/buttons/SurveyButtonWithAction" +import { SurveyButtonWrapper } from "./core/buttons/SurveyButtonWrapper" +import type { TPage as TPage } from "src/survey-public/components/types" import { Question } from "./Question" export { FORM_ERROR } from "src/core/components/forms" @@ -17,20 +17,20 @@ export const Page: React.FC = ({ page, buttonActions, completed }) => { return (
- + {questions && questions.length && questions.map((question) => ( ))} - + {buttons?.map((button) => { let disabled = false if (["nextPage", "submit"].includes(button.onClick.action)) { disabled = !completed } return ( - = ({ page, buttonActions, completed }) => { /> ) })} - +
) } diff --git a/src/participation/components/survey/Question.tsx b/src/survey-public/components/Question.tsx similarity index 59% rename from src/participation/components/survey/Question.tsx rename to src/survey-public/components/Question.tsx index 9f427a97f..796048ca6 100644 --- a/src/participation/components/survey/Question.tsx +++ b/src/survey-public/components/Question.tsx @@ -1,26 +1,26 @@ -import { SingleOrMultiResponseProps, TextResponseProps } from "src/participation/data/types" -import { Question as TQuestion } from "src/participation/data/types" -import { ParticipationH2 } from "../core/Text" -import { ParticipationLabeledCheckboxGroup } from "../form/ParticipationLabeledCheckboxGroup" -import { ParticipationLabeledRadiobuttonGroup } from "../form/ParticipationLabeledRadiobuttonGroup" -import { ParticipationLabeledTextareaField } from "../form/ParticipationLabeledTextareaField" +import { TQuestion, TSingleOrMultiResponseProps } from "src/survey-public/components/types" +import { SurveyH2 } from "./core/Text" +import { SurveyLabeledCheckboxGroup } from "./core/form/SurveyLabeledCheckboxGroup" +import { SurveyLabeledRadiobuttonGroup } from "./core/form/SurveyLabeledRadiobuttonGroup" +import { SurveyLabeledTextareaField } from "./core/form/SurveyLabeledTextareaField" export { FORM_ERROR } from "src/core/components/forms" type TSingleOrMultuResponseComponentProps = { id: number -} & SingleOrMultiResponseProps +} & TSingleOrMultiResponseProps const SingleResponseComponent: React.FC = ({ id, responses, }) => ( - ({ scope: `single-${id}`, name: `${id}-${item.id}`, label: item.text.de, help: item?.help?.de, value: `${item.id}`, + primaryColor: "red", // todo frm7 }))} /> ) @@ -29,26 +29,30 @@ const MultipleResponseComponent: React.FC id, responses, }) => ( - ({ name: `multi-${id}-${item.id}`, label: item.text.de, help: item?.help?.de, + primaryColor: "red", // todo frm7 }))} /> ) type TTextResponseComponentProps = { id: number -} & TextResponseProps + primaryColor: "red" | "pink" +} -const TextResponseComponent: React.FC = ({ id }) => ( +const TextResponseComponent: React.FC = ({ + id, + primaryColor = "red", // todo frm7 +}) => ( <> - + ) - // TODO type const CustomComponent = (props: any) => (
@@ -65,14 +69,15 @@ const components = { custom: CustomComponent, } -type Props = { question: TQuestion; className?: string } +type Props = { question: TQuestion; className?: string; primaryColor?: "red" | "pink" } export const Question: React.FC = ({ question, className }) => { - const { id, label, component, props } = question + const { id, help, label, component, props } = question const Component = components[component] || null return (
- {label.de} + {label.de} + {help &&
{help.de}
} {/* @ts-ignore */} {Component && }
diff --git a/src/participation/components/survey/Survey.tsx b/src/survey-public/components/Survey.tsx similarity index 84% rename from src/participation/components/survey/Survey.tsx rename to src/survey-public/components/Survey.tsx index 0dc829642..27f1658a2 100644 --- a/src/participation/components/survey/Survey.tsx +++ b/src/survey-public/components/Survey.tsx @@ -1,14 +1,16 @@ import { useCallback, useContext, useState } from "react" -import SurveyForm from "../form/SurveyForm" export { FORM_ERROR } from "src/core/components/forms" -import { stageProgressDefinition } from "src/participation/components/rs8" -import { ProgressContext } from "src/participation/context/contexts" -import { Survey as TSurvey } from "src/participation/data/types" -import { Debug } from "../Debug" -import { Page } from "./Page" -import { scrollToTopWithDelay } from "src/participation/utils/scrollToTopWithDelay" +import { TSurvey } from "src/survey-public/components/types" + +import { ProgressContext } from "src/survey-public/context/contexts" + +import { Debug } from "src/survey-public/components/core/Debug" +import PublicSurveyForm from "src/survey-public/components/core/form/PublicSurveyForm" +import { Page } from "src/survey-public/components/Page" +import { scrollToTopWithDelay } from "src/survey-public/utils/scrollToTopWithDelay" +import { stageProgressDefinition } from "../frm7/data/progress" type Props = { survey: TSurvey; onSubmit: ([]) => void } @@ -97,13 +99,13 @@ export const Survey: React.FC = ({ survey, onSubmit }) => { return ( // @ts-ignore - +
{JSON.stringify(values, null, 2)}
{page && } -
+ ) } diff --git a/src/survey-public/components/SurveyInactivePage.tsx b/src/survey-public/components/SurveyInactivePage.tsx new file mode 100644 index 000000000..9b475a2e5 --- /dev/null +++ b/src/survey-public/components/SurveyInactivePage.tsx @@ -0,0 +1,26 @@ +import { BlitzPage } from "@blitzjs/next" +import { SurveyLayout } from "src/survey-public/components/core/layout/SurveyLayout" +import { TSurvey } from "./types" +import { SurveyScreenHeader } from "./core/layout/SurveyScreenHeader" +import { SurveyLink } from "./core/links/SurveyLink" +type Props = { + surveyDefinition: TSurvey +} +const SurveyInactivePage: BlitzPage = ({ surveyDefinition }) => { + return ( + +
+ + + Zur Projektwebseite + +
+
+ ) +} + +export default SurveyInactivePage diff --git a/src/participation/components/rs8.tsx b/src/survey-public/components/SurveyMainPage.tsx similarity index 65% rename from src/participation/components/rs8.tsx rename to src/survey-public/components/SurveyMainPage.tsx index da67ed4bd..b85994832 100644 --- a/src/participation/components/rs8.tsx +++ b/src/survey-public/components/SurveyMainPage.tsx @@ -1,37 +1,42 @@ -import { createContext, useState } from "react" -import { BlitzPage } from "@blitzjs/next" import { useMutation } from "@blitzjs/rpc" +import { useState } from "react" -import { Done } from "src/participation/components/Done" -import { Email } from "src/participation/components/Email" -import { Feedback } from "src/participation/components/feedback/Feedback" -import { LayoutParticipation } from "src/participation/components/layout/LayoutParticipation" -import { More } from "src/participation/components/More" -import { Survey } from "src/participation/components/survey/Survey" -import moreDefinition from "src/participation/data/more.json" -import surveyDefinition from "src/participation/data/survey.json" -import feedbackDefinition from "src/participation/data/feedback.json" -import emailDefinition from "src/participation/data/email.json" -import { ProgressContext } from "src/participation/context/contexts" +import { Survey } from "src/survey-public/components/Survey" +import { Feedback } from "src/survey-public/components/feedback/Feedback" + +import { Email } from "src/survey-public/components/Email" +import { More } from "src/survey-public/components/More" +import { ProgressContext } from "src/survey-public/context/contexts" +import { Debug } from "src/survey-public/components/core/Debug" +import { SurveyLayout } from "src/survey-public/components/core/layout/SurveyLayout" +import { SurveySpinnerLayover } from "src/survey-public/components/core/layout/SurveySpinnerLayover" +import { scrollToTopWithDelay } from "src/survey-public/utils/scrollToTopWithDelay" + +import createSurveyResponse from "src/survey-responses/mutations/createSurveyResponse" import createSurveySession from "src/survey-sessions/mutations/createSurveySession" import updateSurveySession from "src/survey-sessions/mutations/updateSurveySession" -import createSurveyResponse from "src/survey-responses/mutations/createSurveyResponse" -import { Debug } from "src/participation/components/Debug" -import { scrollToTopWithDelay } from "src/participation/utils/scrollToTopWithDelay" -import { Spinner } from "src/core/components/Spinner" -import { ParticipationSpinnerLayover } from "src/participation/components/survey/ParticipationSpinnerLayover" - -// For Progressbar: stage and associated arbitrarily set status of the progressbar -export const stageProgressDefinition = { - SURVEY: 1, - MORE: 5, - FEEDBACK: 6, - EMAIL: 8, - DONE: 8, +import { TEmail, TFeedback, TMore, TProgress, TResponseConfig, TSurvey } from "./types" + +type Props = { + emailDefinition: TEmail + feedbackDefinition: TFeedback + moreDefinition: TMore + stageProgressDefinition: TProgress + surveyDefinition: TSurvey + responseConfig: TResponseConfig + surveyId: number } -const ParticipationMainPage: BlitzPage = () => { - const [stage, setStage] = useState<"SURVEY" | "MORE" | "FEEDBACK" | "EMAIL" | "DONE">("SURVEY") +export const SurveyMainPage: React.FC = ({ + emailDefinition, + feedbackDefinition, + moreDefinition, + stageProgressDefinition, + surveyDefinition, + responseConfig, + surveyId, +}) => { + const [stage, setStage] = useState<"SURVEY" | "MORE" | "FEEDBACK" | "EMAIL">("SURVEY") const [progress, setProgress] = useState(1) const [isSpinner, setIsSpinner] = useState(false) const [responses, setResponses] = useState([]) @@ -46,7 +51,7 @@ const ParticipationMainPage: BlitzPage = () => { if (surveySessionId) { return surveySessionId } else { - const surveySession = await createSurveySessionMutation({ surveyId: 1 }) + const surveySession = await createSurveySessionMutation({ surveyId }) setSurveySessionId(surveySession.id) return surveySession.id } @@ -60,7 +65,7 @@ const ParticipationMainPage: BlitzPage = () => { const surveySessionId_ = await getOrCreateSurveySessionId() await createSurveyResponseMutation({ surveySessionId: surveySessionId_, - surveyId: surveyDefinition.id, + surveyPart: surveyDefinition.part, data: JSON.stringify(surveyResponses), }) })() @@ -84,7 +89,7 @@ const ParticipationMainPage: BlitzPage = () => { const surveySessionId_ = await getOrCreateSurveySessionId() await createSurveyResponseMutation({ surveySessionId: surveySessionId_, - surveyId: feedbackDefinition.id, + surveyPart: feedbackDefinition.part, data: JSON.stringify(feedbackResponses), }) })() @@ -116,14 +121,6 @@ const ParticipationMainPage: BlitzPage = () => { scrollToTopWithDelay() } - const handleSubmitEmail = async (email: string | null) => { - setStage("DONE") - setProgress(stageProgressDefinition["DONE"]) - scrollToTopWithDelay() - setEmailState(email) - await updateSurveySessionMutation({ id: surveySessionId! }) - } - let component switch (stage) { case "SURVEY": @@ -137,20 +134,24 @@ const ParticipationMainPage: BlitzPage = () => { break case "FEEDBACK": component = ( - + ) break case "EMAIL": - component = - break - case "DONE": - component = + component = break } return ( - @@ -162,10 +163,8 @@ const ParticipationMainPage: BlitzPage = () => { email: {emailState}
{component}
- {isSpinner && } -
+ {isSpinner && } +
) } - -export default ParticipationMainPage diff --git a/src/participation/components/Debug.tsx b/src/survey-public/components/core/Debug.tsx similarity index 100% rename from src/participation/components/Debug.tsx rename to src/survey-public/components/core/Debug.tsx diff --git a/src/survey-public/components/core/SurveyMetaTags.tsx b/src/survey-public/components/core/SurveyMetaTags.tsx new file mode 100644 index 000000000..879e43058 --- /dev/null +++ b/src/survey-public/components/core/SurveyMetaTags.tsx @@ -0,0 +1,17 @@ +import Head from "next/head" +import React from "react" + +type Props = { + title?: string | null + canonicalUrl?: string +} + +export const SurveyMetaTags: React.FC = ({ title, canonicalUrl }) => { + return ( + + {title} + + + + ) +} diff --git a/src/participation/components/core/Text.tsx b/src/survey-public/components/core/Text.tsx similarity index 68% rename from src/participation/components/core/Text.tsx rename to src/survey-public/components/core/Text.tsx index 5ce3eae78..4a349b380 100644 --- a/src/participation/components/core/Text.tsx +++ b/src/survey-public/components/core/Text.tsx @@ -6,7 +6,7 @@ type Props = { className?: string } -export const ParticipationH1: React.FC = ({ className, children }) => { +export const SurveyH1: React.FC = ({ className, children }) => { return (

= ({ className, children }) => { ) } -export const ParticipationH2: React.FC = ({ className, children }) => { +export const SurveyH2: React.FC = ({ className, children }) => { return (

{children} @@ -27,7 +27,7 @@ export const ParticipationH2: React.FC = ({ className, children }) => { ) } -export const ParticipationH3: React.FC = ({ className, children }) => { +export const SurveyH3: React.FC = ({ className, children }) => { return (

{children} @@ -35,6 +35,6 @@ export const ParticipationH3: React.FC = ({ className, children }) => { ) } -export const ParticipationP: React.FC = ({ className, children }) => { +export const SurveyP: React.FC = ({ className, children }) => { return

{children}

} diff --git a/src/survey-public/components/core/buttons/SurveyButton.tsx b/src/survey-public/components/core/buttons/SurveyButton.tsx new file mode 100644 index 000000000..0038b01ec --- /dev/null +++ b/src/survey-public/components/core/buttons/SurveyButton.tsx @@ -0,0 +1,39 @@ +import clsx from "clsx" +import { ReactNode } from "react" +import { + surveyPinkButtonStyles, + surveyRedButtonStyles, + surveyWhiteButtonStyles, +} from "../links/styles" +import { pinkButtonStyles } from "src/core/components/links" +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + color?: string + disabled?: boolean + children: string | ReactNode +} & React.ButtonHTMLAttributes + +export const SurveyButton: React.FC = ({ disabled, color, children, ...props }) => { + let colorClass: string + switch (color) { + case "white": + colorClass = surveyWhiteButtonStyles + break + case "pink": + colorClass = surveyPinkButtonStyles + break + case "red": + colorClass = surveyRedButtonStyles + break + default: + colorClass = pinkButtonStyles + } + const buttonStyles = clsx("px-12", colorClass) + + return ( + + ) +} diff --git a/src/participation/components/survey/SurveyButton.tsx b/src/survey-public/components/core/buttons/SurveyButtonWithAction.tsx similarity index 51% rename from src/participation/components/survey/SurveyButton.tsx rename to src/survey-public/components/core/buttons/SurveyButtonWithAction.tsx index 36fe82b26..caf1ed9af 100644 --- a/src/participation/components/survey/SurveyButton.tsx +++ b/src/survey-public/components/core/buttons/SurveyButtonWithAction.tsx @@ -1,21 +1,21 @@ -import { Button } from "src/participation/data/types" -import { ParticipationButton } from "../core/buttons/ParticipationButton" +import { TButtonWithAction } from "src/survey-public/components/types" +import { SurveyButton } from "./SurveyButton" export { FORM_ERROR } from "src/core/components/forms" type Props = { - button: Button + button: TButtonWithAction buttonActions: { next: () => void; back: () => void } disabled?: boolean } -export const SurveyButton: React.FC = ({ disabled, button, buttonActions }) => { +export const SurveyButtonWithAction: React.FC = ({ disabled, button, buttonActions }) => { const { label, color, onClick } = button if (onClick.action === "submit") return ( - + {label.de} - + ) let buttonActionSelect: any @@ -29,13 +29,8 @@ export const SurveyButton: React.FC = ({ disabled, button, buttonActions } return ( - + {label.de} - + ) } diff --git a/src/participation/components/core/buttons/ParticipationButtonWrapper.tsx b/src/survey-public/components/core/buttons/SurveyButtonWrapper.tsx similarity index 73% rename from src/participation/components/core/buttons/ParticipationButtonWrapper.tsx rename to src/survey-public/components/core/buttons/SurveyButtonWrapper.tsx index c496acd54..c5447210e 100644 --- a/src/participation/components/core/buttons/ParticipationButtonWrapper.tsx +++ b/src/survey-public/components/core/buttons/SurveyButtonWrapper.tsx @@ -4,7 +4,7 @@ type Props = { children: ReactNode } -export const ParticipationButtonWrapper: React.FC = ({ children }) => { +export const SurveyButtonWrapper: React.FC = ({ children }) => { return (
{children} diff --git a/src/participation/components/form/SurveyForm.tsx b/src/survey-public/components/core/form/PublicSurveyForm.tsx similarity index 94% rename from src/participation/components/form/SurveyForm.tsx rename to src/survey-public/components/core/form/PublicSurveyForm.tsx index b694935bf..336ea68a1 100644 --- a/src/participation/components/form/SurveyForm.tsx +++ b/src/survey-public/components/core/form/PublicSurveyForm.tsx @@ -15,7 +15,7 @@ export interface FormProps> export const FORM_ERROR = "FORM_ERROR" -export function SurveyForm>({ +export function PublicSurveyForm>({ children, schema, initialValues, @@ -55,4 +55,4 @@ export function SurveyForm>({ ) } -export default SurveyForm +export default PublicSurveyForm diff --git a/src/survey-public/components/core/form/SurveyLabeledCheckbox.tsx b/src/survey-public/components/core/form/SurveyLabeledCheckbox.tsx new file mode 100644 index 000000000..2345ebbcb --- /dev/null +++ b/src/survey-public/components/core/form/SurveyLabeledCheckbox.tsx @@ -0,0 +1,80 @@ +import { ErrorMessage } from "@hookform/error-message" +import clsx from "clsx" +import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef, ReactNode } from "react" +import { useFormContext } from "react-hook-form" + +export interface TSurveyLabeledCheckbox extends PropsWithoutRef { + /** Field name. */ + name: string + /** Field label. */ + label: string | ReactNode + /** Help text below field label. */ + help?: string + outerProps?: PropsWithoutRef + labelProps?: ComponentPropsWithoutRef<"label"> + primaryColor: "red" | "pink" +} + +export const SurveyLabeledCheckbox = forwardRef( + ({ name, label, help, outerProps, labelProps, primaryColor, ...props }, ref) => { + const { + register, + formState: { isSubmitting, errors }, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + let colorClass: string + switch (primaryColor) { + case "pink": + colorClass = "text-pink-500" + break + case "red": + colorClass = "text-crimson-500" + break + default: + colorClass = "text-pink-500" + } + + return ( +
+
+ +
+ +
+ ) + }, +) diff --git a/src/survey-public/components/core/form/SurveyLabeledCheckboxGroup.tsx b/src/survey-public/components/core/form/SurveyLabeledCheckboxGroup.tsx new file mode 100644 index 000000000..4e7433df4 --- /dev/null +++ b/src/survey-public/components/core/form/SurveyLabeledCheckboxGroup.tsx @@ -0,0 +1,18 @@ +import clsx from "clsx" +import React from "react" +import { SurveyLabeledCheckbox, TSurveyLabeledCheckbox } from "./SurveyLabeledCheckbox" + +type Props = { + items: TSurveyLabeledCheckbox[] + className?: string +} + +export const SurveyLabeledCheckboxGroup: React.FC = ({ items, className }) => { + return ( +
+ {items.map((item, index) => { + return + })} +
+ ) +} diff --git a/src/survey-public/components/core/form/SurveyLabeledRadiobutton.tsx b/src/survey-public/components/core/form/SurveyLabeledRadiobutton.tsx new file mode 100644 index 000000000..ff84b6137 --- /dev/null +++ b/src/survey-public/components/core/form/SurveyLabeledRadiobutton.tsx @@ -0,0 +1,82 @@ +import { ErrorMessage } from "@hookform/error-message" +import clsx from "clsx" +import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react" +import { useFormContext } from "react-hook-form" + +export interface SurveyLabeledRadiobuttonProps + extends PropsWithoutRef { + /** Radiobutton scope. */ + scope: string + /** Field name. */ + name: string + /** Field label. */ + label: string + /** Field value. */ + value: string + /** Help text below field label. */ + help?: string + outerProps?: PropsWithoutRef + labelProps?: ComponentPropsWithoutRef<"label"> + primaryColor: "red" | "pink" +} + +export const SurveyLabeledRadiobutton = forwardRef( + ({ scope, name, label, value, help, outerProps, labelProps, primaryColor, ...props }, ref) => { + const { + register, + formState: { isSubmitting, errors }, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + let colorClass: string + switch (primaryColor) { + case "pink": + colorClass = "text-pink-500" + break + case "red": + colorClass = "text-crimson-500" + break + default: + colorClass = "text-pink-500" + } + + return ( +
+
+ +
+ +
+ ) + }, +) diff --git a/src/survey-public/components/core/form/SurveyLabeledRadiobuttonGroup.tsx b/src/survey-public/components/core/form/SurveyLabeledRadiobuttonGroup.tsx new file mode 100644 index 000000000..6ed4e3d2e --- /dev/null +++ b/src/survey-public/components/core/form/SurveyLabeledRadiobuttonGroup.tsx @@ -0,0 +1,18 @@ +import clsx from "clsx" +import React from "react" +import { SurveyLabeledRadiobutton, SurveyLabeledRadiobuttonProps } from "./SurveyLabeledRadiobutton" + +type Props = { + items: SurveyLabeledRadiobuttonProps[] + className?: string +} + +export const SurveyLabeledRadiobuttonGroup: React.FC = ({ items, className }) => { + return ( +
+ {items.map((item) => { + return + })} +
+ ) +} diff --git a/src/participation/components/form/ParticipationLabeledTextField.tsx b/src/survey-public/components/core/form/SurveyLabeledTextField.tsx similarity index 96% rename from src/participation/components/form/ParticipationLabeledTextField.tsx rename to src/survey-public/components/core/form/SurveyLabeledTextField.tsx index eb435e395..32a1a4bb9 100644 --- a/src/participation/components/form/ParticipationLabeledTextField.tsx +++ b/src/survey-public/components/core/form/SurveyLabeledTextField.tsx @@ -25,7 +25,7 @@ export interface LabeledTextFieldProps extends PropsWithoutRef( +export const SurveyLabeledTextField = forwardRef( ({ name, label, help, outerProps, labelProps, optional, ...props }, ref) => { const { register, diff --git a/src/participation/components/form/ParticipationLabeledTextareaField.tsx b/src/survey-public/components/core/form/SurveyLabeledTextareaField.tsx similarity index 70% rename from src/participation/components/form/ParticipationLabeledTextareaField.tsx rename to src/survey-public/components/core/form/SurveyLabeledTextareaField.tsx index 7ee0a5bb3..7a4c280d0 100644 --- a/src/participation/components/form/ParticipationLabeledTextareaField.tsx +++ b/src/survey-public/components/core/form/SurveyLabeledTextareaField.tsx @@ -3,7 +3,7 @@ import clsx from "clsx" import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react" import { useFormContext } from "react-hook-form" -export interface ParticipationLabeledTextareaProps +export interface SurveyLabeledTextareaProps extends PropsWithoutRef { /** Field name. */ name: string @@ -13,14 +13,25 @@ export interface ParticipationLabeledTextareaProps outerProps?: PropsWithoutRef labelProps?: ComponentPropsWithoutRef<"label"> optional?: boolean + primaryColor: "red" | "pink" } -export const ParticipationLabeledTextareaField = forwardRef< +export const SurveyLabeledTextareaField = forwardRef< HTMLTextAreaElement, - ParticipationLabeledTextareaProps + SurveyLabeledTextareaProps >( ( - { name, label, help, outerProps, labelProps, optional, className: textareaClasName, ...props }, + { + name, + label, + help, + outerProps, + labelProps, + optional, + className: textareaClasName, + primaryColor, + ...props + }, ref, ) => { const { @@ -30,6 +41,18 @@ export const ParticipationLabeledTextareaField = forwardRef< const hasError = Boolean(errors[name]) + let colorClass: string + switch (primaryColor) { + case "pink": + colorClass = "focus:border-pink-500 focus:ring-pink-500" + break + case "red": + colorClass = "focus:border-crimson-500 focus:ring-crimson-500" + break + default: + colorClass = "focus:border-pink-500 focus:ring-pink-500" + } + return (