diff --git a/apps/mana/apps/web/src/lib/i18n/locales/forms/de.json b/apps/mana/apps/web/src/lib/i18n/locales/forms/de.json index eeb5ea9fe..b360ba57a 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/forms/de.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/forms/de.json @@ -23,5 +23,53 @@ "draft": "Entwurf", "published": "Veröffentlicht", "closed": "Geschlossen" + }, + "builder": { + "routeTitle": "Formular bearbeiten", + "loading": "Lade ...", + "notFound": "Formular nicht gefunden.", + "backLink": "← Zurück zur Liste", + "back": "← Zurück", + "delete": "Löschen", + "deleteConfirm": "Formular \"{title}\" wirklich löschen?", + "statusGroupAria": "Status", + "titlePlaceholder": "Formular-Titel", + "titleAria": "Formular-Titel", + "descriptionPlaceholder": "Beschreibung (optional)", + "fields": { + "title": "Felder", + "count": "{n} Felder", + "empty": "Noch keine Felder. Wähle unten einen Typ." + }, + "palette": { + "title": "Feld hinzufügen" + }, + "field": { + "labelPlaceholder": "Feldfrage ...", + "labelAria": "Feldlabel", + "removeAria": "Feld löschen", + "required": "Pflichtfeld", + "lessOptions": "− weniger", + "moreOptions": "+ mehr", + "helpPlaceholder": "Hilfetext (optional)", + "options": "Optionen", + "optionPlaceholder": "Option ...", + "optionRemoveAria": "Option löschen", + "addOption": "+ Option hinzufügen", + "ratingScale": "Skala", + "scale5": "1–5", + "scale10": "1–10", + "min": "Minimum", + "max": "Maximum", + "maxLength": "Max. Länge" + }, + "settings": { + "title": "Einstellungen", + "submitLabel": "Beschriftung Absenden-Button", + "successMessage": "Bestätigungstext nach Absenden", + "requireEmail": "E-Mail-Adresse vom Absender abfragen", + "allowMultiple": "Mehrere Antworten pro Person erlauben", + "anonymous": "Anonym — Submitter-Daten nicht speichern" + } } } diff --git a/apps/mana/apps/web/src/lib/i18n/locales/forms/en.json b/apps/mana/apps/web/src/lib/i18n/locales/forms/en.json index 43e96a4e7..c25918e2f 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/forms/en.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/forms/en.json @@ -23,5 +23,53 @@ "draft": "Draft", "published": "Published", "closed": "Closed" + }, + "builder": { + "routeTitle": "Edit form", + "loading": "Loading ...", + "notFound": "Form not found.", + "backLink": "← Back to list", + "back": "← Back", + "delete": "Delete", + "deleteConfirm": "Really delete form \"{title}\"?", + "statusGroupAria": "Status", + "titlePlaceholder": "Form title", + "titleAria": "Form title", + "descriptionPlaceholder": "Description (optional)", + "fields": { + "title": "Fields", + "count": "{n} fields", + "empty": "No fields yet. Pick a type below." + }, + "palette": { + "title": "Add field" + }, + "field": { + "labelPlaceholder": "Field question ...", + "labelAria": "Field label", + "removeAria": "Delete field", + "required": "Required", + "lessOptions": "− less", + "moreOptions": "+ more", + "helpPlaceholder": "Help text (optional)", + "options": "Options", + "optionPlaceholder": "Option ...", + "optionRemoveAria": "Delete option", + "addOption": "+ Add option", + "ratingScale": "Scale", + "scale5": "1–5", + "scale10": "1–10", + "min": "Minimum", + "max": "Maximum", + "maxLength": "Max length" + }, + "settings": { + "title": "Settings", + "submitLabel": "Submit button label", + "successMessage": "Confirmation text after submit", + "requireEmail": "Ask submitter for email address", + "allowMultiple": "Allow multiple submissions per person", + "anonymous": "Anonymous — don't store submitter data" + } } } diff --git a/apps/mana/apps/web/src/lib/i18n/locales/forms/es.json b/apps/mana/apps/web/src/lib/i18n/locales/forms/es.json index c3dfe4c90..ea259ea2a 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/forms/es.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/forms/es.json @@ -23,5 +23,53 @@ "draft": "Borrador", "published": "Publicado", "closed": "Cerrado" + }, + "builder": { + "routeTitle": "Editar formulario", + "loading": "Cargando ...", + "notFound": "Formulario no encontrado.", + "backLink": "← Volver a la lista", + "back": "← Atrás", + "delete": "Eliminar", + "deleteConfirm": "¿Eliminar el formulario \"{title}\"?", + "statusGroupAria": "Estado", + "titlePlaceholder": "Título del formulario", + "titleAria": "Título del formulario", + "descriptionPlaceholder": "Descripción (opcional)", + "fields": { + "title": "Campos", + "count": "{n} campos", + "empty": "Aún sin campos. Elige un tipo abajo." + }, + "palette": { + "title": "Añadir campo" + }, + "field": { + "labelPlaceholder": "Pregunta del campo ...", + "labelAria": "Etiqueta del campo", + "removeAria": "Eliminar campo", + "required": "Obligatorio", + "lessOptions": "− menos", + "moreOptions": "+ más", + "helpPlaceholder": "Texto de ayuda (opcional)", + "options": "Opciones", + "optionPlaceholder": "Opción ...", + "optionRemoveAria": "Eliminar opción", + "addOption": "+ Añadir opción", + "ratingScale": "Escala", + "scale5": "1–5", + "scale10": "1–10", + "min": "Mínimo", + "max": "Máximo", + "maxLength": "Longitud máx." + }, + "settings": { + "title": "Ajustes", + "submitLabel": "Texto del botón de envío", + "successMessage": "Mensaje de confirmación tras el envío", + "requireEmail": "Pedir correo electrónico al remitente", + "allowMultiple": "Permitir varias respuestas por persona", + "anonymous": "Anónimo — no guardar datos del remitente" + } } } diff --git a/apps/mana/apps/web/src/lib/i18n/locales/forms/fr.json b/apps/mana/apps/web/src/lib/i18n/locales/forms/fr.json index 990967e05..09885ab29 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/forms/fr.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/forms/fr.json @@ -23,5 +23,53 @@ "draft": "Brouillon", "published": "Publié", "closed": "Clôturé" + }, + "builder": { + "routeTitle": "Modifier le formulaire", + "loading": "Chargement ...", + "notFound": "Formulaire introuvable.", + "backLink": "← Retour à la liste", + "back": "← Retour", + "delete": "Supprimer", + "deleteConfirm": "Vraiment supprimer le formulaire \"{title}\" ?", + "statusGroupAria": "Statut", + "titlePlaceholder": "Titre du formulaire", + "titleAria": "Titre du formulaire", + "descriptionPlaceholder": "Description (facultatif)", + "fields": { + "title": "Champs", + "count": "{n} champs", + "empty": "Pas encore de champs. Choisis un type ci-dessous." + }, + "palette": { + "title": "Ajouter un champ" + }, + "field": { + "labelPlaceholder": "Question du champ ...", + "labelAria": "Libellé du champ", + "removeAria": "Supprimer le champ", + "required": "Obligatoire", + "lessOptions": "− moins", + "moreOptions": "+ plus", + "helpPlaceholder": "Texte d'aide (facultatif)", + "options": "Options", + "optionPlaceholder": "Option ...", + "optionRemoveAria": "Supprimer l'option", + "addOption": "+ Ajouter une option", + "ratingScale": "Échelle", + "scale5": "1–5", + "scale10": "1–10", + "min": "Minimum", + "max": "Maximum", + "maxLength": "Longueur max." + }, + "settings": { + "title": "Paramètres", + "submitLabel": "Libellé du bouton d'envoi", + "successMessage": "Message de confirmation après envoi", + "requireEmail": "Demander l'adresse e-mail au destinataire", + "allowMultiple": "Autoriser plusieurs réponses par personne", + "anonymous": "Anonyme — ne pas conserver les données du destinataire" + } } } diff --git a/apps/mana/apps/web/src/lib/i18n/locales/forms/it.json b/apps/mana/apps/web/src/lib/i18n/locales/forms/it.json index bdc6d3194..62b3dca95 100644 --- a/apps/mana/apps/web/src/lib/i18n/locales/forms/it.json +++ b/apps/mana/apps/web/src/lib/i18n/locales/forms/it.json @@ -23,5 +23,53 @@ "draft": "Bozza", "published": "Pubblicato", "closed": "Chiuso" + }, + "builder": { + "routeTitle": "Modifica modulo", + "loading": "Caricamento ...", + "notFound": "Modulo non trovato.", + "backLink": "← Torna alla lista", + "back": "← Indietro", + "delete": "Elimina", + "deleteConfirm": "Eliminare davvero il modulo \"{title}\"?", + "statusGroupAria": "Stato", + "titlePlaceholder": "Titolo del modulo", + "titleAria": "Titolo del modulo", + "descriptionPlaceholder": "Descrizione (facoltativa)", + "fields": { + "title": "Campi", + "count": "{n} campi", + "empty": "Ancora nessun campo. Scegli un tipo sotto." + }, + "palette": { + "title": "Aggiungi campo" + }, + "field": { + "labelPlaceholder": "Domanda del campo ...", + "labelAria": "Etichetta del campo", + "removeAria": "Elimina campo", + "required": "Obbligatorio", + "lessOptions": "− meno", + "moreOptions": "+ altro", + "helpPlaceholder": "Testo di aiuto (facoltativo)", + "options": "Opzioni", + "optionPlaceholder": "Opzione ...", + "optionRemoveAria": "Elimina opzione", + "addOption": "+ Aggiungi opzione", + "ratingScale": "Scala", + "scale5": "1–5", + "scale10": "1–10", + "min": "Minimo", + "max": "Massimo", + "maxLength": "Lunghezza max." + }, + "settings": { + "title": "Impostazioni", + "submitLabel": "Testo del pulsante di invio", + "successMessage": "Messaggio di conferma dopo l'invio", + "requireEmail": "Chiedi l'indirizzo e-mail al mittente", + "allowMultiple": "Permetti più risposte per persona", + "anonymous": "Anonimo — non memorizzare i dati del mittente" + } } } diff --git a/apps/mana/apps/web/src/lib/modules/forms/components/FieldEditor.svelte b/apps/mana/apps/web/src/lib/modules/forms/components/FieldEditor.svelte new file mode 100644 index 000000000..ee522f43c --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/forms/components/FieldEditor.svelte @@ -0,0 +1,411 @@ + + + +
+
+ {index + 1}. + onchange({ label: (e.currentTarget as HTMLInputElement).value })} + placeholder={$_('forms.builder.field.labelPlaceholder', { default: 'Feldfrage ...' })} + aria-label={$_('forms.builder.field.labelAria', { default: 'Feldlabel' })} + /> + +
+ +
+ + + + + +
+ + {#if advancedOpen} +
+ { + const val = (e.currentTarget as HTMLInputElement).value; + onchange({ helpText: val.length > 0 ? val : undefined }); + }} + placeholder={$_('forms.builder.field.helpPlaceholder', { + default: 'Hilfetext (optional)', + })} + /> + + {#if field.type === 'single_choice' || field.type === 'multi_choice'} +
+

+ {$_('forms.builder.field.options', { default: 'Optionen' })} +

+ {#each field.options ?? [] as option (option.id)} +
+ + updateOption(option.id, (e.currentTarget as HTMLInputElement).value)} + placeholder={$_('forms.builder.field.optionPlaceholder', { + default: 'Option ...', + })} + /> + +
+ {/each} + +
+ {:else if field.type === 'rating'} + + {:else if field.type === 'number'} +
+ + +
+ {:else if field.type === 'short_text' || field.type === 'long_text'} +
+ +
+ {/if} +
+ {/if} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/forms/components/FieldPalette.svelte b/apps/mana/apps/web/src/lib/modules/forms/components/FieldPalette.svelte new file mode 100644 index 000000000..d63d2ea3b --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/forms/components/FieldPalette.svelte @@ -0,0 +1,110 @@ + + + +
+

+ {$_('forms.builder.palette.title', { default: 'Feld hinzufügen' })} +

+
+ {#each TYPES as type} + + {/each} +
+
+ + diff --git a/apps/mana/apps/web/src/lib/modules/forms/components/SettingsPanel.svelte b/apps/mana/apps/web/src/lib/modules/forms/components/SettingsPanel.svelte new file mode 100644 index 000000000..19cf1bfd6 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/forms/components/SettingsPanel.svelte @@ -0,0 +1,147 @@ + + + +
+

+ {$_('forms.builder.settings.title', { default: 'Einstellungen' })} +

+ + + + + + + + + + +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/forms/index.ts b/apps/mana/apps/web/src/lib/modules/forms/index.ts index 39cf7f486..90b4defff 100644 --- a/apps/mana/apps/web/src/lib/modules/forms/index.ts +++ b/apps/mana/apps/web/src/lib/modules/forms/index.ts @@ -16,6 +16,9 @@ export { // ─── Collections ───────────────────────────────────────── export { formTable, formResponseTable } from './collections'; +// ─── Lib ───────────────────────────────────────────────── +export { makeDefaultField } from './lib/field-defaults'; + // ─── Types ─────────────────────────────────────────────── export { FIELD_TYPE_LABELS, diff --git a/apps/mana/apps/web/src/lib/modules/forms/lib/field-defaults.ts b/apps/mana/apps/web/src/lib/modules/forms/lib/field-defaults.ts new file mode 100644 index 000000000..d53080f13 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/forms/lib/field-defaults.ts @@ -0,0 +1,73 @@ +import type { FieldType, FormField } from '../types'; + +/** + * Build a fresh `FormField` for the given type with sensible defaults. + * Generates a fresh UUID and the type-specific config so the builder + * can append-and-go without touching the underlying schema engine. + */ +export function makeDefaultField(type: FieldType): FormField { + const id = crypto.randomUUID(); + const base: FormField = { + id, + type, + label: defaultLabel(type), + required: false, + }; + + switch (type) { + case 'single_choice': + case 'multi_choice': + return { + ...base, + options: [ + { id: crypto.randomUUID(), label: 'Option 1' }, + { id: crypto.randomUUID(), label: 'Option 2' }, + ], + }; + case 'rating': + return { ...base, config: { ratingScale: 5 } }; + case 'short_text': + return { ...base, config: { maxLength: 120 } }; + case 'long_text': + return { ...base, config: { maxLength: 2000 } }; + case 'number': + return { ...base, config: {} }; + case 'consent': + return { + ...base, + required: true, + label: 'Ich stimme der Datenverarbeitung zu.', + }; + case 'section': + return { ...base, label: 'Abschnitt' }; + default: + return base; + } +} + +function defaultLabel(type: FieldType): string { + switch (type) { + case 'short_text': + return 'Kurze Frage'; + case 'long_text': + return 'Längere Frage'; + case 'single_choice': + return 'Wähle eine Option'; + case 'multi_choice': + return 'Wähle eine oder mehrere Optionen'; + case 'number': + return 'Zahl'; + case 'date': + return 'Datum'; + case 'email': + return 'E-Mail-Adresse'; + case 'yes_no': + return 'Ja oder Nein?'; + case 'rating': + return 'Wie würdest du das bewerten?'; + case 'section': + return 'Abschnitt'; + case 'consent': + return 'Einwilligung'; + } +} diff --git a/apps/mana/apps/web/src/lib/modules/forms/views/BuilderView.svelte b/apps/mana/apps/web/src/lib/modules/forms/views/BuilderView.svelte new file mode 100644 index 000000000..68c9e6126 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/forms/views/BuilderView.svelte @@ -0,0 +1,382 @@ + + + +
+
+ + +
+ {#each Object.keys(FORM_STATUS_LABELS) as st} + + {/each} +
+ + +
+ +
+ + +
+ +
+
+

{$_('forms.builder.fields.title', { default: 'Felder' })}

+ + {$_('forms.builder.fields.count', { + default: '{n} Felder', + values: { n: items.length }, + })} + +
+ + {#if items.length === 0} +

+ {$_('forms.builder.fields.empty', { + default: 'Noch keine Felder. Wähle unten einen Typ.', + })} +

+ {:else} +
+ {#each items as field, i (field.id)} +
+ patchField(field.id, patch)} + onremove={() => removeField(field.id)} + /> +
+ {/each} +
+ {/if} + + +
+ +
+ +
+
+ + diff --git a/apps/mana/apps/web/src/routes/(app)/forms/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/forms/[id]/+page.svelte new file mode 100644 index 000000000..5ed6117e8 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/forms/[id]/+page.svelte @@ -0,0 +1,48 @@ + + + + {entry?.title ?? $_('forms.builder.routeTitle', { default: 'Formular bearbeiten' })} - Mana + + + + {#if forms$.loading} +

{$_('forms.builder.loading', { default: 'Lade ...' })}

+ {:else if !entry} +
+

{$_('forms.builder.notFound', { default: 'Formular nicht gefunden.' })}

+ {$_('forms.builder.backLink', { default: '← Zurück zur Liste' })} +
+ {:else} + + {/if} +
+ +