feat: configureerbare itemnamen per vragenlijst

Voeg item_label en item_label_plural toe aan questionnaires (migratie),
beheerformulier en dynamische teksten in UI en API-fouten. Standaard
blijft Activiteit/Activiteiten.

Negeer tsbuildinfo en geëmitteerde vite.config.js/.d.ts in .gitignore.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-10 15:29:44 +02:00
parent 6871126788
commit 93aedf319b
9 changed files with 234 additions and 42 deletions

View File

@@ -1,5 +1,14 @@
import { Router, Request, Response } from 'express';
import { questionnaireOps, activityOps, voteOps, commentOps, participantOps, Comment, Questionnaire } from '../database.js';
import {
questionnaireOps,
activityOps,
voteOps,
commentOps,
participantOps,
Comment,
Questionnaire,
resolveItemLabels,
} from '../database.js';
// Helper to check if user can participate (write access)
function canParticipate(questionnaire: Questionnaire, req: Request): { canWrite: boolean; participantName: string | null } {
@@ -152,8 +161,9 @@ router.post('/:slug/activities', (req: Request, res: Response) => {
return;
}
const { singular: itemSingular } = resolveItemLabels(questionnaire);
if (!name?.trim()) {
res.status(400).json({ error: 'Naam activiteit is verplicht' });
res.status(400).json({ error: `Naam ${itemSingular.toLowerCase()} is verplicht` });
return;
}
@@ -186,20 +196,21 @@ router.put('/:slug/activities/:activityId', (req: Request, res: Response) => {
return;
}
const { singular: itemSingular, plural: itemPlural } = resolveItemLabels(questionnaire);
const activity = activityOps.findById(parseInt(req.params.activityId));
if (!activity || activity.questionnaire_id !== questionnaire.id) {
res.status(404).json({ error: 'Activiteit niet gevonden' });
res.status(404).json({ error: `${itemSingular} niet gevonden` });
return;
}
// Check if the user is the one who added the activity
if (activity.added_by !== participantName) {
res.status(403).json({ error: 'Je kunt alleen je eigen activiteiten bewerken' });
res.status(403).json({ error: `Je kunt alleen je eigen ${itemPlural.toLowerCase()} bewerken` });
return;
}
if (!name?.trim()) {
res.status(400).json({ error: 'Naam activiteit is verplicht' });
res.status(400).json({ error: `Naam ${itemSingular.toLowerCase()} is verplicht` });
return;
}
@@ -231,9 +242,10 @@ router.post('/:slug/activities/:activityId/vote', (req: Request, res: Response)
return;
}
const { singular: itemSingular } = resolveItemLabels(questionnaire);
const activity = activityOps.findById(parseInt(req.params.activityId));
if (!activity || activity.questionnaire_id !== questionnaire.id) {
res.status(404).json({ error: 'Activiteit niet gevonden' });
res.status(404).json({ error: `${itemSingular} niet gevonden` });
return;
}
@@ -286,9 +298,10 @@ router.get('/:slug/activities/:activityId/comments', (req: Request, res: Respons
return;
}
const { singular: itemSingular } = resolveItemLabels(questionnaire);
const activity = activityOps.findById(parseInt(req.params.activityId));
if (!activity || activity.questionnaire_id !== questionnaire.id) {
res.status(404).json({ error: 'Activiteit niet gevonden' });
res.status(404).json({ error: `${itemSingular} niet gevonden` });
return;
}
@@ -337,9 +350,10 @@ router.post('/:slug/activities/:activityId/comments', (req: Request, res: Respon
return;
}
const { singular: itemSingular } = resolveItemLabels(questionnaire);
const activity = activityOps.findById(parseInt(req.params.activityId));
if (!activity || activity.questionnaire_id !== questionnaire.id) {
res.status(404).json({ error: 'Activiteit niet gevonden' });
res.status(404).json({ error: `${itemSingular} niet gevonden` });
return;
}