snapshot: preserve custom field parity across forms timeline and pdf
This commit is contained in:
@@ -51,6 +51,10 @@
|
||||
<span class="builder-stat-label">{% trans "Aktuell ausgeblendet" %}</span>
|
||||
<strong>{{ builder_summary.hidden_field_count }}</strong>
|
||||
</article>
|
||||
<article class="builder-stat-card">
|
||||
<span class="builder-stat-label">{% trans "Eigene Felder" %}</span>
|
||||
<strong>{{ builder_summary.custom_field_count }}</strong>
|
||||
</article>
|
||||
{% if form_type == 'onboarding' %}
|
||||
<article class="builder-stat-card">
|
||||
<span class="builder-stat-label">{% trans "Versteckte Abschnitte" %}</span>
|
||||
@@ -129,6 +133,7 @@
|
||||
<div class="field-name">{{ item.field_name }}</div>
|
||||
</div>
|
||||
<div class="badges">
|
||||
{% if item.is_custom %}<span class="badge">{% trans "Eigen" %}</span>{% endif %}
|
||||
{% if item.locked %}<span class="badge locked">{% trans "Fix" %}</span>{% endif %}
|
||||
{% if not item.is_visible %}<span class="badge hidden">{% trans "Ausgeblendet" %}</span>{% endif %}
|
||||
{% if item.is_required %}<span class="badge required">{% trans "Pflicht" %}</span>{% endif %}
|
||||
@@ -451,22 +456,134 @@
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="options-panel nested-accordion js-single-accordion" data-accordion-group="builder-content-subpanels" {% if active_subpanel == 'custom-fields' %}open{% endif %}>
|
||||
<summary class="nested-accordion-summary">
|
||||
<div class="options-head">
|
||||
<h2>{% trans "Eigene Felder" %}</h2>
|
||||
<span class="builder-panel-toggle">{% trans "Öffnen" %}</span>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="nested-accordion-body">
|
||||
<form class="add-option-form" method="post" action="/admin-tools/form-builder/?form_type={{ form_type }}&option_category={{ selected_option_category }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="builder_action" value="add_custom_field" />
|
||||
<input type="text" name="custom_label" placeholder="{% trans 'Label (DE)' %}" required />
|
||||
<input type="text" name="custom_label_en" placeholder="{% trans 'Label (EN, optional)' %}" />
|
||||
<select name="custom_section_key">
|
||||
{% for group in custom_field_groups %}
|
||||
<option value="{{ group.key }}">{{ group.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="custom_field_type">
|
||||
{% for value, label in custom_field_type_choices %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="number" name="custom_sort_order" min="0" value="0" placeholder="{% trans 'Sortierung' %}" />
|
||||
<label class="field-rule-control compact-inline">
|
||||
<span>{% trans "Pflicht" %}</span>
|
||||
<input type="checkbox" name="custom_is_required" />
|
||||
</label>
|
||||
<input type="text" name="custom_help_text" placeholder="{% trans 'Hilfetext (DE, optional)' %}" />
|
||||
<input type="text" name="custom_help_text_en" placeholder="{% trans 'Hilfetext (EN, optional)' %}" />
|
||||
<textarea name="custom_select_options" rows="3" placeholder="{% trans 'Optionen (eine pro Zeile, optional: wert|Label)' %}"></textarea>
|
||||
<textarea name="custom_select_options_en" rows="3" placeholder="{% trans 'Optionen EN (eine pro Zeile, optional: value|Label)' %}"></textarea>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Eigenes Feld hinzufügen" %}</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin-tools/form-builder/?form_type={{ form_type }}&option_category={{ selected_option_category }}">
|
||||
{% csrf_token %}
|
||||
<div class="option-table-wrap">
|
||||
<table class="option-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Schlüssel" %}</th>
|
||||
<th>{% trans "Abschnitt" %}</th>
|
||||
<th>{% trans "Typ" %}</th>
|
||||
<th>{% trans "Sortierung" %}</th>
|
||||
<th>{% trans "Label (DE)" %}</th>
|
||||
<th>{% trans "Label (EN)" %}</th>
|
||||
<th>{% trans "Pflicht" %}</th>
|
||||
<th>{% trans "Aktiv" %}</th>
|
||||
<th>{% trans "Löschen" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in custom_field_groups %}
|
||||
<tr class="option-table-group-row">
|
||||
<th colspan="9">{{ group.title }}</th>
|
||||
</tr>
|
||||
{% for item in group.items %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="hidden" name="custom_field_ids" value="{{ item.id }}" />
|
||||
<strong>{{ item.field_key }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<select name="custom_section_key_{{ item.id }}">
|
||||
{% for group_choice in custom_field_groups %}
|
||||
<option value="{{ group_choice.key }}" {% if group_choice.key == item.section_key %}selected{% endif %}>{{ group_choice.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="custom_field_type_{{ item.id }}">
|
||||
{% for value, label in custom_field_type_choices %}
|
||||
<option value="{{ value }}" {% if value == item.field_type %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" min="0" name="custom_sort_order_{{ item.id }}" value="{{ item.sort_order }}" /></td>
|
||||
<td>
|
||||
<input type="text" name="custom_label_{{ item.id }}" value="{{ item.label }}" required />
|
||||
<input type="text" name="custom_help_text_{{ item.id }}" value="{{ item.help_text }}" placeholder="{% trans 'Hilfetext (DE)' %}" />
|
||||
<textarea name="custom_select_options_{{ item.id }}" rows="2" placeholder="{% trans 'Optionen (DE)' %}">{{ item.select_options }}</textarea>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="custom_label_en_{{ item.id }}" value="{{ item.label_en }}" />
|
||||
<input type="text" name="custom_help_text_en_{{ item.id }}" value="{{ item.help_text_en }}" placeholder="{% trans 'Hilfetext (EN)' %}" />
|
||||
<textarea name="custom_select_options_en_{{ item.id }}" rows="2" placeholder="{% trans 'Optionen (EN)' %}">{{ item.select_options_en }}</textarea>
|
||||
</td>
|
||||
<td><input type="checkbox" name="custom_is_required_{{ item.id }}" {% if item.is_required %}checked{% endif %} /></td>
|
||||
<td><input type="checkbox" name="custom_is_active_{{ item.id }}" {% if item.is_active %}checked{% endif %} /></td>
|
||||
<td>
|
||||
<button class="btn btn-secondary" type="submit" name="delete_custom_field_id" value="{{ item.id }}" data-confirm="{% trans 'Eigenes Feld wirklich löschen?' %}">{% trans "Löschen" %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="9">{% trans "Keine eigenen Felder vorhanden." %}</td></tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="options-actions">
|
||||
<button class="btn btn-primary" type="submit" name="builder_action" value="save_custom_fields">{% trans "Eigene Felder speichern" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.js-single-accordion[data-accordion-group]');
|
||||
accordions.forEach((accordion) => {
|
||||
accordion.addEventListener('toggle', () => {
|
||||
if (!accordion.open) return;
|
||||
const group = accordion.dataset.accordionGroup;
|
||||
document.querySelectorAll(`.js-single-accordion[data-accordion-group="${group}"]`).forEach((peer) => {
|
||||
if (peer !== accordion) peer.open = false;
|
||||
});
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{% static 'workflows/js/form_builder.js' %}"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.js-single-accordion[data-accordion-group]');
|
||||
accordions.forEach((accordion) => {
|
||||
accordion.addEventListener('toggle', () => {
|
||||
if (!accordion.open) return;
|
||||
const group = accordion.dataset.accordionGroup;
|
||||
document.querySelectorAll(`.js-single-accordion[data-accordion-group="${group}"]`).forEach((peer) => {
|
||||
if (peer !== accordion) peer.open = false;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -37,6 +37,57 @@
|
||||
<main class="main">
|
||||
{% include 'workflows/includes/messages.html' %}
|
||||
|
||||
{% if ops_summary.show %}
|
||||
<section class="ops-overview-card">
|
||||
<div class="ops-overview-head">
|
||||
<div>
|
||||
<h2>{% trans "Operations Overview" %}</h2>
|
||||
<p>{% trans "Letzte Laufzeit- und Backup-Signale auf einen Blick." %}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ops-overview-grid">
|
||||
{% if ops_summary.can_view_jobs %}
|
||||
<article class="ops-stat-card">
|
||||
<span class="ops-stat-label">{% trans "Fehlgeschlagene Jobs (24h)" %}</span>
|
||||
<strong class="{% if ops_summary.failed_count_24h %}is-error{% endif %}">{{ ops_summary.failed_count_24h }}</strong>
|
||||
</article>
|
||||
<article class="ops-stat-card">
|
||||
<span class="ops-stat-label">{% trans "Erfolgreiche Jobs (24h)" %}</span>
|
||||
<strong>{{ ops_summary.success_count_24h }}</strong>
|
||||
</article>
|
||||
<article class="ops-stat-card">
|
||||
<span class="ops-stat-label">{% trans "Offene Starts (24h)" %}</span>
|
||||
<strong>{{ ops_summary.started_count_24h }}</strong>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% if ops_summary.can_manage_backups and ops_summary.backup_health %}
|
||||
<article class="ops-stat-card">
|
||||
<span class="ops-stat-label">{% trans "Backup-Status" %}</span>
|
||||
<strong>{{ ops_summary.backup_health.label }}</strong>
|
||||
<span class="mini">{{ ops_summary.backup_health.summary }}</span>
|
||||
</article>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if ops_summary.can_view_jobs and ops_summary.recent_failed_logs %}
|
||||
<div class="ops-failure-list">
|
||||
<div class="ops-failure-head">
|
||||
<h3>{% trans "Letzte Fehler" %}</h3>
|
||||
<a class="btn btn-secondary" href="/admin-tools/jobs/">{% trans "Job Monitor öffnen" %}</a>
|
||||
</div>
|
||||
<div class="ops-failure-items">
|
||||
{% for log in ops_summary.recent_failed_logs %}
|
||||
<article class="ops-failure-item">
|
||||
<strong>{{ log.task_name }}</strong>
|
||||
<span>{{ log.target_label|default:log.target_type }}</span>
|
||||
<code>{{ log.error_message|truncatechars:120 }}</code>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% for section in portal_app_sections %}
|
||||
{% if not forloop.first %}
|
||||
<div class="section-divider" aria-hidden="true"></div>
|
||||
|
||||
@@ -14,6 +14,45 @@
|
||||
|
||||
{% include 'workflows/includes/messages.html' %}
|
||||
|
||||
<section class="card">
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<label>{% trans "Fehlgeschlagene Jobs (24h)" %}</label>
|
||||
<div class="branding-inline-value">{{ job_summary.failed_count_24h }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{% trans "Erfolgreiche Jobs (24h)" %}</label>
|
||||
<div class="branding-inline-value">{{ job_summary.success_count_24h }}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{% trans "Offene Starts (24h)" %}</label>
|
||||
<div class="branding-inline-value">{{ job_summary.started_count_24h }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if job_summary.recent_failed %}
|
||||
<div class="table-wrap" style="margin-top:12px;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Zuletzt fehlgeschlagen" %}</th>
|
||||
<th>{% trans "Ziel" %}</th>
|
||||
<th>{% trans "Fehler" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in job_summary.recent_failed %}
|
||||
<tr>
|
||||
<td>{{ log.task_name }}</td>
|
||||
<td>{{ log.target_label|default:log.target_type }}</td>
|
||||
<td><code>{{ log.error_message|truncatechars:140 }}</code></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<form method="get" class="app-registry-filters">
|
||||
<div class="field">
|
||||
|
||||
@@ -60,12 +60,20 @@
|
||||
</div>
|
||||
<div class="grid">
|
||||
{% for field in section.fields %}
|
||||
{% if field.field.widget.input_type == 'checkbox' %}
|
||||
<div class="field inline-check field-full">
|
||||
{{ field }} {{ field.label_tag }}
|
||||
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
|
||||
{{ field.errors }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="field {% if field.name == 'notes' %}field-full{% endif %}">
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
|
||||
{{ field.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
{% with field=block.field %}
|
||||
{% if field.is_hidden %}
|
||||
{{ field }}
|
||||
{% elif field.name in onboarding_inline_checks %}
|
||||
{% elif field.name in onboarding_inline_checks or field.field.widget.input_type == 'checkbox' %}
|
||||
<div class="field inline-check field-full {% if section.key == 'abschluss' %}finish-check{% endif %}">
|
||||
{{ field }} {{ field.label_tag }}
|
||||
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
|
||||
@@ -102,7 +102,7 @@
|
||||
{% for field in block.fields %}
|
||||
{% if field.is_hidden %}
|
||||
{{ field }}
|
||||
{% elif field.name in onboarding_inline_checks %}
|
||||
{% elif field.name in onboarding_inline_checks or field.field.widget.input_type == 'checkbox' %}
|
||||
<div class="field inline-check field-full {% if section.key == 'abschluss' %}finish-check{% endif %}">
|
||||
{{ field }} {{ field.label_tag }}
|
||||
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
|
||||
|
||||
@@ -37,9 +37,15 @@
|
||||
.timeline-detail-row { display:grid; grid-template-columns:160px 1fr; gap:12px; font-size:13px; }
|
||||
.timeline-detail-row strong { color:#566886; }
|
||||
.timeline-detail-list { margin:0; padding-left:18px; color:#4f617f; }
|
||||
.timeline-custom-fields { margin: 0 0 20px; padding: 18px 20px; border: 1px solid #d9e3f8; border-radius: 20px; background: linear-gradient(180deg,#ffffff 0%,#f7faff 100%); box-shadow: 0 18px 40px rgba(23,39,90,.08); }
|
||||
.timeline-custom-fields h2 { margin: 0 0 14px; font-size: 18px; color: #20345f; }
|
||||
.timeline-custom-grid { display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px 16px; }
|
||||
.timeline-custom-item { padding: 12px 14px; border: 1px solid #d8e1f5; border-radius: 16px; background: #fff; }
|
||||
.timeline-custom-item strong { display:block; margin-bottom: 4px; color:#566886; font-size:12px; letter-spacing:.05em; text-transform:uppercase; }
|
||||
.timeline-custom-item span { color:#22324d; font-size:14px; line-height:1.45; }
|
||||
@media (max-width: 1160px) { .timeline-summary-grid { grid-template-columns:repeat(3, minmax(0,1fr)); } }
|
||||
@media (max-width: 820px) { .timeline-summary-grid { grid-template-columns:repeat(2, minmax(0,1fr)); } }
|
||||
@media (max-width: 700px) { .timeline-summary-grid { grid-template-columns:1fr; } .timeline-head { flex-direction:column; } .timeline-stamp { white-space:normal; } .timeline-detail-row { grid-template-columns:1fr; } }
|
||||
@media (max-width: 700px) { .timeline-summary-grid { grid-template-columns:1fr; } .timeline-custom-grid { grid-template-columns:1fr; } .timeline-head { flex-direction:column; } .timeline-stamp { white-space:normal; } .timeline-detail-row { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -80,6 +86,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if custom_field_details %}
|
||||
<section class="timeline-custom-fields">
|
||||
<h2>{% trans "Benutzerdefinierte Felder" %}</h2>
|
||||
<div class="timeline-custom-grid">
|
||||
{% for item in custom_field_details %}
|
||||
<div class="timeline-custom-item">
|
||||
<strong>{{ item.label }}</strong>
|
||||
<span>{{ item.value }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="timeline-list">
|
||||
{% for row in timeline_rows %}
|
||||
<article class="timeline-item" data-kind="{{ row.kind }}">
|
||||
|
||||
Reference in New Issue
Block a user