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/schema.prisma b/db/schema.prisma index 14a9d5809..d0a02b1d9 100644 --- a/db/schema.prisma +++ b/db/schema.prisma @@ -372,7 +372,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/package-lock.json b/package-lock.json index f1da96d6d..94c1cf7f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" @@ -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", @@ -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", @@ -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": { @@ -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", @@ -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": { @@ -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..ad76c2a15 100644 --- a/package.json +++ b/package.json @@ -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/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/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]/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}

- - - - - - ) -} - -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/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/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/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/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..fb71085d5 --- /dev/null +++ b/src/survey-public/components/Email.tsx @@ -0,0 +1,44 @@ +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 { 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, title, mailjetWidgetUrl } = email + + useEffect(() => { + iframeResizer({}, "#mailjet-widget") + }, []) + + return ( +
+ + {questionText.de} + {description.de} + +
+ `, + }} + /> + +
+ + {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 67% rename from src/participation/components/survey/Question.tsx rename to src/survey-public/components/Question.tsx index 9f427a97f..3f7baa5fd 100644 --- a/src/participation/components/survey/Question.tsx +++ b/src/survey-public/components/Question.tsx @@ -1,20 +1,19 @@ -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}`, @@ -29,7 +28,7 @@ const MultipleResponseComponent: React.FC id, responses, }) => ( - ({ name: `multi-${id}-${item.id}`, @@ -41,14 +40,13 @@ const MultipleResponseComponent: React.FC type TTextResponseComponentProps = { id: number -} & TextResponseProps +} const TextResponseComponent: React.FC = ({ id }) => ( <> - + ) - // TODO type const CustomComponent = (props: any) => (
@@ -72,7 +70,7 @@ export const Question: React.FC = ({ question, className }) => { const Component = components[component] || null return (
- {label.de} + {label.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..96abc5277 --- /dev/null +++ b/src/survey-public/components/SurveyInactivePage.tsx @@ -0,0 +1,22 @@ +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 63% rename from src/participation/components/rs8.tsx rename to src/survey-public/components/SurveyMainPage.tsx index da67ed4bd..85e295c20 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,23 +134,23 @@ const ParticipationMainPage: BlitzPage = () => { break case "FEEDBACK": component = ( - + ) break case "EMAIL": - component = - break - case "DONE": - component = + component = break } return ( - + stage: {stage} @@ -162,10 +159,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/participation/components/core/buttons/ParticipationButton.tsx b/src/survey-public/components/core/buttons/SurveyButton.tsx similarity index 57% rename from src/participation/components/core/buttons/ParticipationButton.tsx rename to src/survey-public/components/core/buttons/SurveyButton.tsx index c38f062c1..f4d605f53 100644 --- a/src/participation/components/core/buttons/ParticipationButton.tsx +++ b/src/survey-public/components/core/buttons/SurveyButton.tsx @@ -1,6 +1,6 @@ import clsx from "clsx" import { ReactNode } from "react" -import { participationPinkButtonStyles, participationWhiteButtonStyles } from "../links/styles" +import { surveyPinkButtonStyles, surveyWhiteButtonStyles } from "../links/styles" export { FORM_ERROR } from "src/core/components/forms" type Props = { @@ -9,15 +9,10 @@ type Props = { children: string | ReactNode } & React.ButtonHTMLAttributes -export const ParticipationButton: React.FC = ({ - disabled, - color = "pink", - children, - ...props -}) => { +export const SurveyButton: React.FC = ({ disabled, color = "pink", children, ...props }) => { const buttonStyles = clsx( "px-12", - color === "white" ? participationWhiteButtonStyles : participationPinkButtonStyles, + color === "white" ? surveyWhiteButtonStyles : surveyPinkButtonStyles, ) 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..92a2ee160 --- /dev/null +++ b/src/survey-public/components/core/form/SurveyLabeledCheckbox.tsx @@ -0,0 +1,67 @@ +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"> +} + +export const SurveyLabeledCheckbox = forwardRef( + ({ name, label, help, outerProps, labelProps, ...props }, ref) => { + const { + register, + formState: { isSubmitting, errors }, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + 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..0df32bda6 --- /dev/null +++ b/src/survey-public/components/core/form/SurveyLabeledRadiobutton.tsx @@ -0,0 +1,69 @@ +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"> +} + +export const SurveyLabeledRadiobutton = forwardRef( + ({ 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/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 92% rename from src/participation/components/form/ParticipationLabeledTextareaField.tsx rename to src/survey-public/components/core/form/SurveyLabeledTextareaField.tsx index 7ee0a5bb3..517fcff97 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 @@ -15,9 +15,9 @@ export interface ParticipationLabeledTextareaProps optional?: boolean } -export const ParticipationLabeledTextareaField = forwardRef< +export const SurveyLabeledTextareaField = forwardRef< HTMLTextAreaElement, - ParticipationLabeledTextareaProps + SurveyLabeledTextareaProps >( ( { name, label, help, outerProps, labelProps, optional, className: textareaClasName, ...props }, diff --git a/src/participation/components/layout/ProgressBar.tsx b/src/survey-public/components/core/layout/ProgressBar.tsx similarity index 89% rename from src/participation/components/layout/ProgressBar.tsx rename to src/survey-public/components/core/layout/ProgressBar.tsx index 2365a0854..453d0c1e5 100644 --- a/src/participation/components/layout/ProgressBar.tsx +++ b/src/survey-public/components/core/layout/ProgressBar.tsx @@ -1,5 +1,5 @@ import { useContext } from "react" -import { ProgressContext } from "src/participation/context/contexts" +import { ProgressContext } from "src/survey-public/context/contexts" export { FORM_ERROR } from "src/core/components/forms" diff --git a/src/participation/components/layout/ContainerParticipation.tsx b/src/survey-public/components/core/layout/SurveyContainer.tsx similarity index 68% rename from src/participation/components/layout/ContainerParticipation.tsx rename to src/survey-public/components/core/layout/SurveyContainer.tsx index c9dcdba5e..6c2d96e81 100644 --- a/src/participation/components/layout/ContainerParticipation.tsx +++ b/src/survey-public/components/core/layout/SurveyContainer.tsx @@ -4,6 +4,6 @@ type Props = { children?: React.ReactNode } -export const ContainerParticipation: BlitzLayout = ({ children }) => { +export const SurveyContainer: BlitzLayout = ({ children }) => { return
{children}
} diff --git a/src/participation/components/layout/FooterParticipation.tsx b/src/survey-public/components/core/layout/SurveyFooter.tsx similarity index 61% rename from src/participation/components/layout/FooterParticipation.tsx rename to src/survey-public/components/core/layout/SurveyFooter.tsx index 98df39de1..a24bdf394 100644 --- a/src/participation/components/layout/FooterParticipation.tsx +++ b/src/survey-public/components/core/layout/SurveyFooter.tsx @@ -1,8 +1,8 @@ import React from "react" -import { FooterLinkList } from "../../../core/layouts/Footer/FooterLinkList" -import { links } from "../../../core/layouts/Footer/links.const" +import { FooterLinkList } from "../../../../core/layouts/Footer/FooterLinkList" +import { links } from "../../../../core/layouts/Footer/links.const" -export const FooterParticipation: React.FC = () => { +export const SurveyFooter: React.FC = () => { return (
diff --git a/src/participation/components/layout/HeaderParticipation.tsx b/src/survey-public/components/core/layout/SurveyHeader.tsx similarity index 65% rename from src/participation/components/layout/HeaderParticipation.tsx rename to src/survey-public/components/core/layout/SurveyHeader.tsx index 4070ea1ea..089a913f9 100644 --- a/src/participation/components/layout/HeaderParticipation.tsx +++ b/src/survey-public/components/core/layout/SurveyHeader.tsx @@ -1,13 +1,14 @@ 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" +import { ProgressBar } from "src/survey-public/components/core/layout/ProgressBar" +import { SurveyLink } from "../links/SurveyLink" type Props = { logoSrc: string + landingPageUrl: string } -export const HeaderParticipation: React.FC = ({ logoSrc }) => { +export const SurveyHeader: React.FC = ({ logoSrc, landingPageUrl }) => { return (
diff --git a/src/participation/components/layout/LayoutParticipation.tsx b/src/survey-public/components/core/layout/SurveyLayout.tsx similarity index 60% rename from src/participation/components/layout/LayoutParticipation.tsx rename to src/survey-public/components/core/layout/SurveyLayout.tsx index 8d89b7346..93ac14b4d 100644 --- a/src/participation/components/layout/LayoutParticipation.tsx +++ b/src/survey-public/components/core/layout/SurveyLayout.tsx @@ -1,18 +1,19 @@ 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" +import { SurveyMetaTags } from "../SurveyMetaTags" +import { SurveyContainer } from "./SurveyContainer" +import { SurveyFooter } from "./SurveyFooter" +import { SurveyHeader } from "./SurveyHeader" type Props = { logoUrl: string + children?: React.ReactNode - canonicalUrl?: string + canonicalUrl: string } -export const LayoutParticipation: BlitzLayout = ({ logoUrl, children, canonicalUrl }) => { +export const SurveyLayout: 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" }[ @@ -25,16 +26,16 @@ export const LayoutParticipation: BlitzLayout = ({ logoUrl, children, can {canonicalUrl && } - +
- +
- {children} + {children}
- + ) diff --git a/src/participation/components/layout/ScreenHeaderParticipation.tsx b/src/survey-public/components/core/layout/SurveyScreenHeader.tsx similarity index 57% rename from src/participation/components/layout/ScreenHeaderParticipation.tsx rename to src/survey-public/components/core/layout/SurveyScreenHeader.tsx index 99994f4f0..9e7bbfa35 100644 --- a/src/participation/components/layout/ScreenHeaderParticipation.tsx +++ b/src/survey-public/components/core/layout/SurveyScreenHeader.tsx @@ -1,11 +1,11 @@ -import { ParticipationH1 } from "../core/Text" +import { SurveyH1 } from "../Text" -export type ScreenHeaderParticipationProps = { +export type SurveyScreenHeaderProps = { title: string description?: string } -export const ScreenHeaderParticipation: React.FC = ({ +export const SurveyScreenHeader: React.FC = ({ title, description, @@ -13,7 +13,7 @@ export const ScreenHeaderParticipation: React.FC return (
- {title} + {title}
{Boolean(description) && ( diff --git a/src/participation/components/survey/ParticipationSpinnerLayover.tsx b/src/survey-public/components/core/layout/SurveySpinnerLayover.tsx similarity index 96% rename from src/participation/components/survey/ParticipationSpinnerLayover.tsx rename to src/survey-public/components/core/layout/SurveySpinnerLayover.tsx index 049c25d7d..9d138acc6 100644 --- a/src/participation/components/survey/ParticipationSpinnerLayover.tsx +++ b/src/survey-public/components/core/layout/SurveySpinnerLayover.tsx @@ -1,6 +1,6 @@ import clsx from "clsx" -export const ParticipationSpinnerLayover: React.FC = () => { +export const SurveySpinnerLayover: React.FC = () => { return (
diff --git a/src/survey-public/components/core/links/SurveyLink.tsx b/src/survey-public/components/core/links/SurveyLink.tsx new file mode 100644 index 000000000..9a10f4138 --- /dev/null +++ b/src/survey-public/components/core/links/SurveyLink.tsx @@ -0,0 +1,52 @@ +import { RouteUrlObject } from "blitz" +import clsx from "clsx" +import NextLink from "next/link" +import { forwardRef } from "react" +import { selectSurveyLinkStyle } from "./styles" + +// the link component is duplicated to avoid dependencies between TS and 'Beteiligung' + +export type SurveyLinkProps = { + href: RouteUrlObject | string + className?: string + classNameOverwrites?: string + /** @default `false` */ + blank?: boolean + /** @desc Style Link as Button */ + button?: true | "white" | "pink" | "blue" + children: React.ReactNode +} & Omit, "href"> + +export const SurveyLink: React.FC = forwardRef( + ({ href, className, classNameOverwrites, children, blank = false, button, ...props }, ref) => { + const classNames = clsx(classNameOverwrites ?? selectSurveyLinkStyle(button, className)) + + // external link + if (typeof href === "string") { + return ( + + {children} + + ) + } + + return ( + + {children} + + ) + }, +) diff --git a/src/participation/components/core/links/styles.ts b/src/survey-public/components/core/links/styles.ts similarity index 87% rename from src/participation/components/core/links/styles.ts rename to src/survey-public/components/core/links/styles.ts index 51368488f..39149b136 100644 --- a/src/participation/components/core/links/styles.ts +++ b/src/survey-public/components/core/links/styles.ts @@ -1,5 +1,5 @@ import clsx from "clsx" -import { ParticipationLinkProps } from "./ParticipationLink" +import { SurveyLinkProps } from "./SurveyLink" // BUTTON: const buttonBase = @@ -32,7 +32,7 @@ const whiteButtonStylesForLinkElement = clsx( activeStyleWhiteLinkElement, ) // for button elements -export const participationWhiteButtonStyles = clsx( +export const surveyWhiteButtonStyles = clsx( buttonBase, "enabled:bg-white enabled:ring-1 enabled:ring-gray-400", "disabled:text-gray-400 disabled:bg-white disabled:ring-1 disabled:ring-gray-200", @@ -48,17 +48,14 @@ const pinkButtonStylesForLinkElement = clsx( activeStylePinkLinkElement, ) // for button elements -export const participationPinkButtonStyles = clsx( +export const surveyPinkButtonStyles = clsx( buttonBase, "enabled:text-white enabled:bg-pink-500", "disabled:bg-pink-100 disabled:text-white", activeStylePinkButtonElement, ) -export const selectParticipationLinkStyle = ( - button: ParticipationLinkProps["button"], - className?: string, -) => { +export const selectSurveyLinkStyle = (button: SurveyLinkProps["button"], className?: string) => { switch (button) { case true: return clsx(pinkButtonStylesForLinkElement, className) @@ -66,6 +63,9 @@ export const selectParticipationLinkStyle = ( return clsx(whiteButtonStylesForLinkElement, className) case "pink": return clsx(pinkButtonStylesForLinkElement, className) + // todo frm7 + case "blue": + return clsx(pinkButtonStylesForLinkElement, className) default: return clsx(partcipationLinkStyles, className) } diff --git a/src/participation/components/feedback/Feedback.tsx b/src/survey-public/components/feedback/Feedback.tsx similarity index 59% rename from src/participation/components/feedback/Feedback.tsx rename to src/survey-public/components/feedback/Feedback.tsx index 37d25e6a5..d1b0d5568 100644 --- a/src/participation/components/feedback/Feedback.tsx +++ b/src/survey-public/components/feedback/Feedback.tsx @@ -1,19 +1,34 @@ import { useCallback, useContext, useState } from "react" -import { stageProgressDefinition } from "src/participation/components/rs8" -import { PinContext, ProgressContext } from "src/participation/context/contexts" -import SurveyForm from "../form/SurveyForm" + +import { PinContext, ProgressContext } from "src/survey-public/context/contexts" +import { scrollToTopWithDelay } from "src/survey-public/utils/scrollToTopWithDelay" +import PublicSurveyForm from "../core/form/PublicSurveyForm" +import { + TFeedback, + TMapProps, + TPage, + TProgress, + TResponseConfig, + TSingleOrMultiResponseProps, +} from "../types" import { FeedbackFirstPage } from "./FeedbackFirstPage" import { FeedbackSecondPage } from "./FeedbackSecondPage" -import { scrollToTopWithDelay } from "src/participation/utils/scrollToTopWithDelay" export { FORM_ERROR } from "src/core/components/forms" type Props = { onSubmit: any - feedback: any // TODO + feedback: TFeedback + stageProgressDefinition: TProgress + responseConfig: TResponseConfig } -export const Feedback: React.FC = ({ onSubmit, feedback }) => { +export const Feedback: React.FC = ({ + onSubmit, + feedback, + stageProgressDefinition, + responseConfig, +}) => { const { setProgress } = useContext(ProgressContext) const [pinPosition, setPinPosition] = useState(null) const [values, setValues] = useState({}) @@ -21,20 +36,30 @@ export const Feedback: React.FC = ({ onSubmit, feedback }) => { const [isPageTwoCompleted, setIsPageTwoCompleted] = useState(false) const [isMapDirty, setIsMapDirty] = useState(false) const [feedbackPageProgress, setFeedbackPageProgress] = useState(0) - const [feedbackCategory, setFeedbackCategory] = useState(6) // default: 6 / "Sonstiges" + const [feedbackCategory, setFeedbackCategory] = useState(1) const [isMap, setIsMap] = useState(false) const { pages } = feedback - const projectGeometry = feedback.pages[0].questions[2].props.projectGeometry - const layerStyles = feedback.pages[0].questions[2].props.layerStyles + const { evaluationRefs } = responseConfig + + const pinId = evaluationRefs["feedback-location"] as number + const isUserLocationQuestionId = evaluationRefs["is-feedback-location"] + const feedbackCategoryId = evaluationRefs["feedback-category"] + const userText1Id = evaluationRefs["feedback-usertext-1"] + const userText2Id = evaluationRefs["feedback-usertext-2"] + const categoryId = evaluationRefs["feedback-category"] + + const categoryProps = pages[0]?.questions.find((q) => q.id === categoryId) + ?.props as TSingleOrMultiResponseProps - const pinId = pages[0].questions.find( - (question: Record) => question.component === "map", - ).id + const categories = categoryProps.responses - const categories = pages[0].questions[0].props.responses + const { maptilerStyleUrl, config } = pages[0]?.questions.find((q) => q.id === pinId) + ?.props as TMapProps + + const categoryText = categories.find((q) => q.id === feedbackCategory)?.text.de const handleNextPage = () => { const newFeedbackPageProgress = Math.min(pages.length, feedbackPageProgress + 1) @@ -73,7 +98,7 @@ export const Feedback: React.FC = ({ onSubmit, feedback }) => { const handleSubmit = (values: Record, submitterId?: string) => { values = transformValues(values) - delete values["22"] // delete map ja/nein response + delete values[isUserLocationQuestionId!] // delete map ja/nein response onSubmit({ ...values, [pinId]: isMap ? pinPosition : null }, submitterId) } @@ -82,26 +107,32 @@ export const Feedback: React.FC = ({ onSubmit, feedback }) => { (values: Record) => { setValues(values) values = transformValues(values) - const isMapOption = values["22"] === 1 // "1" -> yes, "2" -> no - see feedback.json + const isMapOption = values[isUserLocationQuestionId!] === 1 // "1" -> yes, "2" -> no - see feedback.json setIsMap(isMapOption) - const isQuestionsCompletedPageOne = values["21"] && values["22"] + const isQuestionsCompletedPageOne = + values[feedbackCategoryId!] && values[isUserLocationQuestionId!] setIsPageOneCompleted( !isMapOption // if user did not choose map ? isQuestionsCompletedPageOne // page is completed in case both questions are answered : isQuestionsCompletedPageOne && isMapDirty, // page is completed in case both questions are answered and user has touched the map marker ) - const isMinimumOneQuestionPageTwo = values["34"] || values["35"] + let isMinimumOneQuestionPageTwo: boolean + if (!userText2Id) { + isMinimumOneQuestionPageTwo = Boolean(values[userText1Id!]) + } else { + isMinimumOneQuestionPageTwo = Boolean(values[userText1Id!] || values[userText2Id]) + } setIsPageTwoCompleted(isMinimumOneQuestionPageTwo) if (!isMapOption) setPinPosition(null) // set pinPosition to null if not yes - setFeedbackCategory(values["21"] || categories.length) // sets state to response id of chosen category (question 21) // fallback: '"Sonstiges" + setFeedbackCategory(values[feedbackCategoryId!] || 1) // sets state to response id of chosen category or 1 if no category is chosen }, - [categories.length, isMapDirty], + [feedbackCategoryId, isMapDirty, isUserLocationQuestionId, userText1Id, userText2Id], ) return ( // @ts-ignore - + {feedbackPageProgress === 0 && ( = ({ onSubmit, feedback }) => { {feedbackPageProgress === 1 && ( )} - + ) } diff --git a/src/participation/components/feedback/FeedbackFirstPage.tsx b/src/survey-public/components/feedback/FeedbackFirstPage.tsx similarity index 56% rename from src/participation/components/feedback/FeedbackFirstPage.tsx rename to src/survey-public/components/feedback/FeedbackFirstPage.tsx index 3881140a2..a7c38d303 100644 --- a/src/participation/components/feedback/FeedbackFirstPage.tsx +++ b/src/survey-public/components/feedback/FeedbackFirstPage.tsx @@ -1,9 +1,9 @@ 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" +import { SurveyButton } from "../core/buttons/SurveyButton" +import { SurveyScreenHeader } from "../core/layout/SurveyScreenHeader" +import { SurveyMap } from "../maps/SurveyMap" +import { Question } from "../Question" +import { SurveyButtonWrapper } from "../core/buttons/SurveyButtonWrapper" export { FORM_ERROR } from "src/core/components/forms" @@ -30,17 +30,16 @@ export const FeedbackFirstPage: React.FC = ({ return ( <> - + {isMap && ( - = ({ )} {/* TODO Disabled */} - - + + {buttons[0].label.de} - - + + ) } diff --git a/src/survey-public/components/feedback/FeedbackSecondPage.tsx b/src/survey-public/components/feedback/FeedbackSecondPage.tsx new file mode 100644 index 000000000..90f25f49e --- /dev/null +++ b/src/survey-public/components/feedback/FeedbackSecondPage.tsx @@ -0,0 +1,69 @@ +import { useContext } from "react" +import { PinContext } from "src/survey-public/context/contexts" +import { SurveyButton } from "../core/buttons/SurveyButton" +import { SurveyButtonWrapper } from "../core/buttons/SurveyButtonWrapper" + +import { SurveyScreenHeader } from "src/survey-public/components/core/layout/SurveyScreenHeader" +import { TPage, TQuestion } from "src/survey-public/components/types" +import { Question } from "../Question" +import { SurveyH2, SurveyH3, SurveyP } from "../core/Text" +import { SurveyStaticMap } from "../maps/SurveyStaticMap" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + page: TPage + onButtonClick: any + staticMapProps: { + maptilerStyleUrl: string + pinColor: string + } + feedbackCategory: string | undefined + isCompleted: boolean + userTextIndices: (number | undefined)[] +} + +export const FeedbackSecondPage: React.FC = ({ + page, + isCompleted, + onButtonClick, + staticMapProps, + feedbackCategory, + userTextIndices, +}) => { + const { pinPosition } = useContext(PinContext) + const { title, description, questions, buttons } = page + + return ( + <> + + {questions![0]!.label.de} + {feedbackCategory} + + {pinPosition && ( + <> + {questions![1]!.label.de} + + + )} +
+ {userTextIndices.map((questionId) => { + const q = questions!.find((q: TQuestion) => q.id === questionId) + if (q) return + })} +
+ + + + {buttons![0]!.label.de} + + + {buttons![1]!.label.de} + + + + Zurück + + + ) +} diff --git a/src/participation/components/maps/ParticipationBackgroundSwitcher.tsx b/src/survey-public/components/maps/SurveyBackgroundSwitcher.tsx similarity index 96% rename from src/participation/components/maps/ParticipationBackgroundSwitcher.tsx rename to src/survey-public/components/maps/SurveyBackgroundSwitcher.tsx index cf52c0548..e4c425f9a 100644 --- a/src/participation/components/maps/ParticipationBackgroundSwitcher.tsx +++ b/src/survey-public/components/maps/SurveyBackgroundSwitcher.tsx @@ -16,11 +16,7 @@ type Props = { className: string } -export const ParticipationBackgroundSwitcher: React.FC = ({ - value, - onChange, - className, -}) => { +export const SurveyBackgroundSwitcher: React.FC = ({ value, onChange, className }) => { return (
diff --git a/src/participation/components/maps/ParticipationMap.tsx b/src/survey-public/components/maps/SurveyMap.tsx similarity index 74% rename from src/participation/components/maps/ParticipationMap.tsx rename to src/survey-public/components/maps/SurveyMap.tsx index 34f8c9324..483bb390d 100644 --- a/src/participation/components/maps/ParticipationMap.tsx +++ b/src/survey-public/components/maps/SurveyMap.tsx @@ -1,47 +1,36 @@ -import { MultiLineString } from "@turf/helpers" import clsx from "clsx" +import maplibregl from "maplibre-gl" import "maplibre-gl/dist/maplibre-gl.css" import React, { useCallback, useContext, useEffect, useState } from "react" import Map, { - Layer, LngLat, LngLatBoundsLike, Marker, MarkerDragEvent, NavigationControl, - Source, useMap, } from "react-map-gl/maplibre" import { LayerType } from "src/core/components/Map/BackgroundSwitcher" -import { PinContext } from "src/participation/context/contexts" -import { MapBanner } from "./MapBanner" -import { ParticipationBackgroundSwitcher } from "./ParticipationBackgroundSwitcher" -import Pin from "./Pin" +import { SurveyBackgroundSwitcher } from "src/survey-public/components/maps/SurveyBackgroundSwitcher" +import { SurveyMapBanner } from "src/survey-public/components/maps/SurveyMapBanner" +import SurveyPin from "src/survey-public/components/maps/SurveyPin" +import { PinContext } from "src/survey-public/context/contexts" -export type ParticipationMapProps = { +export type SurveyMapProps = { className?: string children?: React.ReactNode projectMap: { - projectGeometry: MultiLineString - layerStyles: Record + maptilerStyleUrl: string initialMarker: { lng: number; lat: number } config: { bounds: LngLatBoundsLike - minZoom?: number - maxZoom?: number + pinColor: string } } - isMapDirty: any setIsMapDirty: any } -export const ParticipationMap: React.FC = ({ - projectMap, - className, - children, - isMapDirty, - setIsMapDirty, -}) => { +export const SurveyMap: React.FC = ({ projectMap, className, setIsMapDirty }) => { const { mainMap } = useMap() const [events, logEvents] = useState>({}) const [isPinInView, setIsPinInView] = useState(true) @@ -56,14 +45,11 @@ export const ParticipationMap: React.FC = ({ const { pinPosition, setPinPosition } = useContext(PinContext) const maptilerApiKey = "ECOoUBmpqklzSCASXxcu" - const vectorStyle = `https://api.maptiler.com/maps/a4824657-3edd-4fbd-925e-1af40ab06e9c/style.json?key=${maptilerApiKey}` - const satelliteStyle = `https://api.maptiler.com/maps/hybrid/style.json?key=${maptilerApiKey}` - if (!pinPosition) setPinPosition(projectMap.initialMarker) + const vectorStyle = `${projectMap.maptilerStyleUrl}?key=${maptilerApiKey}` + const satelliteStyle = `${"https://api.maptiler.com/maps/hybrid/style.json"}?key=${maptilerApiKey}` - useEffect(() => { - if (!mainMap) return - }, [mainMap]) + if (!pinPosition) setPinPosition(projectMap.initialMarker) useEffect(() => { const lgMediaQuery = window.matchMedia("(min-width: 768px)") @@ -134,8 +120,10 @@ export const ParticipationMap: React.FC = ({ onMove={handleMapMove} mapStyle={selectedLayer === "vector" ? vectorStyle : satelliteStyle} onZoom={handleMapZoom} + mapLib={maplibregl} + // @ts-expect-error: See https://github.com/visgl/react-map-gl/issues/2310 + RTLTextPlugin={null} > - {children} {pinPosition && ( = ({ onDrag={onMarkerDrag} onDragEnd={onMarkerDragEnd} > - - - {projectMap.layerStyles.map((layer: any) => { - return - })} - + )} + {isMediumScreen && } - - void } -export const MapBanner: React.FC = ({ status, className, action }) => { +export const SurveyMapBanner: React.FC = ({ status, className, action }) => { switch (status) { case "default": return ( diff --git a/src/participation/components/maps/Pin.tsx b/src/survey-public/components/maps/SurveyPin.tsx similarity index 83% rename from src/participation/components/maps/Pin.tsx rename to src/survey-public/components/maps/SurveyPin.tsx index 9b425fd35..05f5e2a0f 100644 --- a/src/participation/components/maps/Pin.tsx +++ b/src/survey-public/components/maps/SurveyPin.tsx @@ -1,17 +1,17 @@ import * as React from "react" - -const Pin = () => { +type Props = { color: string } +const SurveyPin: React.FC = ({ color }) => { return ( ) } -export default React.memo(Pin) +export default React.memo(SurveyPin) diff --git a/src/survey-public/components/maps/SurveyStaticMap.tsx b/src/survey-public/components/maps/SurveyStaticMap.tsx new file mode 100644 index 000000000..71adcf23b --- /dev/null +++ b/src/survey-public/components/maps/SurveyStaticMap.tsx @@ -0,0 +1,49 @@ +import "maplibre-gl/dist/maplibre-gl.css" +import React from "react" +import Map, { Marker, useMap } from "react-map-gl/maplibre" +import SurveyStaticPin from "./SurveyStaticPin" + +type Props = { + marker: { lng: number; lat: number } + maptilerStyleUrl: string + pinColor: string +} + +export const SurveyStaticMap: React.FC = ({ marker, maptilerStyleUrl, pinColor }) => { + const { mainMap } = useMap() + const maptilerApiKey = "ECOoUBmpqklzSCASXxcu" + const vectorStyle = `${maptilerStyleUrl}?key=${maptilerApiKey}` + + return ( +
+ + + + + +
+ ) +} diff --git a/src/survey-public/components/maps/SurveyStaticPin.tsx b/src/survey-public/components/maps/SurveyStaticPin.tsx new file mode 100644 index 000000000..387a69a42 --- /dev/null +++ b/src/survey-public/components/maps/SurveyStaticPin.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import { string } from "zod" +type Props = { + color: string +} + +const SurveyStaticPin: React.FC = ({ color }) => { + return ( + + + + + ) +} + +export default React.memo(SurveyStaticPin) diff --git a/src/survey-public/components/types.ts b/src/survey-public/components/types.ts new file mode 100644 index 000000000..d681a5ed7 --- /dev/null +++ b/src/survey-public/components/types.ts @@ -0,0 +1,111 @@ +import { LngLatBoundsLike } from "react-map-gl/maplibre" + +export type TSurvey = { + part: number + version: number + logoUrl: string + canonicalUrl: string + pages: TPage[] +} + +export type TPage = { + id: number + title: TTranslatableText + description: TTranslatableText + questions?: TQuestion[] + buttons: TButtonWithAction[] +} + +export type TQuestion = { + id: number + component: "singleResponse" | "multipleResponse" + label: TTranslatableText + props: TSingleOrMultiResponseProps +} + +export type TButtonWithAction = { + label: TTranslatableText + color: TColor + onClick: { + action: "nextPage" | "previousPage" | "submit" + } +} + +type TButton = { + label: TTranslatableText + color: TColor +} + +type TColor = "white" | "pink" | "blue" + +type TTranslatableText = { + de: string +} + +export type TSingleOrMultiResponseProps = { + responses: TResponse[] +} + +export type TResponse = { + id: number + text: TTranslatableText + help?: TTranslatableText +} + +export type TEmail = { + title: TTranslatableText + questionText: TTranslatableText + description: TTranslatableText + mailjetWidgetUrl: string + button: TButton +} + +export type TMore = { + title: TTranslatableText + description: TTranslatableText + questionText: TTranslatableText + buttons: TButtonWithAction[] +} + +export type TMapProps = { + maptilerStyleUrl: string + marker?: { + lat: number + lng: number + } + config: { + bounds: LngLatBoundsLike + pinColor: string + } + placeholder?: TTranslatableText + caption?: TTranslatableText +} + +type TTextProps = { + placeholder?: TTranslatableText + caption?: TTranslatableText +} + +export type TFeedbackQuestion = { + id: number + label: TTranslatableText + component: "singleResponse" | "multipleResponse" | "text" | "map" | "custom" + props?: TSingleOrMultiResponseProps | TMapProps | TTextProps +} + +export type TFeedback = { + part: number + pages: { + id: number + title: TTranslatableText + description: TTranslatableText + questions: TFeedbackQuestion[] + buttons: TButtonWithAction[] + }[] +} + +export type TResponseConfig = { + evaluationRefs: Record +} + +export type TProgress = Record<"SURVEY" | "MORE" | "FEEDBACK" | "EMAIL", number> diff --git a/src/participation/context/contexts.ts b/src/survey-public/context/contexts.ts similarity index 100% rename from src/participation/context/contexts.ts rename to src/survey-public/context/contexts.ts diff --git a/src/survey-public/frm7/SurveyFRM7.tsx b/src/survey-public/frm7/SurveyFRM7.tsx new file mode 100644 index 000000000..3fbef0caa --- /dev/null +++ b/src/survey-public/frm7/SurveyFRM7.tsx @@ -0,0 +1,23 @@ +import { SurveyMainPage } from "src/survey-public/components/SurveyMainPage" +import { emailDefinition } from "src/survey-public/frm7/data/email" +import { feedbackDefinition } from "src/survey-public/frm7/data/feedback" +import { moreDefinition } from "src/survey-public/frm7/data/more" +import { stageProgressDefinition } from "src/survey-public/frm7/data/progress" +import { surveyDefinition } from "src/survey-public/frm7/data/survey" +import { responseConfig } from "./data/response-config" +type Props = { + surveyId: number +} +export const SurveyFRM7: React.FC = ({ surveyId }) => { + return ( + + ) +} diff --git a/src/survey-public/frm7/data/email.ts b/src/survey-public/frm7/data/email.ts new file mode 100644 index 000000000..07926c078 --- /dev/null +++ b/src/survey-public/frm7/data/email.ts @@ -0,0 +1,14 @@ +import { TEmail } from "src/survey-public/components/types" + +export const emailDefinition: TEmail = { + title: { de: "Vielen Dank für Ihre Teilnahme" }, + questionText: { de: "Was passiert jetzt?" }, + description: { + 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.", + }, + mailjetWidgetUrl: "https://7p8q.mjt.lu/wgt/7p8q/t5g/form?c=f8dcc5f9", + button: { + label: { de: "Zurück zur Startseite" }, + color: "white", + }, +} diff --git a/src/survey-public/frm7/data/feedback.ts b/src/survey-public/frm7/data/feedback.ts new file mode 100644 index 000000000..e9269f76b --- /dev/null +++ b/src/survey-public/frm7/data/feedback.ts @@ -0,0 +1,113 @@ +import { TFeedback } from "src/survey-public/components/types" + +export const feedbackDefinition: TFeedback = { + part: 2, + pages: [ + { + id: 1, + title: { de: "Wir sind gespannt auf Ihre Anmerkungen. FRM7" }, + 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: 7, text: { de: "Mögliche Konflikte FRM7" } }, + { id: 8, text: { de: "Noch ein Thema" } }, + { 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: { + maptilerStyleUrl: + "https://api.maptiler.com/maps/a9cd44e7-43f6-4277-8ae0-d910f8162524/style.json", + marker: { + lat: 50.13810478491584, + lng: 8.774882307368216, + }, + config: { + bounds: [8.576990015191768, 50.18115285304344, 8.791982825789262, 50.084578531515405], + pinColor: "#E5007D", + // todo frm7 + }, + }, + }, + ], + 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" }, + }, + }, + ], + 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/survey-public/frm7/data/more.ts b/src/survey-public/frm7/data/more.ts new file mode 100644 index 000000000..9302ccec7 --- /dev/null +++ b/src/survey-public/frm7/data/more.ts @@ -0,0 +1,21 @@ +import { TMore } from "src/survey-public/components/types" + +export const moreDefinition: TMore = { + 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.", + }, + questionText: { de: "Haben Sie noch konkrete Hinweise zu Themen vor Ort?" }, + buttons: [ + { + label: { de: "Ja, ich habe noch Hinweise" }, + color: "blue", + onClick: { action: "nextPage" }, + }, + { + label: { de: "Nein, ich möchte die Umfrage beenden" }, + color: "white", + onClick: { action: "submit" }, + }, + ], +} diff --git a/src/survey-public/frm7/data/progress.ts b/src/survey-public/frm7/data/progress.ts new file mode 100644 index 000000000..f24bd594c --- /dev/null +++ b/src/survey-public/frm7/data/progress.ts @@ -0,0 +1,10 @@ +// For Progressbar: stage and associated arbitrarily set status of the progressbar + +import { TProgress } from "src/survey-public/components/types" + +export const stageProgressDefinition: TProgress = { + SURVEY: 1, + MORE: 5, + FEEDBACK: 6, + EMAIL: 8, +} diff --git a/src/survey-public/frm7/data/response-config.ts b/src/survey-public/frm7/data/response-config.ts new file mode 100644 index 000000000..62ec523b7 --- /dev/null +++ b/src/survey-public/frm7/data/response-config.ts @@ -0,0 +1,12 @@ +import { TResponseConfig } from "../../components/types" + +// todo frm7 update when definitions are final for frm7 + +export const responseConfig: TResponseConfig = { + evaluationRefs: { + "feedback-category": 21, + "is-feedback-location": 22, + "feedback-location": 23, + "feedback-usertext-1": 34, + }, +} diff --git a/src/survey-public/frm7/data/survey.ts b/src/survey-public/frm7/data/survey.ts new file mode 100644 index 000000000..873d32bab --- /dev/null +++ b/src/survey-public/frm7/data/survey.ts @@ -0,0 +1,186 @@ +import { TSurvey } from "src/survey-public/components/types" + +export const surveyDefinition: TSurvey = { + part: 1, + version: 1, + logoUrl: "https://radschnellweg8-lb-wn.de/logo.png", + canonicalUrl: "https://frmtestetstest7.de/beteiligung/", + pages: [ + { + id: 1, + title: { de: "Ihre Meinung ist gefragt FRM7 lalal" }, + 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 FRM 7 zum Erfolgsprojekt zu machen!\n\nDie Bürgerbeteiligung läuft noch bis zum 20.01.2024. Die Beantwortung dauert ca. 5-10 Minuten.", + }, + buttons: [ + { + label: { de: "Beteiligung starten" }, + color: "blue", + onClick: { action: "nextPage" }, + }, + ], + }, + { + id: 2, + title: { de: "Nutzung FRM7" }, + description: { + de: "Zuerst möchten wir Ihnen einige Fragen zur Nutzung des FRM7 Ludwigsburg–Waiblingen stellen.", + }, + questions: [ + { + id: 1, + label: { de: "Würden Sie den FRM7 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 FRM7 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 FRM7 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 FRM7 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 FRM7 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: 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 FRM7 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/survey-public/rs8/SurveyRS8.tsx b/src/survey-public/rs8/SurveyRS8.tsx new file mode 100644 index 000000000..7fb5d26dd --- /dev/null +++ b/src/survey-public/rs8/SurveyRS8.tsx @@ -0,0 +1,23 @@ +import { SurveyMainPage } from "src/survey-public/components/SurveyMainPage" +import { emailDefinition } from "src/survey-public/rs8/data/email" +import { feedbackDefinition } from "src/survey-public/rs8/data/feedback" +import { moreDefinition } from "src/survey-public/rs8/data/more" +import { stageProgressDefinition } from "src/survey-public/rs8/data/progress" +import { surveyDefinition } from "src/survey-public/rs8/data/survey" +import { responseConfig } from "./data/response-config" +type Props = { + surveyId: number +} +export const SurveyRS8: React.FC = ({ surveyId }) => { + return ( + + ) +} diff --git a/src/survey-public/rs8/data/email.ts b/src/survey-public/rs8/data/email.ts new file mode 100644 index 000000000..a6c9ddb14 --- /dev/null +++ b/src/survey-public/rs8/data/email.ts @@ -0,0 +1,14 @@ +import { TEmail } from "../../components/types" + +export const emailDefinition: TEmail = { + title: { de: "Vielen Dank für Ihre Teilnahme" }, + questionText: { de: "Was passiert jetzt?" }, + description: { + 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.", + }, + mailjetWidgetUrl: "https://7p8q.mjt.lu/wgt/7p8q/t5g/form?c=f8dcc5f9", + button: { + label: { de: "Zurück zur Startseite" }, + color: "white", + }, +} diff --git a/src/survey-public/rs8/data/feedback.ts b/src/survey-public/rs8/data/feedback.ts new file mode 100644 index 000000000..78ea0baf2 --- /dev/null +++ b/src/survey-public/rs8/data/feedback.ts @@ -0,0 +1,126 @@ +import { TFeedback } from "../../components/types" + +// todo refs readme + +export const feedbackDefinition: TFeedback = { + part: 2, + 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: { + maptilerStyleUrl: + "https://api.maptiler.com/maps/b09268b1-91d0-42e2-9518-321a1a94738f/style.json", + marker: { + lat: 48.87405710508672, + lng: 9.271044583540359, + }, + config: { + bounds: [9.387312714501604, 48.90390202531458, 9.103949029818097, 48.81629635563661], + pinColor: "#E5007D", + }, + }, + }, + ], + 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/survey-public/rs8/data/more.ts b/src/survey-public/rs8/data/more.ts new file mode 100644 index 000000000..6692d48d0 --- /dev/null +++ b/src/survey-public/rs8/data/more.ts @@ -0,0 +1,21 @@ +import { TMore } from "../../components/types" + +export const moreDefinition: TMore = { + 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.", + }, + questionText: { 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/survey-public/rs8/data/progress.ts b/src/survey-public/rs8/data/progress.ts new file mode 100644 index 000000000..cde0ea8f5 --- /dev/null +++ b/src/survey-public/rs8/data/progress.ts @@ -0,0 +1,10 @@ +// For Progressbar: stage and associated arbitrarily set status of the progressbar + +import { TProgress } from "../../components/types" + +export const stageProgressDefinition: TProgress = { + SURVEY: 1, + MORE: 5, + FEEDBACK: 6, + EMAIL: 8, +} diff --git a/src/survey-public/rs8/data/response-config.ts b/src/survey-public/rs8/data/response-config.ts new file mode 100644 index 000000000..6da4e2393 --- /dev/null +++ b/src/survey-public/rs8/data/response-config.ts @@ -0,0 +1,11 @@ +import { TResponseConfig } from "../../components/types" + +export const responseConfig: TResponseConfig = { + evaluationRefs: { + "feedback-category": 21, + "is-feedback-location": 22, + "feedback-location": 23, + "feedback-usertext-1": 34, + "feedback-usertext-2": 35, + }, +} diff --git a/src/survey-public/rs8/data/survey.ts b/src/survey-public/rs8/data/survey.ts new file mode 100644 index 000000000..bab8c2e3b --- /dev/null +++ b/src/survey-public/rs8/data/survey.ts @@ -0,0 +1,237 @@ +import { TSurvey } from "../../components/types" + +export const surveyDefinition: TSurvey = { + part: 1, + 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/survey-public/utils/getConfigBySurveySlug.ts b/src/survey-public/utils/getConfigBySurveySlug.ts new file mode 100644 index 000000000..1b812f98f --- /dev/null +++ b/src/survey-public/utils/getConfigBySurveySlug.ts @@ -0,0 +1,40 @@ +import { responseConfig as FRM7ResponseConfig } from "src/survey-public/frm7/data/response-config" +import { responseConfig as RS8ResponseConfig } from "src/survey-public/rs8/data/response-config" + +import { feedbackDefinition as feedbackDefinitionFRM7 } from "src/survey-public/frm7/data/feedback" +import { feedbackDefinition as feedbackDefinitionRS8 } from "src/survey-public/rs8/data/feedback" + +import { surveyDefinition as surveyDefinitionFRM7 } from "src/survey-public/frm7/data/survey" +import { surveyDefinition as surveyDefinitionRS8 } from "src/survey-public/rs8/data/survey" + +export const getResponseConfigBySurveySlug = (slug: string) => { + switch (slug) { + case "rs8": + return RS8ResponseConfig + case "frm7": + return FRM7ResponseConfig + default: + return RS8ResponseConfig + } +} + +export const getFeedbackDefinitionBySurveySlug = (slug: string) => { + switch (slug) { + case "rs8": + return feedbackDefinitionRS8 + case "frm7": + return feedbackDefinitionFRM7 + default: + return feedbackDefinitionRS8 + } +} +export const getSurveyDefinitionBySurveySlug = (slug: string) => { + switch (slug) { + case "rs8": + return surveyDefinitionRS8 + case "frm7": + return surveyDefinitionFRM7 + default: + return surveyDefinitionRS8 + } +} diff --git a/src/participation/utils/scrollToTopWithDelay.ts b/src/survey-public/utils/scrollToTopWithDelay.ts similarity index 100% rename from src/participation/utils/scrollToTopWithDelay.ts rename to src/survey-public/utils/scrollToTopWithDelay.ts diff --git a/src/survey-response-topics-on-survey-responses/queries/getSurveyResponseTopicsOnSurveyResponsesBySurveyResponse.ts b/src/survey-response-topics-on-survey-responses/queries/getSurveyResponseTopicsOnSurveyResponsesBySurveyResponse.ts index 3e2082ac2..44472b738 100644 --- a/src/survey-response-topics-on-survey-responses/queries/getSurveyResponseTopicsOnSurveyResponsesBySurveyResponse.ts +++ b/src/survey-response-topics-on-survey-responses/queries/getSurveyResponseTopicsOnSurveyResponsesBySurveyResponse.ts @@ -1,7 +1,6 @@ import { resolver } from "@blitzjs/rpc" import { paginate } from "blitz" import db, { Prisma } from "db" -import { SurveyResponse } from "src/pages/[projectSlug]/surveys/[surveyId]/responses" type GetSurveyResponseTopicsOnSurveyResponsesInput = { surveyResponseId: number } & Pick< Prisma.SurveyResponseTopicsOnSurveyResponsesFindManyArgs, diff --git a/src/survey-responses/components/feedback/EditableSurveyResponseForm.tsx b/src/survey-responses/components/feedback/EditableSurveyResponseForm.tsx index 2365beb80..7e4cfbb97 100644 --- a/src/survey-responses/components/feedback/EditableSurveyResponseForm.tsx +++ b/src/survey-responses/components/feedback/EditableSurveyResponseForm.tsx @@ -2,8 +2,10 @@ import { useMutation } from "@blitzjs/rpc" import { zodResolver } from "@hookform/resolvers/zod" import { Operator } from "@prisma/client" import clsx from "clsx" +import { useRouter } from "next/router" import { PropsWithoutRef, useState } from "react" import { FormProvider, UseFormProps, useForm } from "react-hook-form" +import { LngLatBoundsLike } from "react-map-gl/maplibre" import { LabeledCheckboxGroup, LabeledRadiobuttonGroup, @@ -20,7 +22,6 @@ import updateSurveyResponse from "../../mutations/updateSurveyResponse" import { EditableSurveyResponseFormMap } from "./EditableSurveyResponseFormMap" import { EditableSurveyResponseListItemProps } from "./EditableSurveyResponseListItem" import { surveyResponseStatus } from "./surveyResponseStatus" -import { useRouter } from "next/router" type FormProps> = Omit< PropsWithoutRef, @@ -29,6 +30,10 @@ type FormProps> = Omit< schema?: S initialValues?: UseFormProps>["defaultValues"] refetchResponsesAndTopics: () => void + userLocationQuestionId: number | undefined + maptilerStyleUrl: string + defaultViewState: LngLatBoundsLike + pinColor: string } & Pick export const FORM_ERROR = "FORM_ERROR" @@ -38,7 +43,11 @@ export function EditableSurveyResponseForm>({ response, operators, topics, + defaultViewState, subsections, + maptilerStyleUrl, + userLocationQuestionId, + pinColor, initialValues, refetchResponsesAndTopics, }: FormProps) { @@ -198,9 +207,13 @@ export function EditableSurveyResponseForm>({
diff --git a/src/survey-responses/components/feedback/EditableSurveyResponseFormMap.tsx b/src/survey-responses/components/feedback/EditableSurveyResponseFormMap.tsx index 34906c3e9..3af860b72 100644 --- a/src/survey-responses/components/feedback/EditableSurveyResponseFormMap.tsx +++ b/src/survey-responses/components/feedback/EditableSurveyResponseFormMap.tsx @@ -1,97 +1,67 @@ -import { featureCollection, lineString, point } from "@turf/helpers" -import { midpoint, nearestPointOnLine } from "@turf/turf" -import React from "react" -import { Layer, Marker, Source } from "react-map-gl/maplibre" -import { BaseMap } from "src/core/components/Map/BaseMap" -import { layerColors } from "src/core/components/Map/layerColors" -import { subsectionsBbox } from "src/core/components/Map/utils" -import { SubsectionWithPosition } from "src/subsections/queries/getSubsection" -import { MapPinIcon } from "@heroicons/react/20/solid" +import "maplibre-gl/dist/maplibre-gl.css" +import React, { useState } from "react" +import Map, { LngLatBoundsLike, Marker, NavigationControl, useMap } from "react-map-gl/maplibre" +import { BackgroundSwitcher, LayerType } from "src/core/components/Map/BackgroundSwitcher" +import SurveyStaticPin from "src/core/components/Map/SurveyStaticPin" type Props = { - responsePoint: { lat: number; lng: number } | undefined - subsections: SubsectionWithPosition[] + marker: { lat: number; lng: number } | undefined + maptilerStyleUrl: string + defaultViewState?: LngLatBoundsLike + pinColor: string } -export const EditableSurveyResponseFormMap: React.FC = ({ responsePoint, subsections }) => { - const bbox = subsectionsBbox(subsections) +export const EditableSurveyResponseFormMap: React.FC = ({ + marker, + maptilerStyleUrl, + defaultViewState, + pinColor, +}) => { + const { mainMap } = useMap() - // Group subsection linestrings by operator: - let prevOperatorId: number | undefined - let lineColor = layerColors.selectable + const [selectedLayer, setSelectedLayer] = useState("vector") - const collectedLines: ReturnType[] = [] - const collectedLabels: Record[] = [] + const handleLayerSwitch = (layer: LayerType) => { + setSelectedLayer(layer) + } - subsections.forEach((sub) => { - // Toggle color whenever the operator changed - if (sub.operator?.id !== prevOperatorId) { - lineColor = - lineColor === layerColors.selectable ? layerColors.selected : layerColors.selectable - } + const maptilerApiKey = "ECOoUBmpqklzSCASXxcu" - // Collect lines - collectedLines.push(lineString(sub.geometry, { name: sub.operator?.title, color: lineColor })) - - // Collect label data - // We want one label per operator. - // Ideally that label would be positioned in the center of the merged linestrings. - // However, in the future our linestrings might not be mergable. - // Therefore, we pick the first linestring and accept the missplacement in some situation… - const fakeCenter = midpoint(sub.geometry.at(0)!, sub.geometry.at(-1)!) - const pointOnLine = nearestPointOnLine(lineString(sub.geometry), fakeCenter) - if (sub.operator?.id !== prevOperatorId) { - collectedLabels.push({ - name: sub.operator?.title, - longitude: pointOnLine.geometry.coordinates[0], - latitude: pointOnLine.geometry.coordinates[1], - color: lineColor, - }) - } - - prevOperatorId = sub.operator?.id - }) + const vectorStyle = `${maptilerStyleUrl}?key=${maptilerApiKey}` + const satelliteStyle = `https://api.maptiler.com/maps/hybrid/style.json?key=${maptilerApiKey}` return ( - - - - - - {collectedLabels.map(({ name, longitude, latitude, color }) => { - if (!latitude || !longitude) return null - return ( - - - {name} - +
+ + {marker && ( + + - ) - })} - - {responsePoint && ( - - - - )} - + )} + + + +
) } diff --git a/src/survey-responses/components/feedback/EditableSurveyResponseListItem.tsx b/src/survey-responses/components/feedback/EditableSurveyResponseListItem.tsx index 9edfa1706..21fef3f9d 100644 --- a/src/survey-responses/components/feedback/EditableSurveyResponseListItem.tsx +++ b/src/survey-responses/components/feedback/EditableSurveyResponseListItem.tsx @@ -1,4 +1,4 @@ -import { useRouterQuery } from "@blitzjs/next" +import { useParam, useRouterQuery } from "@blitzjs/next" import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid" import clsx from "clsx" import { useRouter } from "next/router" @@ -11,7 +11,15 @@ import getSurveyResponseTopicsByProject from "src/survey-response-topics/queries import getFeedbackSurveyResponses from "src/survey-responses/queries/getFeedbackSurveyResponses" import { getSurveyResponseCategoryById } from "src/survey-responses/utils/getSurveyResponseCategoryById" import { EditableSurveyResponseForm } from "./EditableSurveyResponseForm" -import feedbackDefinition from "src/participation/data/feedback.json" +import EditableSurveyResponseUserText from "./EditableSurveyResponseUserText" + +import { + getFeedbackDefinitionBySurveySlug, + getResponseConfigBySurveySlug, +} from "src/survey-public/utils/getConfigBySurveySlug" +import { TMapProps } from "src/survey-public/components/types" +import getSurvey from "src/surveys/queries/getSurvey" +import { useQuery } from "@blitzjs/rpc" export type EditableSurveyResponseListItemProps = { response: Prettify>[number]> @@ -42,19 +50,46 @@ const EditableSurveyResponseListItem: React.FC q.id === evaluationRefs["feedback-location"], + )!.props as TMapProps + + const maptilerStyleUrl = mapProps.maptilerStyleUrl + const defaultViewState = mapProps?.config?.bounds + + const feedbackQuestions = [] + + for (let page of feedbackDefinition.pages) { + feedbackQuestions.push(...page.questions) } - const userCategory = + + const feedbackQuestion = feedbackQuestions.find( + (q) => q.id === evaluationRefs["feedback-category"], + ) + + const userTextPreview = // @ts-expect-error `data` is of type unkown - response.data["21"] && getSurveyResponseCategoryById(Number(response.data["21"])) + response.data[evaluationRefs["feedback-usertext-1"]] || + // @ts-expect-error `data` is of type unkown + response.data[evaluationRefs["feedback-usertext-2"]] + + const feedbackUserCategory = + // @ts-expect-error `data` is of type unkown + response.data[evaluationRefs["feedback-category"]] && + evaluationRefs["feedback-category"] && + getSurveyResponseCategoryById( + // @ts-expect-error `data` is of type unkown + Number(response.data[evaluationRefs["feedback-category"]]), + feedbackQuestion!, + ) return (
@@ -77,7 +112,7 @@ const EditableSurveyResponseListItem: React.FC{operatorSlugWitFallback}
- +
{open ? ( @@ -90,28 +125,19 @@ const EditableSurveyResponseListItem: React.FC
-
-
-

- {/* @ts-expect-error `data` is of type unkown */} - {response.data["34"] - ? "Was gefällt Ihnen hier besonders?" - : "Was wünschen Sie sich?"} -

- -
- {userAdditionalText && ( -
-

Was wünschen Sie sich?

- -
- )} -
{`Bürgerbeitrag vom: ${response.surveySession.createdAt.toLocaleDateString()} um ${response.surveySession.createdAt.toLocaleTimeString()}`}
-
+

Kategorie

- {userCategory} + {feedbackUserCategory}
@@ -122,11 +148,15 @@ const EditableSurveyResponseListItem: React.FC
)} diff --git a/src/survey-responses/components/feedback/EditableSurveyResponseUserText.tsx b/src/survey-responses/components/feedback/EditableSurveyResponseUserText.tsx new file mode 100644 index 000000000..45133e3c3 --- /dev/null +++ b/src/survey-responses/components/feedback/EditableSurveyResponseUserText.tsx @@ -0,0 +1,78 @@ +import clsx from "clsx" +import { Markdown } from "src/core/components/Markdown/Markdown" +import { Prettify } from "src/core/types" +import { TFeedbackQuestion } from "src/survey-public/components/types" + +import getFeedbackSurveyResponses from "src/survey-responses/queries/getFeedbackSurveyResponses" + +export type EditableSurveyResponseUserTextProps = { + feedbackQuestions: TFeedbackQuestion[] + response: Prettify>[number]> + userTextIndices: Array + surveyId: string +} + +const EditableSurveyResponseUserText: React.FC = ({ + response, + feedbackQuestions, + userTextIndices, + surveyId, +}) => { + // this is only used for RS8 survey responses + // wegen des Bugs (nur einer der Texte wurde angezeigt) sollen hier alle vor dem Bugfix nicht angezeigten Texte blau hinterlegt werden + // if (surveyId === "1") {... sollte gelöscht werden wenn der Bug keine Rolle mehr spielt + if (surveyId === "1") + return ( +
+ {/* @ts-expect-error `data` is of type unkown */} + {response.data[userTextIndices[0]] && ( +
+

+ {feedbackQuestions.find((q) => q.id === userTextIndices[0])?.label.de} +

+ {/* @ts-expect-error `data` is of type unkown */} + +
+ )} + {/* @ts-expect-error `data` is of type unkown */} + {response.data[userTextIndices[1]] && ( +
+

+ {feedbackQuestions.find((q) => q.id === userTextIndices[1])?.label.de} +

+ {/* @ts-expect-error `data` is of type unkown */} + +
+ )} +
{`Bürgerbeitrag vom: ${response.surveySession.createdAt.toLocaleDateString()} um ${response.surveySession.createdAt.toLocaleTimeString()}`}
+
+ ) + return ( +
+ {userTextIndices.map((userTextIndex) => { + /* @ts-expect-error `data` is of type unkown */ + if (!response.data[userTextIndex]) return null + return ( +
+

+ {feedbackQuestions.find((q) => q.id === userTextIndex)?.label.de} +

+ {/* @ts-expect-error `data` is of type unkown */} + +
+ ) + })} +
{`Bürgerbeitrag vom: ${response.surveySession.createdAt.toLocaleDateString()} um ${response.surveySession.createdAt.toLocaleTimeString()}`}
+
+ ) +} + +export default EditableSurveyResponseUserText diff --git a/src/survey-responses/mutations/createSurveyResponse.ts b/src/survey-responses/mutations/createSurveyResponse.ts index 5d3b75e90..566105bca 100644 --- a/src/survey-responses/mutations/createSurveyResponse.ts +++ b/src/survey-responses/mutations/createSurveyResponse.ts @@ -4,7 +4,7 @@ import { z } from "zod" const CreateSurveyResponse = z.object({ surveySessionId: z.number(), - surveyId: z.number(), + surveyPart: z.number(), data: z.string(), }) diff --git a/src/survey-responses/mutations/updateSurveyResponse.ts b/src/survey-responses/mutations/updateSurveyResponse.ts index 1b1367477..de4c0485c 100644 --- a/src/survey-responses/mutations/updateSurveyResponse.ts +++ b/src/survey-responses/mutations/updateSurveyResponse.ts @@ -14,7 +14,7 @@ const UpdateSurveyResponseSchema = SurveyResponseSchema.merge( // We do not want to update this data, it should stay as is data: true, surveySessionId: true, - surveyId: true, + surveyPart: true, }) export default resolver.pipe( diff --git a/src/survey-responses/queries/getFeedbackSurveyResponses.ts b/src/survey-responses/queries/getFeedbackSurveyResponses.ts index c24ec2494..108ae4394 100644 --- a/src/survey-responses/queries/getFeedbackSurveyResponses.ts +++ b/src/survey-responses/queries/getFeedbackSurveyResponses.ts @@ -17,11 +17,10 @@ export default resolver.pipe( // Only surveyResponse.surveyId === surveyId surveyId, }, - // Only surveyResponse.surveyId === 2 which - // Reminder: `response.surveyId` is NOT a relation field + // Only surveyResponse.surveyPart === 2 // the field here just represents first or second part of the survey json - // surveyId `2` is src/participation/data/feedback.json - surveyId: 2, + // surveyPart `2` in feedback.ts + surveyPart: 2, }, orderBy: { id: "desc" }, include: { diff --git a/src/survey-responses/queries/getGroupedSurveyResponses.ts b/src/survey-responses/queries/getGroupedSurveyResponses.ts index 67ab110c8..982e08d5f 100644 --- a/src/survey-responses/queries/getGroupedSurveyResponses.ts +++ b/src/survey-responses/queries/getGroupedSurveyResponses.ts @@ -42,21 +42,20 @@ export default resolver.pipe( }), }) - // Reminder: `response.surveyId` is NOT a relation field - // the field here just represents first or second part of the survey json + // the surveyPart field here represents first or second part of the survey - // src/participation/data/survey.json + // /data/survey.ts const surveyResponsesFirstPart = surveySessions .map((session) => session.responses) .flat() - .filter((response) => response.surveyId === 1) + .filter((response) => response.surveyPart === 1) .sort((a, b) => b.id - a.id) - // src/participation/data/feedback.json + // /data/feedback.ts const surveyResponsesFeedbackPart = surveySessions .map((session) => session.responses) .flat() - .filter((response) => response.surveyId === 2) + .filter((response) => response.surveyPart === 2) // We need to sort again; the frontend received different orders before… .sort((a, b) => b.id - a.id) diff --git a/src/survey-responses/schema.ts b/src/survey-responses/schema.ts index 351a149a4..921f7bebb 100644 --- a/src/survey-responses/schema.ts +++ b/src/survey-responses/schema.ts @@ -5,7 +5,7 @@ export const SurveyResponseSchema = z.object({ data: z.string(), status: z.nativeEnum(SurveyResponseStatusEnum), surveySessionId: z.coerce.number(), - surveyId: z.coerce.number(), + surveyPart: z.coerce.number(), note: z.string().nullish(), operatorId: z.coerce.number().nullish(), }) diff --git a/src/survey-responses/utils/getSurveyResponseCategoryById.ts b/src/survey-responses/utils/getSurveyResponseCategoryById.ts index 62e8caa8c..cec829c44 100644 --- a/src/survey-responses/utils/getSurveyResponseCategoryById.ts +++ b/src/survey-responses/utils/getSurveyResponseCategoryById.ts @@ -1,8 +1,13 @@ -import feedbackDefinition from "src/participation/data/feedback.json" +import { + TFeedbackQuestion, + TResponse, + TSingleOrMultiResponseProps, +} from "src/survey-public/components/types" -export const getSurveyResponseCategoryById = (id: number) => - feedbackDefinition.pages[0]?.questions - // @ts-ignore - .find((e) => e.id === 21) - // @ts-ignore - .props.responses.find((r) => r.id === id).text.de +export const getSurveyResponseCategoryById = ( + id: number, + feedbackUserCategoryQuestion: TFeedbackQuestion, +) => { + const props = feedbackUserCategoryQuestion?.props as TSingleOrMultiResponseProps + return props.responses.find((r: TResponse) => r.id === id)?.text.de +} diff --git a/src/surveys/queries/getPublicSurveyBySlug.ts b/src/surveys/queries/getPublicSurveyBySlug.ts index 2b834f87d..e56b898fa 100644 --- a/src/surveys/queries/getPublicSurveyBySlug.ts +++ b/src/surveys/queries/getPublicSurveyBySlug.ts @@ -1,4 +1,5 @@ import { resolver } from "@blitzjs/rpc" +import { NotFoundError } from "blitz" import db from "db" import { z } from "zod" @@ -6,12 +7,12 @@ const GetSurvey = z.object({ slug: z.string(), }) -export default resolver.pipe( - resolver.zod(GetSurvey), - async ({ slug }) => - await db.survey.findFirstOrThrow({ - where: { slug }, - // For the public query, only return public information - select: { slug: true, active: true }, - }), -) +export default resolver.pipe(resolver.zod(GetSurvey), async ({ slug }) => { + const survey = await db.survey.findFirst({ + where: { slug }, + // For the public query, only return public information + select: { slug: true, id: true, active: true }, + }) + if (!survey) throw new NotFoundError() + return survey +})