release: publish saldo 0.1.0

This commit is contained in:
2026-04-21 21:17:36 +02:00
commit 6f5e704739
95 changed files with 9196 additions and 0 deletions
+815
View File
@@ -0,0 +1,815 @@
{% extends "base.html" %}
{% block title %}Planung {{ month.label }} | {{ app_name }}{% endblock %}
{% block content %}
{% from "_ui.html" import avatar %}
<section class="planning-hero planning-hero-strong">
<div class="planning-title">
<div class="eyebrow">Planung</div>
<h1>{{ planning_heading }}</h1>
<p class="muted">Monat direkt auf dieser Seite pflegen: Einkommen, Kategorien, Einträge, Verteilung und Split-Personen.</p>
</div>
</section>
<section class="cards-grid cards-grid-four">
<article class="metric-card">
<span>Gesamteinkommen</span>
<strong>{{ summary.total_income|currency }}</strong>
</article>
<article class="metric-card">
<span>Gesamtkosten</span>
<strong>{{ summary.total_costs|currency }}</strong>
</article>
<article class="metric-card highlight">
<span>Restbetrag</span>
<strong>{{ summary.remainder|currency }}</strong>
<small>Aktuelle Verteilung {{ summary.allocation_total|currency }}</small>
</article>
<article class="metric-card soft-accent">
<span>Vorschläge</span>
<strong>{{ summary.suggestion_total|currency }}</strong>
<small>Bis Mindestziel offen</small>
</article>
</section>
<section class="planning-hub-grid">
<button type="button" class="summary-category-card utility-summary-card" data-open-dialog="income-dialog">
<div class="summary-card-head">
<strong>Einkommen</strong>
<img src="{{ url_for('static', filename='icons/pencil.svg') }}" alt="" class="ui-icon small-ui-icon">
</div>
<div class="summary-card-meta">
<span>{{ summary.total_income|currency }}</span>
<small>{{ month.incomes|length }} Zeilen</small>
</div>
</button>
<button type="button" class="summary-category-card utility-summary-card" data-open-dialog="split-people-dialog">
<div class="summary-card-head">
<strong>Personen für Splits</strong>
<img src="{{ url_for('static', filename='icons/pencil.svg') }}" alt="" class="ui-icon small-ui-icon">
</div>
<div class="summary-card-meta">
<span>{{ participants|length }}</span>
<small>{{ participant_summary.internal_count }} Nutzer, {{ participant_summary.external_count }} {{ "Gast" if participant_summary.external_count == 1 else "Gäste" }}</small>
</div>
</button>
</section>
<section class="account-board">
<article class="panel account-panel premium-panel">
<div class="panel-head account-head">
<div>
<h2>Gemeinschaftskonten</h2>
<small>{{ community_account_summary.shared_count }} Konten · {{ community_account_summary.personal_count }} privat</small>
</div>
<button class="ghost-button icon-button" type="button" data-open-dialog="community-account-create-dialog">
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
Konto
</button>
</div>
<div class="category-summary-grid">
{% for card in community_account_cards %}
{% if card.is_read_only %}
<div class="summary-category-card summary-static-card community-account-card">
<div class="summary-card-head">
<strong>{{ card.community_account.name }}</strong>
<span class="icon-label muted-label">Nur Anzeige</span>
</div>
<div class="summary-card-meta">
<span>{{ card.current_total|currency }}</span>
<small>Persönliche Auszahlung</small>
</div>
{% if card.delta %}
<div class="card-inline-note">
<span class="badge {% if card.delta > 0 %}badge-warn{% else %}badge-good{% endif %}">
{{ card.delta|currency }} zum Vormonat
</span>
</div>
{% endif %}
</div>
{% else %}
<button type="button" class="summary-category-card community-account-card" data-open-dialog="community-account-item-{{ card.community_account.id }}">
<div class="summary-card-head">
<strong>{{ card.community_account.name }}</strong>
<img src="{{ url_for('static', filename='icons/pencil.svg') }}" alt="" class="ui-icon small-ui-icon">
</div>
<div class="summary-card-meta">
<span>{{ card.current_total|currency }}</span>
<small>{{ card.assigned_budget_names|length }} Budgets</small>
</div>
{% if card.delta %}
<div class="card-inline-note">
<span class="badge {% if card.delta > 0 %}badge-warn{% else %}badge-good{% endif %}">
{{ card.delta|currency }} zum Vormonat
</span>
</div>
{% endif %}
{% if card.assigned_budget_names %}
<div class="card-inline-note">
<small>{{ card.assigned_budget_names|join(", ") }}</small>
</div>
{% endif %}
</button>
{% endif %}
{% endfor %}
</div>
</article>
</section>
<section class="account-board">
{% for account_data in planning_accounts %}
{% if account_data.categories %}
<article class="panel account-panel premium-panel">
<div class="panel-head account-head">
<div>
<h2>{{ account_data.account.name }}</h2>
<small>
Gesamtkosten {{ account_data.total|currency }}
{% if account_data.account.slug == "gemeinschaftskonto" %}
· Jährlich {{ (account_data.total * 12)|currency }}
{% endif %}
</small>
</div>
{% if account_data.account.slug == "sparen-und-verteilung" %}
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-area="distribution" data-placeholder="Name Sparkonto" data-dialog-label="Sparkonto">
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
Sparkonto
</button>
{% elif account_data.account.slug == "gemeinschaftskonto" %}
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-area="budget" data-placeholder="Name Budget">
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
Kategorie
</button>
{% else %}
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-account-id="{{ account_data.account.id }}">
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
Kategorie
</button>
{% endif %}
</div>
<div class="category-summary-grid">
{% for category_data in account_data.categories %}
<button type="button" class="summary-category-card {% if category_data.distribution_hint and category_data.distribution_hint.status %}range-status-{{ category_data.distribution_hint.status }}{% endif %}" data-open-dialog="{{ category_data.dialog_id }}">
<div class="summary-card-head">
<div>
<strong>{{ category_data.category.name }}</strong>
{% if category_data.category.description %}
<small>{{ category_data.category.description }}</small>
{% endif %}
</div>
<img src="{{ url_for('static', filename='icons/pencil.svg') }}" alt="" class="ui-icon small-ui-icon">
</div>
<div class="summary-card-meta">
<span>{{ category_data.total|currency }}</span>
<small>{{ category_data.entry_count }} Einträge</small>
</div>
{% if category_data.distribution_hint %}
<div class="card-inline-note">
<small>{{ category_data.distribution_hint.range_label if category_data.distribution_hint.range_label else "Rest nach Zielkonten" }}</small>
</div>
<div class="card-inline-note">
<small>Aktuell {{ category_data.distribution_hint.current_pct }} % vom Einkommen</small>
</div>
{% endif %}
{% if category_data.distribution_kind == "personal" and category_data.distribution_suggestion_total is not none %}
<div class="card-inline-note">
{% if category_data.distribution_suggestion_total > 0 %}
<span class="badge">Automatisch +{{ category_data.distribution_suggestion_total|currency }}</span>
{% elif category_data.distribution_suggestion_total < 0 %}
<span class="badge">Fehlen {{ (-category_data.distribution_suggestion_total)|currency }}</span>
{% else %}
<span class="badge">Automatisch ausgeglichen</span>
{% endif %}
</div>
{% endif %}
</button>
{% endfor %}
</div>
</article>
{% endif %}
{% endfor %}
</section>
<datalist id="category-suggestions">
{% for category in categories %}
<option value="{{ category.name }}">{{ category.account.name }}</option>
{% endfor %}
</datalist>
<dialog id="income-dialog" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<div class="stack-form">
<div class="dialog-section-head">
<div>
<h3>Einkommen</h3>
<small>{{ summary.total_income|currency }} · {{ month.incomes|length }} Zeilen</small>
</div>
</div>
<div class="sheet-card-grid">
{% for income in month.incomes|sort(attribute='sort_order') %}
<form method="post" action="{{ url_for('planning.update_income', label=month.label) }}" class="sheet-card">
<input type="hidden" name="income_id" value="{{ income.id }}">
<input type="hidden" name="return_dialog" value="income-dialog">
<input name="income_label" value="{{ income.label }}" placeholder="Name der Einkommenszeile" required>
<input name="amount" type="number" step="0.01" inputmode="decimal" value="{{ income.amount }}">
<div class="dialog-action-row dialog-action-spread">
<button class="ghost-button small-button" type="submit">Speichern</button>
{% if month.incomes|length > 1 %}
<button class="ghost-button danger-button small-button" type="submit" formaction="{{ url_for('planning.delete_income', label=month.label, income_id=income.id) }}">Löschen</button>
{% endif %}
</div>
</form>
{% endfor %}
</div>
<form method="post" action="{{ url_for('planning.create_income', label=month.label) }}" class="soft-form-section stack-form">
<div class="dialog-section-head">
<div>
<strong>Neue Einkommenszeile</strong>
<small>Zum Beispiel Gehalt, Bonus oder Nebenjob.</small>
</div>
</div>
<input name="income_label" placeholder="Name der Einkommenszeile" required>
<input name="amount" type="number" step="0.01" inputmode="decimal" placeholder="Betrag">
<button class="primary-button" type="submit">Einkommen anlegen</button>
</form>
</div>
</dialog>
<dialog id="split-people-dialog" class="app-dialog category-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<div class="stack-form">
<div class="dialog-section-head">
<div>
<h3>Personen für Splits</h3>
<small>{{ participant_summary.internal_count }} Nutzer, {{ participant_summary.external_count }} {{ "Gast" if participant_summary.external_count == 1 else "Gäste" }}</small>
</div>
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-participant" data-return-dialog="split-people-dialog">
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
Person
</button>
</div>
<div class="participant-card-grid">
{% for participant in participants %}
{% if participant.is_app_user %}
<div class="participant-manage-card summary-static-card">
<span class="list-row-main">
{{ avatar(participant.display_name, participant.avatar_url, participant.avatar_initials, "sm") }}
<strong>{{ participant.display_name }}</strong>
</span>
<small>Nutzer · automatisch aus Benutzerkonto</small>
<span class="icon-label muted-label">Automatisch</span>
</div>
{% else %}
<button class="participant-manage-card" type="button" data-open-dialog="participant-dialog-{{ participant.id }}">
<span class="list-row-main">
{{ avatar(participant.display_name, participant.avatar_url, participant.avatar_initials, "sm") }}
<strong>{{ participant.display_name }}</strong>
</span>
<small>Gast · {{ "aktiv" if participant.is_active else "inaktiv" }}</small>
<span class="icon-label"><img src="{{ url_for('static', filename='icons/pencil.svg') }}" alt="" class="ui-icon small-ui-icon">Details</span>
</button>
{% endif %}
{% endfor %}
</div>
</div>
</dialog>
<dialog id="community-account-create-dialog" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.create_community_account', label=month.label) }}" class="stack-form">
<div class="dialog-section-head">
<div>
<h3>Neues Gemeinschaftskonto</h3>
<small>Zum Beispiel separates Fixkosten- oder Reisekonto.</small>
</div>
</div>
<input name="name" placeholder="Kontoname" required>
<textarea name="description" rows="3" placeholder="Beschreibung optional"></textarea>
<button class="primary-button" type="submit">Konto anlegen</button>
</form>
</dialog>
{% for card in community_account_cards if not card.is_read_only %}
<dialog id="community-account-item-{{ card.community_account.id }}" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.update_community_account', label=month.label, community_account_id=card.community_account.id) }}" class="stack-form">
<input type="hidden" name="return_dialog" value="community-account-item-{{ card.community_account.id }}">
<h3>{{ card.community_account.name }}</h3>
<small>{{ card.current_total|currency }} aktuell{% if card.delta %} · {{ card.delta|currency }} zum Vormonat{% endif %}</small>
<input name="name" value="{{ card.community_account.name }}" required>
<textarea name="description" rows="3" placeholder="Beschreibung optional">{{ card.community_account.description or '' }}</textarea>
<div class="distribution-note-card">
<div>
<strong>Budgets zuweisen</strong>
<small>Diese Budget-Kategorien laufen über dieses Gemeinschaftskonto. Bereits anders zugewiesene Budgets sind hier nicht auswählbar.</small>
</div>
</div>
<div class="participant-chip-grid">
{% for category in categories if category.account.slug == "gemeinschaftskonto" %}
{% if category.community_account_id in [none, card.community_account.id] %}
<label class="participant-chip budget-assignment-chip">
<input
type="checkbox"
name="category_ids"
value="{{ category.id }}"
{% if category.community_account_id == card.community_account.id %}checked{% endif %}>
{{ category.name }}
</label>
{% endif %}
{% endfor %}
</div>
{% if card.assigned_budget_names %}
<small>Aktuell zugewiesen: {{ card.assigned_budget_names|join(", ") }}</small>
{% endif %}
<div class="dialog-action-row dialog-action-spread">
<button class="primary-button" type="submit">Konto speichern</button>
<button class="ghost-button danger-button" type="button" data-open-dialog="confirm-delete-community-account-{{ card.community_account.id }}">Konto löschen</button>
</div>
</form>
</dialog>
<dialog id="confirm-delete-community-account-{{ card.community_account.id }}" class="app-dialog confirm-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<div class="stack-form">
<h3>Konto wirklich löschen?</h3>
<p class="muted">`{{ card.community_account.name }}` wird ausgeblendet und seine Budget-Zuordnungen werden gelöst.</p>
<form method="post" action="{{ url_for('planning.delete_community_account', label=month.label, community_account_id=card.community_account.id) }}" class="dialog-action-row dialog-action-spread">
<button class="ghost-button" type="button" data-open-dialog="community-account-item-{{ card.community_account.id }}">Zurück</button>
<button class="primary-button danger-fill-button" type="submit">Jetzt löschen</button>
</form>
</div>
</dialog>
{% endfor %}
<dialog id="dialog-add-category" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.create_category', label=month.label) }}" class="stack-form">
<input type="hidden" name="area" value="budget" data-dialog-area>
<input type="hidden" name="account_id" value="" data-dialog-account-id>
<h3>Neue Kategorie</h3>
<select name="community_account_id" data-community-account-field>
<option value="">Gemeinschaftskonto zuweisen</option>
{% for community_account in community_accounts if community_account.account_type == "shared" %}
<option value="{{ community_account.id }}">{{ community_account.name }}</option>
{% endfor %}
</select>
<input name="name" list="category-suggestions" placeholder="Name Budget" data-dialog-name-placeholder required>
<button class="primary-button" type="submit">Kategorie anlegen</button>
</form>
</dialog>
<dialog id="dialog-add-entry" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.create_entry', label=month.label) }}" class="stack-form">
<h3>Neuen Eintrag anlegen</h3>
<input type="hidden" name="area" value="budget" data-dialog-area>
<input type="hidden" name="account_id" value="" data-dialog-account-id>
<input type="hidden" name="return_dialog" value="" data-dialog-return-dialog>
<input name="category_name" list="category-suggestions" data-dialog-category-name placeholder="Kategorie" required>
<input name="name" placeholder="Eintragsname" required>
<div class="sheet-card-grid" data-annual-sync-wrapper>
<label>
Monatlich
<input name="planned_amount" type="number" step="0.01" inputmode="decimal" placeholder="Monatlicher Betrag" data-annual-sync="monthly">
</label>
<label data-annual-visibility>
Jährlich
<input name="annual_amount" type="number" step="0.01" inputmode="decimal" placeholder="Jährlicher Betrag" data-annual-sync="yearly">
</label>
</div>
<select name="benefit_scope">
{% for option in benefit_options %}
<option value="{{ option.value }}">Betrifft {{ option.label }}</option>
{% endfor %}
</select>
<label class="check-label">
<input type="checkbox" name="is_allocation_target">
Sparkonto
<small>Zeigt Zielbereich und direkte Budgetpflege über die Karte in Planung.</small>
</label>
<textarea name="note" rows="3" placeholder="Notiz optional"></textarea>
<details class="split-picker">
<summary class="ghost-button">Mit anderen Personen teilen</summary>
<div class="participant-chip-grid split-panel">
{% for participant in participants %}
<label class="participant-chip"><input type="checkbox" name="participant_ids" value="{{ participant.id }}"> {{ participant.display_name }}</label>
{% endfor %}
</div>
</details>
<button class="primary-button" type="submit">Eintrag anlegen</button>
</form>
</dialog>
<dialog id="dialog-add-participant" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.create_participant', label=month.label) }}" class="stack-form" enctype="multipart/form-data">
<input type="hidden" name="return_dialog" value="" data-dialog-return-dialog>
<h3>Neue Person</h3>
<input name="name" placeholder="Name" required>
<label>
<span>Avatar hochladen</span>
<input name="avatar_file" type="file" accept="image/*">
</label>
<input name="avatar_url" placeholder="Avatar-URL optional">
<label class="check-label"><input type="checkbox" name="is_external" checked> Extern ohne App-Zugang</label>
<button class="primary-button" type="submit">Person anlegen</button>
</form>
</dialog>
{% for account_data in planning_accounts %}
{% for category_data in account_data.categories %}
{% if not category_data.is_personal_split %}
<dialog id="{{ category_data.dialog_id }}" class="app-dialog category-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<div class="stack-form">
<div class="dialog-section-head">
<div>
<h3>{{ category_data.category.name }}</h3>
<small>{{ category_data.total|currency }} · {{ category_data.entry_count }} Einträge</small>
</div>
{% if category_data.allow_new_entries %}
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-entry" data-account-id="{{ category_data.category.account_id }}" data-category-name="{{ category_data.category.name }}" data-return-dialog="{{ category_data.dialog_id }}" data-area="{{ 'budget' if category_data.category.account.slug == 'gemeinschaftskonto' else 'distribution' }}">
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
Eintrag
</button>
{% endif %}
</div>
{% if category_data.distribution_kind == "single" %}
{% set distribution_entry = category_data.entries|first %}
{% set distribution_suggestion = distribution_entry.distribution_suggestion if distribution_entry else none %}
{% if category_data.direct_entry %}
<form method="post" action="{{ url_for('planning.update_entry', label=month.label) }}" class="soft-form-section stack-form">
<input type="hidden" name="value_id" value="{{ category_data.direct_entry.value.id }}">
<input type="hidden" name="return_dialog" value="{{ category_data.dialog_id }}">
<input type="hidden" name="entry_name" value="{{ category_data.direct_entry.entry.name }}">
<input type="hidden" name="category_id" value="{{ category_data.direct_entry.entry.category_id }}">
<input type="hidden" name="benefit_scope" value="{{ category_data.direct_entry.entry.benefit_scope }}">
<div class="dialog-section-head">
<div>
<strong>Budget direkt anpassen</strong>
<small>Wenn `Sparkonto` aktiv ist, steuert dieser Eintrag das Budget direkt auf der Karte.</small>
</div>
</div>
<label class="check-label">
<input
type="checkbox"
name="is_allocation_target"
{% if category_data.direct_entry.entry.is_allocation_target %}checked{% endif %}>
Sparkonto
<small>Aktiviert Zielbereich und direkte Budgetpflege über diese Karte.</small>
</label>
<label>
Monatliches Budget
<input
name="planned_amount"
type="number"
step="0.01"
inputmode="decimal"
value="{{ category_data.direct_entry.value.planned_amount }}">
</label>
<label class="check-label">
<input
type="checkbox"
name="allocation_is_locked"
{% if category_data.direct_entry.distribution_allocation and category_data.direct_entry.distribution_allocation.is_locked %}checked{% endif %}>
Budget fixieren
</label>
<button class="primary-button" type="submit">Budget speichern</button>
</form>
{% endif %}
{% if category_data.direct_entry and category_data.direct_entry.entry.is_allocation_target %}
<div class="distribution-note-card">
<div>
<strong>Verteilung</strong>
<small>
Der Betrag in dieser Kategorie steuert direkt die monatliche Verteilung.
{% if category_data.distribution_hint %}
Zielbereich {{ category_data.distribution_hint.range_label }} vom Einkommen.
{% endif %}
</small>
</div>
{% if distribution_suggestion %}
<div class="row-actions">
<span class="badge">Noch offen {{ category_data.distribution_suggestion_total|currency }}</span>
<form method="post" action="{{ url_for('planning.accept_single_suggestion', label=month.label, account_id=distribution_suggestion.target_account_id) }}">
<input type="hidden" name="return_dialog" value="{{ category_data.dialog_id }}">
<button class="ghost-button small-button" type="submit">Übernehmen</button>
</form>
</div>
{% endif %}
</div>
{% if category_data.distribution_hint %}
<form method="post" action="{{ url_for('planning.update_distribution_settings', label=month.label, slug=category_data.distribution_account_slug) }}" class="soft-form-section stack-form">
<input type="hidden" name="return_dialog" value="{{ category_data.dialog_id }}">
<div class="dialog-section-head">
<div>
<strong>Zielbereich anpassen</strong>
<small>Der Vorschlag arbeitet innerhalb dieses Prozentbereichs.</small>
</div>
</div>
<div class="sheet-card-grid">
<label>
Von %
<input type="number" name="min_pct" min="0" max="100" step="1" value="{{ category_data.distribution_hint.min_pct }}">
</label>
<label>
Bis %
<input type="number" name="max_pct" min="0" max="100" step="1" value="{{ category_data.distribution_hint.max_pct }}">
</label>
</div>
<button class="ghost-button small-button" type="submit">Bereich speichern</button>
</form>
{% endif %}
{% endif %}
{% endif %}
{% if category_data.distribution_kind != "single" %}
<form method="post" action="{{ url_for('planning.update_category', label=month.label, category_id=category_data.category.id) }}" class="stack-form soft-form-section">
<input name="name" value="{{ category_data.category.name }}" required>
{% if category_data.category.account.slug == "gemeinschaftskonto" %}
<select name="community_account_id">
<option value="">Kein Gemeinschaftskonto</option>
{% for community_account in community_accounts if community_account.account_type == "shared" %}
<option value="{{ community_account.id }}" {% if category_data.category.community_account_id == community_account.id %}selected{% endif %}>{{ community_account.name }}</option>
{% endfor %}
</select>
{% endif %}
<textarea name="description" rows="3" placeholder="Beschreibung optional">{{ category_data.category.description or '' }}</textarea>
<div class="dialog-action-row">
<button class="primary-button" type="submit">Kategorie speichern</button>
</div>
</form>
{% endif %}
<div class="dialog-entry-list">
{% for item in category_data.entries %}
{% if not (category_data.direct_entry and category_data.direct_entry.value.id == item.value.id) %}
<button type="button" class="dialog-entry-row" data-open-dialog="{{ item.dialog_id }}">
<div>
<strong>{{ item.entry.name }}</strong>
<small>
{{ item.benefit_label }}
{% if item.share_names %} · Split: {{ item.share_names }}{% endif %}
{% if item.value.note %} · {{ item.value.note }}{% endif %}
</small>
</div>
<div class="entry-row-trailing">
{% if item.entry.category and item.entry.category.account and item.entry.category.account.slug == "gemeinschaftskonto" and item.benefit_users %}
<div class="stacked-avatars">
{% for user in item.benefit_users %}
{{ avatar(user.ui_name, user.avatar_url, user.avatar_initials, "sm", "stacked-avatar") }}
{% endfor %}
</div>
{% endif %}
<span>{{ item.amount|currency }}</span>
</div>
</button>
{% endif %}
{% endfor %}
</div>
<form method="post" action="{{ url_for('planning.delete_category', label=month.label, category_id=category_data.category.id) }}">
<button class="ghost-button danger-button" type="submit">Kategorie löschen</button>
</form>
</div>
</dialog>
{% endif %}
{% endfor %}
{% endfor %}
{% for account_data in planning_accounts %}
{% for category_data in account_data.categories %}
{% if category_data.is_personal_split %}
<dialog id="{{ category_data.dialog_id }}" class="app-dialog category-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<div class="stack-form">
<div class="dialog-section-head">
<div>
<h3>Persönliche Auszahlung</h3>
<small>{{ category_data.total|currency }} · {{ category_data.entry_count }} Einträge</small>
</div>
</div>
<div class="distribution-note-card">
<div>
<strong>Split</strong>
<small>Erst werden Sparen, Urlaub und Freizeit bedient. Der verbleibende Rest wird danach automatisch auf die persönliche Auszahlung verteilt.</small>
</div>
{% if category_data.distribution_suggestion_total > 0 %}
<span class="badge">Automatisch +{{ category_data.distribution_suggestion_total|currency }}</span>
{% elif category_data.distribution_suggestion_total < 0 %}
<span class="badge">Fehlen {{ (-category_data.distribution_suggestion_total)|currency }}</span>
{% else %}
<span class="badge">Automatisch ausgeglichen</span>
{% endif %}
</div>
<form method="post" action="{{ url_for('planning.update_personal_split', label=month.label) }}" class="soft-form-section stack-form">
<input type="hidden" name="return_dialog" value="{{ category_data.dialog_id }}">
<div class="dialog-section-head">
<div>
<strong>Aufteilung anpassen</strong>
<small>Wenn du einen Wert aenderst, wird der andere automatisch auf 100 % ergaenzt.</small>
</div>
</div>
<div class="sheet-card-grid">
<label>
{{ category_data.distribution_items[0].label if category_data.distribution_items|length > 0 else 'Person 1' }} in %
<input
type="number"
name="flo_pct"
min="0"
max="100"
step="1"
value="{{ personal_split.flo_pct }}"
data-personal-split="flo">
</label>
<label>
{{ category_data.distribution_items[1].label if category_data.distribution_items|length > 1 else 'Person 2' }} in %
<input
type="number"
name="desi_pct"
min="0"
max="100"
step="1"
value="{{ personal_split.desi_pct }}"
data-personal-split="desi">
</label>
</div>
<button class="ghost-button small-button" type="submit">Split speichern</button>
</form>
<div class="personal-split-grid">
{% for distribution_item in category_data.distribution_items %}
<div class="sheet-card compact-sheet-card">
<strong>{{ distribution_item.label }}</strong>
<span>{{ distribution_item.auto_amount|currency }}</span>
<small>Automatisch aus Restbetrag berechnet</small>
</div>
{% endfor %}
</div>
<div class="dialog-entry-list">
{% for item in category_data.entries %}
<div class="dialog-entry-row static-entry-row">
<div>
<strong>{{ item.entry.name }}</strong>
<small>
{{ item.benefit_label }}
{% if item.value.note %} · {{ item.value.note }}{% endif %}
</small>
</div>
<span>{{ item.amount|currency }}</span>
</div>
{% endfor %}
</div>
</div>
</dialog>
{% endif %}
{% endfor %}
{% endfor %}
{% for account_data in planning_accounts %}
{% for category_data in account_data.categories %}
{% for item in category_data.entries %}
<dialog id="{{ item.dialog_id }}" class="app-dialog entry-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.update_entry', label=month.label) }}" class="stack-form">
<input type="hidden" name="value_id" value="{{ item.value.id }}">
<input type="hidden" name="return_dialog" value="{{ category_data.dialog_id }}">
<div class="dialog-section-head">
<h3>{{ item.entry.name }}</h3>
</div>
<input name="entry_name" value="{{ item.entry.name }}" required>
<select name="category_id">
{% for category in categories %}
<option value="{{ category.id }}" {% if item.entry.category_id == category.id %}selected{% endif %}>{{ category.name }}</option>
{% endfor %}
</select>
{% if item.entry.category.account.slug == "gemeinschaftskonto" %}
<div class="sheet-card-grid" data-annual-sync-wrapper>
<label>
Monatlich
<input name="planned_amount" type="number" step="0.01" inputmode="decimal" value="{{ item.value.planned_amount }}" data-annual-sync="monthly">
</label>
<label>
Jährlich
<input name="annual_amount" type="number" step="0.01" inputmode="decimal" value="{{ '%.2f'|format(item.value.planned_amount * 12) }}" data-annual-sync="yearly">
</label>
</div>
{% else %}
<input name="planned_amount" type="number" step="0.01" inputmode="decimal" value="{{ item.value.planned_amount }}">
{% endif %}
<select name="benefit_scope">
{% for option in benefit_options %}
<option value="{{ option.value }}" {% if item.entry.benefit_scope == option.value %}selected{% endif %}>Betrifft {{ option.label }}</option>
{% endfor %}
</select>
<label class="check-label">
<input type="checkbox" name="is_allocation_target" {% if item.entry.is_allocation_target %}checked{% endif %}>
Sparkonto
<small>Zeigt Zielbereich und direkte Budgetpflege über die Karte in Planung.</small>
</label>
<textarea name="note" rows="4" placeholder="Notiz">{{ item.value.note or '' }}</textarea>
{% if item.is_distribution_entry and item.distribution_allocation %}
<div class="distribution-note-card">
<div>
<strong>Verteilung für {{ item.entry.name }}</strong>
<small>
Dieser Eintrag steuert den Zielbetrag in der Verteilung.
{% if item.distribution_hint %}
Zielbereich {{ item.distribution_hint.range_label }} vom Einkommen.
{% endif %}
</small>
</div>
{% if item.distribution_suggestion %}
<span class="badge">Noch offen {{ item.distribution_hint.remaining_amount if item.distribution_hint else item.distribution_suggestion.suggested_amount|currency }}</span>
{% endif %}
</div>
<label class="check-label"><input type="checkbox" name="allocation_is_locked" {% if item.distribution_allocation.is_locked %}checked{% endif %}> Verteilung fixieren</label>
{% endif %}
<details class="split-picker" {% if item.entry.share_rules %}open{% endif %}>
<summary class="ghost-button">Mit anderen Personen teilen</summary>
<div class="participant-chip-grid split-panel">
{% for participant in participants %}
<label class="participant-chip">
<input
type="checkbox"
name="participant_ids"
value="{{ participant.id }}"
{% if item.entry.share_rules|selectattr('participant_id', 'equalto', participant.id)|list %}checked{% endif %}>
{{ participant.display_name }}
</label>
{% endfor %}
</div>
</details>
<div class="dialog-action-row dialog-action-spread">
<button class="primary-button" type="submit">Speichern</button>
<button class="ghost-button danger-button" type="button" data-open-dialog="confirm-delete-entry-{{ item.value.id }}">Eintrag löschen</button>
</div>
</form>
</dialog>
<dialog id="confirm-delete-entry-{{ item.value.id }}" class="app-dialog confirm-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<div class="stack-form">
<h3>Eintrag wirklich löschen?</h3>
<p class="muted">`{{ item.entry.name }}` wird ausgeblendet und erscheint nicht mehr in der Planung.</p>
<form method="post" action="{{ url_for('planning.delete_entry', label=month.label, entry_id=item.entry.id) }}" class="dialog-action-row dialog-action-spread">
<input type="hidden" name="return_dialog" value="{{ category_data.dialog_id }}">
<button class="ghost-button" type="button" data-open-dialog="{{ item.dialog_id }}">Zurück</button>
<button class="primary-button danger-fill-button" type="submit">Jetzt löschen</button>
</form>
</div>
</dialog>
{% endfor %}
{% endfor %}
{% endfor %}
{% for participant in participants if not participant.is_app_user %}
<dialog id="participant-dialog-{{ participant.id }}" class="app-dialog">
<form method="dialog" class="dialog-close-row">
<button class="dialog-close-button" type="submit" aria-label="Schließen">×</button>
</form>
<form method="post" action="{{ url_for('planning.update_participant', label=month.label, participant_id=participant.id) }}" class="stack-form" enctype="multipart/form-data">
<input type="hidden" name="return_dialog" value="split-people-dialog">
<h3>Person bearbeiten</h3>
<input name="name" value="{{ participant.display_name }}" required>
<label>
<span>Avatar hochladen</span>
<input name="avatar_file" type="file" accept="image/*">
</label>
<input name="avatar_url" value="{{ participant.avatar_url or '' }}" placeholder="Avatar-URL optional">
<label class="check-label"><input type="checkbox" name="is_external" {% if participant.is_external %}checked{% endif %}> Extern ohne App-Zugang</label>
<label class="check-label"><input type="checkbox" name="is_active" {% if participant.is_active %}checked{% endif %}> Aktiv</label>
<button class="primary-button" type="submit">Speichern</button>
</form>
</dialog>
{% endfor %}
{% endblock %}