From 61e3fae18d860bf3f998d96cd44b45668d469474 Mon Sep 17 00:00:00 2001 From: Md Bayazid Bostame Date: Fri, 27 Mar 2026 21:06:03 +0100 Subject: [PATCH] refine form builder workspace interactions --- .../static/workflows/css/form_builder.css | 864 +++++++++++++++--- .../templates/workflows/form_builder.html | 741 +++++++++------ backend/workflows/views.py | 117 ++- 3 files changed, 1290 insertions(+), 432 deletions(-) diff --git a/backend/workflows/static/workflows/css/form_builder.css b/backend/workflows/static/workflows/css/form_builder.css index 4f7ce6c..dc9b3e4 100644 --- a/backend/workflows/static/workflows/css/form_builder.css +++ b/backend/workflows/static/workflows/css/form_builder.css @@ -18,6 +18,121 @@ body { backdrop-filter: blur(12px); } +.builder-workspace { + display: grid; + grid-template-columns: 280px minmax(0, 1fr); + gap: 18px; + align-items: start; +} + +.builder-sidebar { + position: sticky; + top: 18px; + display: grid; + gap: 14px; +} + +.builder-main { + min-width: 0; +} + +.builder-sidebar-card { + padding: 16px; + border: 1px solid #d7e0ec; + border-radius: 18px; + background: linear-gradient(180deg, rgba(248, 251, 255, 0.98), rgba(255, 255, 255, 0.98)); + box-shadow: 0 12px 24px rgba(15, 23, 42, 0.05); +} + +.builder-sidebar-card h2 { + margin: 6px 0 8px; + font-size: 20px; + color: #142033; +} + +.builder-sidebar-card p { + margin: 0; + color: #5f7089; + font-size: 13px; + line-height: 1.6; +} + +.builder-sidebar-eyebrow { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 9px; + border-radius: 999px; + background: #eaf1ff; + color: #214d99; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.builder-side-nav { + display: grid; + gap: 8px; +} + +.builder-side-link { + display: grid; + gap: 4px; + padding: 14px 16px; + border: 1px solid #d7e0ec; + border-radius: 16px; + background: linear-gradient(180deg, #fbfdff, #ffffff); + color: #142033; + text-decoration: none; + transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; +} + +.builder-side-link:hover { + transform: translateY(-1px); + border-color: #b8cae0; + box-shadow: 0 12px 24px rgba(15, 23, 42, 0.06); +} + +.builder-side-link.is-active { + border-color: #9eb6d8; + background: linear-gradient(180deg, #eef5ff, #ffffff); + box-shadow: 0 12px 24px rgba(15, 23, 42, 0.08); +} + +.builder-side-link-title { + font-size: 14px; + font-weight: 800; +} + +.builder-side-link-meta { + color: #607086; + font-size: 12px; + font-weight: 700; +} + +.builder-sidebar-stats { + display: grid; + gap: 12px; +} + +.builder-side-stat { + display: grid; + gap: 2px; +} + +.builder-side-stat strong { + font-size: 22px; + line-height: 1; + color: #163566; +} + +.builder-side-stat span { + color: #607086; + font-size: 12px; + font-weight: 700; +} + .builder-hero, .builder-panel, .builder-stat-card, @@ -39,6 +154,14 @@ body { max-width: 760px; } +.builder-hero-sub { + margin: 10px 0 0; + max-width: 640px; + color: #5c6d87; + font-size: 15px; + line-height: 1.6; +} + .builder-eyebrow { display: inline-flex; align-items: center; @@ -142,12 +265,91 @@ body { box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); } +.builder-module-surface { + overflow: hidden; +} + +.builder-module-hidden { + display: none; +} + +.builder-module-nav { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 14px; +} + +.builder-module-link { + display: inline-flex; + align-items: center; + min-height: 36px; + padding: 0 12px; + border: 1px solid #d1dbea; + border-radius: 999px; + background: #f7fbff; + color: #304159; + font-size: 13px; + font-weight: 800; + text-decoration: none; + transition: transform 0.16s ease, border-color 0.16s ease, background-color 0.16s ease, box-shadow 0.16s ease; +} + +.builder-module-link:hover { + transform: translateY(-1px); + border-color: #adc2dd; + background: #ffffff; + box-shadow: 0 10px 18px rgba(15, 23, 42, 0.06); +} + +.builder-module-link.is-active { + border-color: #164a99; + background: linear-gradient(135deg, #0f3b7a 0%, #1759b8 100%); + color: #ffffff; +} + .builder-panel-head h2, .options-head h2 { margin: 0; color: #142033; } +.builder-panel-copy, +.options-copy { + min-width: 0; +} + +.builder-panel-sub, +.options-sub { + margin: 6px 0 0; + color: #61718a; + font-size: 13px; + line-height: 1.5; +} + +.builder-panel-meta { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.builder-panel-count { + display: inline-flex; + align-items: center; + min-height: 30px; + padding: 0 10px; + border: 1px solid #d7e1ee; + border-radius: 999px; + background: #f8fbff; + color: #294567; + font-size: 12px; + font-weight: 800; + letter-spacing: 0.01em; + white-space: nowrap; +} + .mini { color: #61718a; font-size: 13px; @@ -234,114 +436,18 @@ body { margin-bottom: 14px; } -.builder-accordion { - padding: 0; - overflow: hidden; -} - -.nested-accordion { - padding: 0; - overflow: hidden; -} - -.nested-accordion-summary { - list-style: none; - cursor: pointer; - padding: 14px 16px; - transition: background-color 0.18s ease; -} - -.nested-accordion-summary::-webkit-details-marker { - display: none; -} - -.nested-accordion-summary .options-head { - margin-bottom: 0; -} - -.nested-accordion-summary:hover { - background: rgba(242, 247, 255, 0.72); -} - -.nested-accordion[open] .nested-accordion-summary { - border-bottom: 1px solid rgba(201, 212, 226, 0.8); -} - -.nested-accordion:not([open]) .builder-panel-toggle::after { - transform: rotate(-90deg); -} - -.nested-accordion[open] .builder-panel-toggle { - color: #194ea7; -} - -.nested-accordion-body { - padding: 14px 16px 16px; -} - -.nested-accordion[open] .nested-accordion-body { - animation: builderReveal 0.24s ease; -} - -.builder-panel-summary { - list-style: none; - cursor: pointer; - padding: 14px 16px; - transition: background-color 0.18s ease; -} - -.builder-panel-summary::-webkit-details-marker { - display: none; -} - -.builder-panel-summary .builder-panel-head { - margin-bottom: 0; -} - -.builder-panel-summary:hover { - background: rgba(242, 247, 255, 0.72); -} - -.builder-panel-toggle { - display: inline-flex; - align-items: center; - gap: 8px; - color: #5f7089; - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.builder-panel-toggle::after { - content: "▾"; - font-size: 13px; - line-height: 1; - transition: transform 0.18s ease; -} - -.builder-accordion:not([open]) .builder-panel-toggle::after { - transform: rotate(-90deg); -} - -.builder-accordion[open] .builder-panel-toggle { - color: #194ea7; -} - -.builder-accordion[open] .builder-panel-toggle::before { - content: ""; -} - -.builder-accordion[open] .builder-panel-summary { - border-bottom: 1px solid rgba(201, 212, 226, 0.8); +.module-card-body { + display: grid; + gap: 14px; } .builder-panel-body { padding: 14px 16px 16px; } -.builder-accordion[open] .builder-panel-body { - animation: builderReveal 0.24s ease; +.builder-panel-body-static { + display: grid; + gap: 16px; } .builder-rule-layout { @@ -355,6 +461,10 @@ body { gap: 14px; } +.builder-module-card { + overflow: hidden; +} + .preview-shell { display: grid; gap: 12px; @@ -458,29 +568,43 @@ body { .field-rule-list { display: grid; + gap: 10px; + padding: 10px; } .field-rule-row { display: grid; - grid-template-columns: minmax(220px, 1.4fr) 120px 160px 120px; - gap: 12px; + grid-template-columns: minmax(240px, 1.5fr) minmax(120px, 0.55fr) minmax(170px, 0.7fr) auto; + gap: 14px; align-items: center; - padding: 12px 14px; - border-top: 1px solid #edf2f7; + padding: 14px; + border: 1px solid #e7edf6; + border-radius: 14px; + background: rgba(255, 255, 255, 0.96); transition: background-color 0.18s ease; } .field-rule-row:first-child { - border-top: 0; + border-top: 1px solid #e7edf6; } .field-rule-row:hover { - background: rgba(246, 250, 255, 0.92); + background: rgba(246, 250, 255, 0.96); } .field-rule-main strong { display: block; color: #162133; + font-size: 14px; +} + +.field-rule-meta { + margin-top: 8px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + flex-wrap: wrap; } .field-rule-control { @@ -491,6 +615,18 @@ body { font-weight: 700; } +.field-rule-settings { + display: flex; + align-items: end; + justify-content: flex-end; + gap: 12px; + flex-wrap: wrap; +} + +.field-rule-control-compact { + min-width: 120px; +} + .field-rule-control input[type='checkbox'] { width: 16px; height: 16px; @@ -501,6 +637,10 @@ body { justify-content: flex-end; } +.field-rule-status-inline { + justify-content: flex-start; +} + .conditional-rule-grid { display: grid; gap: 12px; @@ -573,6 +713,33 @@ body { .conditional-targets { display: grid; gap: 8px; + padding: 12px; + border: 1px solid #e7edf6; + border-radius: 14px; + background: #f9fbff; +} + +.conditional-rule-summary { + display: grid; + gap: 8px; + padding: 12px; + border: 1px solid #dbe6f5; + border-radius: 14px; + background: linear-gradient(180deg, #f6faff, #ffffff); +} + +.conditional-summary-prefix { + color: #294567; + font-size: 12px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.conditional-summary-chips { + display: flex; + flex-wrap: wrap; + gap: 8px; } .conditional-target-label { @@ -594,6 +761,42 @@ body { gap: 10px; } +.conditional-sentence-builder { + display: grid; + gap: 10px; +} + +.conditional-sentence-row { + display: grid; + grid-template-columns: minmax(180px, 1.2fr) minmax(220px, 1.3fr) minmax(160px, 0.9fr) minmax(160px, 0.9fr); + gap: 10px; + align-items: end; + padding: 12px; + border: 1px solid #e5ebf3; + border-radius: 14px; + background: #f8fbff; +} + +.conditional-sentence-label { + color: #33506f; + font-size: 12px; + font-weight: 800; + line-height: 1.45; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.conditional-sentence-row select, +.conditional-sentence-row input[type='text'] { + width: 100%; + min-height: 40px; + border: 1px solid #cbd5e1; + border-radius: 10px; + padding: 8px 10px; + box-sizing: border-box; + background: #fff; +} + .conditional-clause-row { display: grid; grid-template-columns: 56px minmax(220px, 1.35fr) minmax(180px, 0.8fr) minmax(180px, 0.85fr); @@ -666,14 +869,161 @@ body { .columns { display: grid; - grid-template-columns: repeat(4, minmax(220px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; + min-width: 0; +} + +.structure-workspace { + display: grid; + grid-template-columns: 260px minmax(0, 1fr); + gap: 16px; + align-items: start; + min-width: 0; +} + +.structure-sidebar { + display: grid; + gap: 12px; + position: sticky; + top: 18px; +} + +.structure-canvas { + min-width: 0; + overflow: visible; + padding-bottom: 4px; + display: grid; + gap: 14px; +} + +.structure-section-nav { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.structure-section-pill { + display: inline-flex; + align-items: center; + gap: 10px; + min-height: 44px; + padding: 0 14px 0 10px; + border: 1px solid #d7e0ec; + border-radius: 16px; + background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%); + color: #142033; + text-decoration: none; + transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; +} + +.structure-section-pill:hover { + transform: translateY(-1px); + border-color: #bfd0e4; + box-shadow: 0 10px 18px rgba(15, 23, 42, 0.06); +} + +.structure-section-pill.is-active { + border-color: #9eb6d8; + background: linear-gradient(180deg, #eef5ff 0%, #ffffff 100%); + box-shadow: 0 12px 22px rgba(15, 23, 42, 0.08); +} + +.structure-section-pill-index { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 999px; + background: #eaf1ff; + color: #214d99; + font-size: 12px; + font-weight: 800; +} + +.structure-section-pill-copy { + display: grid; + gap: 1px; +} + +.structure-section-pill-copy strong { + font-size: 14px; + line-height: 1.2; +} + +.structure-section-pill-copy span { + color: #607086; + font-size: 11px; + font-weight: 700; +} + +.structure-card { + display: grid; + gap: 8px; + padding: 16px; + border: 1px solid #d7e0ec; + border-radius: 18px; + background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%); + box-shadow: 0 12px 24px rgba(15, 23, 42, 0.05); +} + +.structure-card h3 { + margin: 0; + font-size: 17px; + color: #142033; +} + +.structure-card p { + margin: 0; + color: #5f7089; + font-size: 13px; + line-height: 1.6; +} + +.structure-card-muted { + background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%); +} + +.structure-card-eyebrow { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 9px; + border-radius: 999px; + background: #eaf1ff; + color: #214d99; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.structure-stat { + display: grid; + gap: 2px; +} + +.structure-stat strong { + font-size: 22px; + line-height: 1; + color: #163566; +} + +.structure-stat span { + color: #607086; + font-size: 12px; + font-weight: 700; } .columns.single { grid-template-columns: minmax(320px, 1fr); } +.structure-columns-single { + grid-template-columns: 1fr; +} + .column { border: 1px solid #d7e0ec; border-radius: 16px; @@ -685,6 +1035,10 @@ body { transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; } +.column.is-collapsed { + min-height: 0; +} + .column:hover { transform: translateY(-2px); border-color: #bfd0e4; @@ -721,10 +1075,30 @@ body { flex: 1; } +.column.is-collapsed .dropzone { + display: none; +} + +.btn-compact { + min-height: 34px; + padding: 0 12px; +} + .dropzone.drag-over { background: #ecf5ff; } +.structure-empty { + padding: 14px; + border: 1px dashed #cbd7e6; + border-radius: 14px; + background: #f8fbff; + color: #61718a; + font-size: 13px; + font-weight: 700; + text-align: center; +} + .field-card { background: rgba(255, 255, 255, 0.96); border: 1px solid #d7dfeb; @@ -815,8 +1189,9 @@ body { } .category-switch select, -.option-table select, .add-option-form input, +.option-card input[type='text'], +.option-table select, .option-table input[type='text'] { border: 1px solid #cbd5e1; border-radius: 10px; @@ -832,10 +1207,6 @@ body { margin-bottom: 12px; } -.option-table-wrap { - overflow-x: auto; -} - .option-table { width: 100%; border-collapse: collapse; @@ -852,6 +1223,9 @@ body { .option-table th { background: #f8fbff; color: #3d4c63; + position: sticky; + top: 0; + z-index: 1; } .option-table-group-row th { @@ -862,15 +1236,6 @@ body { letter-spacing: 0.04em; } -.option-row { - cursor: grab; -} - -.option-row.dragging { - opacity: 0.5; - background: #ecf5ff; -} - .drag-handle { display: inline-flex; align-items: center; @@ -886,20 +1251,186 @@ body { user-select: none; } +.option-card-list { + display: grid; + gap: 12px; +} + +.option-card { + display: grid; + gap: 14px; + padding: 16px; + border: 1px solid #d7e0ec; + border-radius: 18px; + background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%); + box-shadow: 0 10px 18px rgba(15, 23, 42, 0.04); + cursor: grab; + transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease; +} + +.option-card:hover { + transform: translateY(-1px); + border-color: #c4d4e7; + box-shadow: 0 14px 24px rgba(15, 23, 42, 0.06); +} + +.option-row.dragging { + opacity: 0.55; + background: #ecf5ff; + box-shadow: none; +} + +.option-card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.option-card-order { + display: flex; + align-items: flex-start; + gap: 12px; + min-width: 0; +} + +.option-card-title-block { + display: grid; + gap: 4px; + min-width: 0; +} + +.option-card-title-block strong { + color: #142033; + font-size: 15px; + line-height: 1.35; +} + +.option-card-meta { + color: #61718a; + font-size: 12px; + font-weight: 700; +} + +.option-card-actions { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.option-card-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 36px; + padding: 0 12px; + border: 1px solid #d7e1ee; + border-radius: 999px; + background: #f8fbff; + color: #294567; + font-size: 12px; + font-weight: 800; +} + +.option-card-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.option-empty-state { + padding: 18px; + border: 1px dashed #c8d5e7; + border-radius: 16px; + background: #f8fbff; + color: #61718a; + font-size: 14px; + font-weight: 700; + text-align: center; +} + .options-actions { margin-top: 12px; display: flex; justify-content: flex-end; } +.options-actions-sticky { + position: sticky; + bottom: 0; + z-index: 2; + padding-top: 12px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.98) 32%); +} + +.field-text-card-list { + display: grid; + gap: 12px; +} + +.field-text-card { + display: grid; + gap: 12px; + padding: 16px; + border: 1px solid #d7e0ec; + border-radius: 18px; + background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%); + box-shadow: 0 10px 18px rgba(15, 23, 42, 0.04); +} + +.field-text-card-head strong { + color: #142033; + font-size: 15px; +} + +.field-text-card-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.custom-fields-surface .builder-entity-form { + gap: 10px; + padding: 14px; +} + +.custom-fields-surface .builder-entity-grid { + gap: 10px; +} + +.custom-fields-surface .builder-group-stack { + gap: 10px; +} + +.custom-fields-surface .builder-group-card { + border-radius: 16px; +} + +.custom-fields-surface .builder-group-head { + padding: 10px 12px; +} + +.custom-fields-surface .builder-entity-card { + padding: 14px; + gap: 10px; + border-radius: 14px; +} + +.custom-fields-surface .builder-entity-card-head strong { + font-size: 14px; +} + .builder-entity-form { display: grid; gap: 12px; margin-bottom: 14px; - padding: 14px; + padding: 16px; border: 1px solid #d7e0ec; - border-radius: 16px; - background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%); + border-radius: 18px; + background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9); } .builder-entity-head h3, @@ -930,6 +1461,10 @@ body { font-weight: 800; } +.builder-entity-control input[type='text'] { + width: 100%; +} + .builder-entity-control-narrow { max-width: 180px; } @@ -946,9 +1481,10 @@ body { .builder-group-card { border: 1px solid #d7e0ec; - border-radius: 16px; + border-radius: 18px; background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%); overflow: hidden; + box-shadow: 0 10px 22px rgba(15, 23, 42, 0.04); } .builder-group-head { @@ -962,12 +1498,13 @@ body { } .builder-entity-card { - padding: 14px; + padding: 16px; border: 1px solid #e5ebf3; border-radius: 16px; background: #ffffff; display: grid; gap: 12px; + box-shadow: 0 8px 18px rgba(15, 23, 42, 0.04); } .builder-entity-card-head { @@ -998,6 +1535,14 @@ body { overflow-wrap: anywhere; } +.builder-inline-meta { + margin-top: 8px; + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + .builder-switch, .builder-switch-inline { display: inline-flex; @@ -1042,7 +1587,7 @@ body { .section-rule-card { display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; + grid-template-columns: auto auto minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 14px 16px; @@ -1063,6 +1608,20 @@ body { background: linear-gradient(180deg, #f5f8fc, #fbfdff); } +.section-rule-order { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 999px; + background: #eaf1ff; + color: #23457a; + font-size: 12px; + font-weight: 800; + box-shadow: inset 0 0 0 1px #cdddff; +} + .section-rule-actions { display: inline-flex; align-items: center; @@ -1172,9 +1731,6 @@ body { transition: none; } - .builder-accordion[open] .builder-panel-body { - animation: none; - } } @media (max-width: 1220px) { @@ -1182,9 +1738,30 @@ body { .columns { grid-template-columns: repeat(2, minmax(220px, 1fr)); } + + .structure-workspace { + grid-template-columns: 1fr; + } + + .structure-sidebar { + position: static; + grid-template-columns: repeat(2, minmax(220px, 1fr)); + } + + .structure-canvas { + overflow-x: visible; + } } @media (max-width: 900px) { + .builder-workspace { + grid-template-columns: 1fr; + } + + .builder-sidebar { + position: static; + } + .builder-hero, .builder-panel-head, .options-head, @@ -1197,6 +1774,10 @@ body { justify-content: flex-start; } + .builder-panel-meta { + justify-content: flex-start; + } + .builder-rule-layout { grid-template-columns: 1fr; } @@ -1212,11 +1793,26 @@ body { grid-template-columns: 1fr; } - .field-rule-row, - .conditional-clause-row { + .structure-sidebar { grid-template-columns: 1fr; } + .field-rule-row, + .conditional-clause-row, + .conditional-sentence-row, + .field-text-card-grid { + grid-template-columns: 1fr; + } + + .section-rule-card { + grid-template-columns: auto minmax(0, 1fr); + } + + .section-rule-toggle { + grid-column: 1 / -1; + justify-content: flex-start; + } + .field-rule-status { justify-content: flex-start; } diff --git a/backend/workflows/templates/workflows/form_builder.html b/backend/workflows/templates/workflows/form_builder.html index 1380495..576a138 100644 --- a/backend/workflows/templates/workflows/form_builder.html +++ b/backend/workflows/templates/workflows/form_builder.html @@ -10,10 +10,51 @@ {% block shell_body %} {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %} +
+ + +
{% trans "Deployment Configuration" %}

{% trans "Form Builder" %}

+

{% trans "Steuern Sie Struktur, Regeln und Inhalte Ihrer Standard-Workflows an einem Ort." %}

{% for key, label in form_types %} @@ -32,75 +73,150 @@
-
- +
+
-
+

{% trans "Struktur & Reihenfolge" %}

+

{% trans "Ordnen Sie Abschnitte und Felder in der Reihenfolge, in der sie im Formular erscheinen sollen." %}

+
+
+ {{ columns|length }} {% trans "Abschnitte" %} + {{ builder_summary.configurable_field_count }} {% trans "konfigurierbare Felder" %}
- {% trans "Geöffnet" %}
-
-
-
- {% for column in columns %} -
-
-

{{ column.title }}

- {% blocktrans trimmed with count=column.items|length %}{{ count }} Feld/Felder{% endblocktrans %} -
-
- {% for item in column.items %} -
-
-
{{ item.label }}
-
{{ item.field_name }}
-
-
- {% if item.is_custom %}{% trans "Eigen" %}{% endif %} - {% if item.locked %}{% trans "Fix" %}{% endif %} - {% if not item.is_visible %}{% endif %} - {% if item.is_required %}{% trans "Pflicht" %}{% endif %} -
-
- {% endfor %} -
-
- {% endfor %} -
-
-
+
+ + +
+ + +
+ {% for column in columns %} + {% if active_structure_section == column.key %} +
+
+
+

{{ column.title }}

+ {% blocktrans trimmed with count=column.items|length %}{{ count }} Feld/Felder{% endblocktrans %} +
+ {% trans "Geöffnet" %} +
+
+ {% for item in column.items %} +
+
+
{{ item.label }}
+
{{ item.field_name }}
+
+
+ {% if item.is_custom %}{% trans "Eigen" %}{% endif %} + {% if item.locked %}{% trans "Fix" %}{% endif %} + {% if not item.is_visible %}{% endif %} + {% if item.is_required %}{% trans "Pflicht" %}{% endif %} +
+
+ {% empty %} +
{% trans "Noch keine Felder in diesem Abschnitt." %}
+ {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+
+
+
+
+ +
+
-
+

{% trans "Sichtbarkeit & Regeln" %}

+

{% trans "Legen Sie fest, welche Teile sichtbar, erforderlich oder regelgesteuert sein sollen." %}

+
+
+ {{ builder_summary.hidden_field_count }} {% trans "ausgeblendet" %} + {{ builder_summary.hidden_section_count }} {% trans "versteckte Abschnitte" %}
- {% trans "Öffnen" %}
- -
+ +
-
- + {% if active_module == 'section-rules' %} +
+
-

{% trans "Abschnitte steuern" %}

- {% trans "Öffnen" %} +
+

{% trans "Abschnitte steuern" %}

+

{% trans "Reihenfolge und Sichtbarkeit der Formularabschnitte." %}

+
+
+ {{ section_rule_items|length }} {% trans "Abschnitte" %} +
-
-
{% csrf_token %} + +
{% for section in section_rule_items %} + {% if active_section_rules_section == section.key %}
+
{{ forloop.counter }}
+ {% endif %} {% endfor %}
-
+
-
+
+ {% elif active_module == 'field-rules' %} -
- +
+
-

{% trans "Feldregeln verwalten" %}

- {% trans "Öffnen" %} +
+

{% trans "Feldregeln verwalten" %}

+

{% trans "Steuern Sie Sichtbarkeit und Pflichtstatus für einzelne Felder." %}

+
+
+ {{ builder_summary.configurable_field_count }} {% trans "konfigurierbar" %} +
-
-
{% csrf_token %} + +
{% for group in field_rule_groups %} + {% if active_field_rules_section == group.key %}

{{ group.title }}

@@ -161,30 +294,34 @@
{{ item.label }} -
{{ item.field_name }}
+
+ {{ item.field_name }} +
+ {% if item.locked %} + {% trans "Fix" %} + {% elif not item.is_visible %} + + {% elif item.is_required %} + {% trans "Pflicht" %} + {% else %} + {% trans "Flexibel" %} + {% endif %} +
+
- - -
- {% if item.locked %} - {% trans "Fix" %} - {% elif not item.is_visible %} - - {% elif item.is_required %} - {% trans "Pflicht" %} - {% else %} - {% trans "Flexibel" %} - {% endif %} +
+ +
{% empty %} @@ -192,28 +329,44 @@ {% endfor %}
+ {% endif %} {% endfor %}
-
+
-
+ + {% elif form_type == 'onboarding' and active_module == 'conditional-rules' %} - {% if form_type == 'onboarding' %} -
- +
+
-

{% trans "Bedingte Logik" %}

- {% trans "Öffnen" %} +
+

{% trans "Bedingte Logik" %}

+

{% trans "Lassen Sie Felder abhängig von anderen Antworten ein- oder ausblenden." %}

+
+
+ {{ conditional_rule_items|length }} {% trans "Regeln" %} +
-
-
{% csrf_token %} + +
{% for item in conditional_rule_items %} + {% if active_conditional_target == item.target_key %}
@@ -228,6 +381,26 @@
+
+ {% trans "Sichtbar, wenn" %} +
+ {% with first_clause=item.clauses.0 second_clause=item.clauses.1 %} + {% if first_clause.field %} + {{ first_clause.field }} + {{ first_clause.operator }} + {% if first_clause.value %}{{ first_clause.value }}{% endif %} + {% else %} + {% trans "Keine Bedingung" %} + {% endif %} + {% if second_clause.field %} + {% trans "und" %} + {{ second_clause.field }} + {{ second_clause.operator }} + {% if second_clause.value %}{{ second_clause.value }}{% endif %} + {% endif %} + {% endwith %} +
+
{% trans "Steuert" %}
@@ -238,105 +411,94 @@ {% endfor %}
-
+
{% with first_clause=item.clauses.0 second_clause=item.clauses.1 %} -
-
- {% trans "Wenn" %} -
- - - +
+ {% trans "Zeige dieses Element, wenn" %} + + +
{% trans "Zusätzliche Bedingung" %} -
-
- {% trans "Und" %} -
- - - +
+ {% trans "Und zusätzlich" %} + + +
{% endwith %}
+
+ {% trans "Nutzen Sie die zusätzliche Bedingung nur, wenn ein zweites Kriterium wirklich nötig ist." %} +
+ {% endif %} {% endfor %}
-
+
-
+ {% endif %}
- + -
- +
+
-
+

{% trans "Optionen & Texte" %}

+

{% trans "Pflegen Sie Auswahlwerte, Feldtexte und benutzerdefinierte Erweiterungen." %}

+
+
+ {{ builder_summary.custom_field_count }} {% trans "eigene Felder" %} + {% if form_type == 'onboarding' %} + {{ builder_summary.custom_section_count }} {% trans "eigene Abschnitte" %} + {% endif %}
- {% trans "Öffnen" %}
-
-
+ +
-
- -
-

{% trans "Optionen verwalten" %}

- {% trans "Öffnen" %} -
-
-
+ {% if active_module == 'options' %} +
+
- - - + - ⋮⋮ - - - - - - - - - - {% empty %} - {% trans "Keine Optionen in dieser Kategorie." %} - {% endfor %} - - +
+ {% for item in option_items %} +
+ +
+
+ ⋮⋮ +
+ {{ item.label }} + #{{ forloop.counter }}{% if item.value %} · {{ item.value }}{% endif %} +
+
+
+ + +
+
+
+ + + +
+
+ {% empty %} +
{% trans "Keine Optionen in dieser Kategorie." %}
+ {% endfor %}
-
+
-
+ + {% elif active_module == 'field-texts' %} -
- +
+
-

{% trans "Feldtexte verwalten" %}

- {% trans "Öffnen" %} +
+

{% trans "Feldtexte verwalten" %}

+

{% trans "Überschreiben Sie Labels und Hilfetexte pro Feld." %}

+
-
-
{% csrf_token %} -
- - - - - - - - - - - - {% for group in field_text_groups %} - - - - {% for item in group.items %} - - - - - - - - {% empty %} - - {% endfor %} - {% endfor %} - -
{% trans "Feld" %}{% trans "Label (DE)" %}{% trans "Label (EN)" %}{% trans "Hilfetext (DE)" %}{% trans "Hilfetext (EN)" %}
{{ group.title }}
- - {{ item.field_name }} -
{% trans "Keine Feldkonfigurationen verfügbar." %}
+ + +
+ {% for group in field_text_groups %} + {% if active_field_texts_section == group.key %} + {% for item in group.items %} +
+ +
+
+ {{ item.label_override|default:item.field_name }} +
{{ item.field_name }}
+
+
+
+ + + + +
+
+ {% empty %} +
{% trans "Keine Feldkonfigurationen verfügbar." %}
+ {% endfor %} + {% endif %} + {% endfor %}
-
+
-
+ + {% elif form_type == 'onboarding' and active_module == 'custom-sections' %} - {% if form_type == 'onboarding' %} -
- +
+
-

{% trans "Eigene Abschnitte" %}

- {% trans "Öffnen" %} +
+

{% trans "Eigene Abschnitte" %}

+

{% trans "Erweitern Sie den Workflow um eigene inhaltliche Blöcke." %}

+
+
+ {{ builder_summary.custom_section_count }} {% trans "eigene Abschnitte" %} +
-
-
{% csrf_token %} @@ -496,13 +682,16 @@
{{ item.title }}
{{ item.section_key }}
+
+ {{ item.custom_field_count }} {% trans "Feld/Felder" %} +
- +
@@ -524,22 +713,25 @@
{% trans "Keine eigenen Abschnitte vorhanden." %}
{% endfor %}
-
+
-
- {% endif %} + + {% elif active_module == 'custom-fields' %} -
- +
+
-

{% trans "Eigene Felder" %}

- {% trans "Öffnen" %} +
+

{% trans "Eigene Felder" %}

+

{% trans "Erstellen Sie zusätzliche Eingaben innerhalb bestehender oder eigener Abschnitte." %}

+
+
+ {{ builder_summary.custom_field_count }} {% trans "eigene Felder" %} +
-
-
{% csrf_token %} @@ -606,8 +798,20 @@ {% csrf_token %} + +
{% for group in custom_field_groups %} + {% if active_custom_fields_section == group.key %}

{{ group.title }}

@@ -686,23 +890,25 @@ {% endfor %}
+ {% endif %} {% endfor %}
-
+
-
+ + {% elif active_module == 'preview' %} -
- +
+
-

{% trans "Live-Vorschau" %}

- {% trans "Öffnen" %} +
+

{% trans "Live-Vorschau" %}

+

{% trans "So wirkt die aktuelle Struktur für das aktive Formular." %}

+
-
-
{% for section in preview_sections %}
@@ -721,27 +927,16 @@ {% endfor %}
-
+ + {% endif %}
-
+ + + {% endblock %} {% block extra_scripts %} - {% endblock %} diff --git a/backend/workflows/views.py b/backend/workflows/views.py index 9f3faa0..4aa33ba 100644 --- a/backend/workflows/views.py +++ b/backend/workflows/views.py @@ -2189,6 +2189,13 @@ def form_builder_page(request): active_panel = (request.GET.get('panel') or '').strip() active_subpanel = (request.GET.get('subpanel') or '').strip() active_rules_panel = (request.GET.get('rules_panel') or '').strip() + active_module = (request.GET.get('module') or '').strip() + active_structure_section = (request.GET.get('structure_section') or '').strip() + active_field_rules_section = ((request.POST.get('field_rules_section') if request.method == 'POST' else '') or request.GET.get('field_rules_section') or '').strip() + active_field_texts_section = ((request.POST.get('field_texts_section') if request.method == 'POST' else '') or request.GET.get('field_texts_section') or '').strip() + active_custom_fields_section = ((request.POST.get('custom_fields_section') if request.method == 'POST' else '') or request.GET.get('custom_fields_section') or '').strip() + active_section_rules_section = ((request.POST.get('section_rules_section') if request.method == 'POST' else '') or request.GET.get('section_rules_section') or '').strip() + active_conditional_target = ((request.POST.get('conditional_target') if request.method == 'POST' else '') or request.GET.get('conditional_target') or '').strip() if form_type not in DEFAULT_FIELD_ORDER: form_type = 'onboarding' option_category = request.GET.get('option_category', 'department') @@ -2196,6 +2203,34 @@ def form_builder_page(request): if option_category not in option_categories: option_category = option_categories[0] + valid_modules = { + 'structure', + 'section-rules', + 'field-rules', + 'conditional-rules', + 'options', + 'field-texts', + 'custom-sections', + 'custom-fields', + 'preview', + } + + if not active_module: + if active_panel == 'builder-structure': + active_module = 'structure' + elif active_panel == 'builder-rules': + active_module = active_rules_panel or 'section-rules' + elif active_panel == 'builder-content': + active_module = active_subpanel or 'options' + else: + active_module = 'structure' + if active_module not in valid_modules: + active_module = 'structure' + if form_type != 'onboarding' and active_module == 'custom-sections': + active_module = 'options' + if form_type != 'onboarding' and active_module == 'conditional-rules': + active_module = 'field-rules' + if request.method == 'POST': delete_option_id = request.POST.get('delete_option_id', '').strip() delete_custom_field_id = request.POST.get('delete_custom_field_id', '').strip() @@ -2211,7 +2246,7 @@ def form_builder_page(request): option.delete() _audit(request, 'form_option_deleted', target_type='form_option', target_id=deleted_id, target_label=deleted_label) messages.success(request, 'Option wurde gelöscht.') - return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&panel=builder-content&subpanel=options#builder-content") + return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=options") if delete_custom_field_id: custom_field = FormCustomFieldConfig.objects.filter(id=delete_custom_field_id, form_type=form_type).first() if not custom_field: @@ -2222,7 +2257,7 @@ def form_builder_page(request): custom_field.delete() _audit(request, 'form_custom_field_deleted', target_type='form_custom_field', target_id=deleted_id, target_label=deleted_label) messages.success(request, 'Benutzerdefiniertes Feld wurde gelöscht.') - return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&panel=builder-content&subpanel=custom-fields#builder-content") + return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-fields") if delete_custom_section_id: custom_section = FormCustomSectionConfig.objects.filter(id=delete_custom_section_id, form_type=form_type).first() if not custom_section: @@ -2250,7 +2285,7 @@ def form_builder_page(request): details={'section_key': section_key, 'deleted_field_count': deleted_field_count}, ) messages.success(request, 'Benutzerdefinierter Abschnitt wurde gelöscht.') - return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&panel=builder-content&subpanel=custom-sections#builder-content") + return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-sections") action = request.POST.get('builder_action', '') if action == 'add_option': @@ -2301,7 +2336,7 @@ def form_builder_page(request): option.save(update_fields=['label', 'label_en', 'value', 'is_active', 'sort_order']) except IntegrityError: messages.error(request, f'Doppelte Bezeichnung in Kategorie: {next_label}') - return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option.category}&panel=builder-content&subpanel=options#builder-content") + return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option.category}&module=options") option_category = option.category _audit(request, 'form_options_saved', target_type='form_option', target_label=option_category, details={'count': len(option_ids)}) messages.success(request, 'Optionen wurden gespeichert.') @@ -2447,7 +2482,7 @@ def form_builder_page(request): cfg.select_options_en = (request.POST.get(f'custom_select_options_en_{cfg.id}') or '').strip() if cfg.field_type == FormCustomFieldConfig.FIELD_TYPE_SELECT and not cfg.select_options: messages.error(request, f'Auswahlfeld "{cfg.label}" benötigt mindestens eine Option.') - return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&panel=builder-content&subpanel=custom-fields#builder-content") + return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-fields") cfg.save() updated += 1 _audit(request, 'form_custom_fields_saved', target_type='form_custom_field', target_label=form_type, details={'count': updated}) @@ -2536,40 +2571,43 @@ def form_builder_page(request): elif action == 'apply_preset': preset_key = (request.POST.get('preset_key') or '').strip() if apply_form_preset(form_type, preset_key): - active_panel = 'builder-content' - active_subpanel = 'preview' + active_module = 'preview' _audit(request, 'form_preset_applied', target_type='form_config', target_label=form_type, details={'preset': preset_key}) messages.success(request, 'Preset wurde angewendet.') else: messages.error(request, 'Preset konnte nicht angewendet werden.') - if action in {'add_option', 'save_options', 'save_field_texts', 'add_custom_field', 'save_custom_fields', 'add_custom_section', 'save_custom_sections'}: - active_panel = 'builder-content' if action in {'add_option', 'save_options'}: - active_subpanel = 'options' + active_module = 'options' elif action == 'save_field_texts': - active_subpanel = 'field-texts' + active_module = 'field-texts' elif action in {'add_custom_field', 'save_custom_fields'}: - active_subpanel = 'custom-fields' + active_module = 'custom-fields' elif action in {'add_custom_section', 'save_custom_sections'}: - active_subpanel = 'custom-sections' + active_module = 'custom-sections' elif action in {'save_field_rules', 'save_section_rules', 'save_conditional_rules'}: - active_panel = 'builder-rules' + active_module = 'section-rules' if action == 'save_section_rules': - active_rules_panel = 'section-rules' + active_module = 'section-rules' elif action == 'save_field_rules': - active_rules_panel = 'field-rules' + active_module = 'field-rules' elif action == 'save_conditional_rules': - active_rules_panel = 'conditional-rules' + active_module = 'conditional-rules' redirect_target = f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}" - if active_panel: - redirect_target += f"&panel={active_panel}" - if active_subpanel: - redirect_target += f"&subpanel={active_subpanel}" - if active_rules_panel: - redirect_target += f"&rules_panel={active_rules_panel}" - if anchor == 'builder-content' or active_panel == 'builder-content': - redirect_target += "#builder-content" + if active_module: + redirect_target += f"&module={active_module}" + if active_structure_section: + redirect_target += f"&structure_section={active_structure_section}" + if active_section_rules_section and active_module == 'section-rules': + redirect_target += f"§ion_rules_section={active_section_rules_section}" + if active_field_rules_section and active_module == 'field-rules': + redirect_target += f"&field_rules_section={active_field_rules_section}" + if active_conditional_target and active_module == 'conditional-rules': + redirect_target += f"&conditional_target={active_conditional_target}" + if active_field_texts_section and active_module == 'field-texts': + redirect_target += f"&field_texts_section={active_field_texts_section}" + if active_custom_fields_section and active_module == 'custom-fields': + redirect_target += f"&custom_fields_section={active_custom_fields_section}" return redirect(redirect_target) default_names = list(DEFAULT_FIELD_ORDER.get(form_type, [])) @@ -2700,6 +2738,8 @@ def form_builder_page(request): section_rule_items = [] if section_order: + if active_structure_section not in section_order: + active_structure_section = section_order[0] fallback_section = section_order[-1] if section_order else '' custom_section_map = {cfg.section_key: cfg for cfg in custom_section_configs} for key in section_order: @@ -2717,6 +2757,9 @@ def form_builder_page(request): 'field_count': len([c for c in configs if (c.page_key or default_page_map.get(c.field_name, fallback_section)) == key]) + len([c for c in custom_field_configs if c.section_key == key]), } ) + section_rule_keys = [item['key'] for item in section_rule_items] + if section_rule_keys and active_section_rules_section not in section_rule_keys: + active_section_rules_section = section_rule_keys[0] field_rule_items = [] for cfg in configs: @@ -2747,6 +2790,11 @@ def form_builder_page(request): 'items': grouped_custom.get(key, []), } ) + custom_section_field_counts: dict[str, int] = {} + for cfg in custom_field_configs: + custom_section_field_counts[cfg.section_key] = custom_section_field_counts.get(cfg.section_key, 0) + 1 + for cfg in custom_section_configs: + cfg.custom_field_count = custom_section_field_counts.get(cfg.section_key, 0) field_rule_groups = [] if section_order: @@ -2761,6 +2809,9 @@ def form_builder_page(request): 'items': grouped_rules.get(key, []), } ) + field_rule_group_keys = [group['key'] for group in field_rule_groups] + if field_rule_group_keys and active_field_rules_section not in field_rule_group_keys: + active_field_rules_section = field_rule_group_keys[0] field_text_groups = [] if section_order: @@ -2776,6 +2827,12 @@ def form_builder_page(request): 'items': grouped_texts.get(key, []), } ) + field_text_group_keys = [group['key'] for group in field_text_groups] + if field_text_group_keys and active_field_texts_section not in field_text_group_keys: + active_field_texts_section = field_text_group_keys[0] + custom_field_group_keys = [group['key'] for group in custom_field_groups] + if custom_field_group_keys and active_custom_fields_section not in custom_field_group_keys: + active_custom_fields_section = custom_field_group_keys[0] conditional_rule_items = [] if form_type == 'onboarding': @@ -2837,6 +2894,9 @@ def form_builder_page(request): 'target_fields': target_fields, } ) + conditional_rule_keys = [item['target_key'] for item in conditional_rule_items] + if conditional_rule_keys and active_conditional_target not in conditional_rule_keys: + active_conditional_target = conditional_rule_keys[0] preview_sections = [] if section_order: @@ -2906,6 +2966,13 @@ def form_builder_page(request): 'active_panel': active_panel, 'active_subpanel': active_subpanel, 'active_rules_panel': active_rules_panel, + 'active_module': active_module, + 'active_structure_section': active_structure_section, + 'active_field_rules_section': active_field_rules_section, + 'active_field_texts_section': active_field_texts_section, + 'active_custom_fields_section': active_custom_fields_section, + 'active_section_rules_section': active_section_rules_section, + 'active_conditional_target': active_conditional_target, 'available_presets': FORM_PRESETS.get(form_type, {}), 'can_override_locked_builder_rules': can_override_locked_builder_rules, },