<template>
  <div class="club-operations-page">
    <header class="page-header card">
      <div>
        <p class="page-eyebrow">TT-Verein</p>
        <h2>{{ moduleTitle }}</h2>
        <p class="page-subtitle">{{ moduleSubtitle }}</p>
      </div>
      <div class="header-actions">
        <button
          v-if="moduleKey === 'events'"
          type="button"
          class="btn-primary"
          :disabled="!currentClub || eventSaving || !canWriteEvents"
          @click="saveEvent"
        >
          {{ eventSaving ? 'Speichert…' : 'Termin anlegen' }}
        </button>
        <button type="button" class="btn-secondary" :disabled="loading || !currentClub" @click="loadModuleData">
          {{ loading ? 'Lädt…' : 'Neu laden' }}
        </button>
      </div>
    </header>

    <section v-if="!currentClub" class="card empty-state">
      <h3>Kein Verein ausgewählt</h3>
      <p>Bitte zuerst einen Verein auswählen, um diesen Bereich zu öffnen.</p>
    </section>

    <template v-else>
      <p v-if="loadError" class="state-banner state-banner-error">{{ loadError }}</p>

      <section class="stats-grid">
        <article v-for="stat in moduleStats" :key="stat.label" class="card stat-card">
          <span class="stat-label">{{ stat.label }}</span>
          <strong class="stat-value">{{ stat.value }}</strong>
          <small v-if="stat.meta" class="stat-meta">{{ stat.meta }}</small>
        </article>
      </section>

      <section class="workspace-grid">
        <div class="workspace-main">
          <section v-if="moduleKey === 'documents'" class="card section-card">
            <div class="section-header">
              <h3>Dokumentenarbeitsbereich</h3>
              <button type="button" class="btn-secondary" @click="startCreateDocument">Neu</button>
            </div>
            <div class="template-grid">
              <article v-for="template in documentTemplates" :key="template.key" class="template-card">
                <div class="item-topline">
                  <strong>{{ template.title }}</strong>
                  <span class="item-badge">{{ displayDocumentType(template.documentType) }}</span>
                </div>
                <p class="item-meta">{{ template.description }}</p>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" @click="applyDocumentTemplate(template)">Vorlage übernehmen</button>
                </div>
              </article>
            </div>
            <div class="form-grid">
              <label>
                <span>Suche</span>
                <input v-model.trim="documentSearch" type="search" placeholder="Titel, Typ, Status, Datei" />
              </label>
              <label>
                <span>Titel</span>
                <input v-model.trim="documentForm.title" type="text" placeholder="Satzung, Protokoll, Nachweis" />
              </label>
              <label>
                <span>Typ</span>
                <select v-model="documentForm.documentType">
                  <option value="satzung">Satzung</option>
                  <option value="protokoll">Protokoll</option>
                  <option value="nachweis">Nachweis</option>
                  <option value="formular">Formular</option>
                  <option value="vertrag">Vertrag</option>
                  <option value="rechnung">Rechnung</option>
                  <option value="other">Sonstiges</option>
                </select>
              </label>
              <label>
                <span>Status</span>
                <select v-model="documentForm.status">
                  <option value="active">Aktiv</option>
                  <option value="draft">Entwurf</option>
                  <option value="archived">Archiviert</option>
                  <option value="obsolete">Veraltet</option>
                </select>
              </label>
              <label>
                <span>Sichtbarkeit</span>
                <select v-model="documentForm.visibilityScope">
                  <option value="board">Vorstand</option>
                  <option value="finance">Finanzen</option>
                  <option value="trainers">Trainer</option>
                  <option value="all">Alle Freigegebenen</option>
                </select>
              </label>
              <label>
                <span>Datei</span>
                <input :key="documentForm.id || 'new'" type="file" @change="onDocumentFileChange" />
              </label>
            </div>
            <div class="form-grid">
              <label>
                <span>Beschreibung</span>
                <RichTextEditor v-model="documentForm.description" placeholder="Ablage, Anlass, Bezug" />
              </label>
              <label>
                <span>Änderungshinweis</span>
                <RichTextEditor v-model="documentForm.changeNote" placeholder="Neue Version, Korrektur, Nachtrag" />
              </label>
            </div>
            <div class="form-grid">
              <label>
                <span>Belegtyp</span>
                <input v-model.trim="documentForm.linkedEntityType" type="text" placeholder="invoice, event, request" />
              </label>
              <label>
                <span>Beleg-ID</span>
                <input v-model.trim="documentForm.linkedEntityId" type="text" placeholder="123" />
              </label>
            </div>
            <div class="inline-actions">
              <button type="button" class="btn-primary" :disabled="documentSaving" @click="saveDocument">
                {{ documentSaving ? 'Speichert…' : (documentForm.id ? 'Version speichern' : 'Dokument anlegen') }}
              </button>
              <button type="button" class="btn-secondary" @click="startCreateDocument">Zurücksetzen</button>
              <button type="button" class="btn-secondary" :disabled="!documentForm.id" @click="archiveDocument(documentForm)">Archivieren</button>
            </div>
          </section>

          <section v-if="moduleKey === 'documents'" class="card section-card">
            <div class="section-header">
              <h3>Dokumente</h3>
            </div>
            <div v-if="filteredClubDocuments.length === 0" class="state-banner">Noch keine Dokumente im Vereinsbereich vorhanden.</div>
            <div v-else class="item-list">
              <article
                v-for="document in filteredClubDocuments"
                :key="document.id"
                class="item-card clickable-card"
                :class="{ active: selectedDocumentId === document.id }"
                @click="selectDocument(document)"
              >
                <div class="item-topline">
                  <strong>{{ document.title }}</strong>
                  <span class="item-badge">{{ displayDocumentType(document.documentType) }}</span>
                </div>
                <div class="item-topline">
                  <span class="item-badge">{{ displayDocumentStatus(document.status) }}</span>
                  <span class="item-badge">{{ displayVisibilityScope(document.visibilityScope) }}</span>
                </div>
                <p class="item-meta">{{ displayDocumentText(document.description) || 'Keine Beschreibung hinterlegt.' }}</p>
                <p class="item-meta">
                  Version {{ document.currentVersionNo || document.versionCount || 1 }}
                  <span v-if="document.latestVersion?.fileName"> · {{ document.latestVersion.fileName }}</span>
                  <span v-if="document.latestVersion?.fileSizeBytes"> · {{ formatFileSize(document.latestVersion.fileSizeBytes) }}</span>
                  <span v-if="document.latestVersion?.uploadedAt"> · zuletzt {{ formatDate(document.latestVersion.uploadedAt) }}</span>
                </p>
                <p class="item-meta">Verknüpfungen: {{ document.linkedEntityCount || 0 }}</p>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" @click.stop="selectDocument(document)">Bearbeiten</button>
                  <button type="button" class="btn-secondary" :disabled="!document.latestVersion" @click.stop="downloadDocument(document)">Download</button>
                  <button type="button" class="btn-secondary" @click.stop="archiveDocument(document)">Archivieren</button>
                  <button type="button" class="btn-danger" @click.stop="deleteDocument(document)">Löschen</button>
                </div>
              </article>
            </div>
            <div v-if="selectedDocument" class="selected-document-panel">
              <div class="section-header">
                <h4>Ausgewähltes Dokument</h4>
                <button type="button" class="btn-secondary" @click="startCreateDocument">Neu anlegen</button>
              </div>
              <div class="item-card">
                <div class="item-topline">
                  <strong>{{ selectedDocument.title }}</strong>
                  <span class="item-badge">{{ displayDocumentStatus(selectedDocument.status) }}</span>
                </div>
                <p class="item-meta">
                  {{ displayDocumentType(selectedDocument.documentType) }} · {{ displayVisibilityScope(selectedDocument.visibilityScope) }}
                  <span v-if="selectedDocument.archivedAt"> · archiviert {{ formatDate(selectedDocument.archivedAt) }}</span>
                </p>
                <p class="item-meta">{{ displayDocumentText(selectedDocument.description) || 'Keine Beschreibung hinterlegt.' }}</p>
                <p class="item-meta">
                  {{ selectedDocument.versionCount || 0 }} Version{{ (selectedDocument.versionCount || 0) === 1 ? '' : 'en' }}
                  <span v-if="selectedDocument.links?.length"> · {{ selectedDocument.links.length }} Verknüpfung{{ selectedDocument.links.length === 1 ? '' : 'en' }}</span>
                </p>
              </div>
              <div class="section-header">
                <h4>Versionen</h4>
              </div>
              <div v-if="selectedDocument.versions.length === 0" class="state-banner">Keine Versionen vorhanden.</div>
              <div v-else class="item-list compact-list">
                <article v-for="version in selectedDocument.versions" :key="`${selectedDocument.id}-${version.versionNo}`" class="item-card compact-card">
                  <div class="item-topline">
                    <strong>Version {{ version.versionNo }}</strong>
                    <span class="item-badge">{{ formatDate(version.uploadedAt) }}</span>
                  </div>
                  <p class="item-meta">
                    {{ version.fileName }}
                    <span v-if="version.fileSizeBytes"> · {{ formatFileSize(version.fileSizeBytes) }}</span>
                  </p>
                  <p v-if="version.changeNote" class="item-meta">{{ displayDocumentText(version.changeNote) }}</p>
                  <div class="inline-actions">
                    <button type="button" class="btn-secondary" @click="downloadDocumentVersion(selectedDocument, version)">Download</button>
                  </div>
                </article>
              </div>
            </div>
          </section>

          <section v-if="moduleKey === 'documents'" class="card section-card">
            <div class="section-header">
              <h3>Belegquellen und aktuelle Artefakte</h3>
            </div>
            <div class="table-wrap">
              <table class="data-table">
                <thead>
                  <tr>
                    <th>Quelle</th>
                    <th>Umfang</th>
                    <th>Status</th>
                    <th>Ziel</th>
                  </tr>
                </thead>
                <tbody>
                  <tr>
                    <td>Abrechnungsvorlagen</td>
                    <td>{{ billingTemplates.length }}</td>
                    <td>{{ billingTemplates.length ? 'Vorhanden' : 'Leer' }}</td>
                    <td><button type="button" class="btn-secondary" @click="openRoute('/billing')">Öffnen</button></td>
                  </tr>
                  <tr>
                    <td>Erzeugte Abrechnungen</td>
                    <td>{{ billingRuns.length }}</td>
                    <td>{{ billingRuns.length ? 'Vorhanden' : 'Leer' }}</td>
                    <td><button type="button" class="btn-secondary" @click="openRoute('/billing')">Öffnen</button></td>
                  </tr>
                  <tr>
                    <td>Rechnungen</td>
                    <td>{{ invoices.length }}</td>
                    <td>{{ invoicesWithReferenceCount }} mit Belegbezug</td>
                    <td><button type="button" class="btn-secondary" @click="openRoute('/club-invoices')">Öffnen</button></td>
                  </tr>
                </tbody>
              </table>
            </div>
            <div v-if="recentDocumentArtifacts.length === 0" class="state-banner">Noch keine Dokumentartefakte vorhanden.</div>
            <div v-else class="item-list">
              <article v-for="artifact in recentDocumentArtifacts" :key="artifact.key" class="item-card">
                <div class="item-topline">
                  <strong>{{ artifact.title }}</strong>
                  <span class="item-badge">{{ artifact.type }}</span>
                </div>
                <p class="item-meta">{{ artifact.meta }}</p>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'events'" class="card section-card">
            <div class="section-header">
              <h3>{{ eventForm.id ? 'Veranstaltung bearbeiten' : 'Veranstaltung anlegen' }}</h3>
              <button type="button" class="btn-secondary" :disabled="!canWriteEvents" @click="resetEventForm">Neu</button>
            </div>
            <div class="form-grid">
              <label>
                <span>Titel</span>
                <input v-model.trim="eventForm.title" type="text" :disabled="!canWriteEvents" placeholder="z. B. Vereinsmeisterschaft" />
              </label>
              <label>
                <span>Terminart</span>
                <select v-model="eventForm.eventType" :disabled="!canWriteEvents">
                  <option value="club_event">Vereinsveranstaltung</option>
                  <option value="meeting">Sitzung</option>
                  <option value="tournament">Turnier</option>
                  <option value="social">Geselligkeit</option>
                  <option value="workshop">Workshop</option>
                  <option value="other">Sonstige</option>
                </select>
              </label>
              <label>
                <span>Status</span>
                <select v-model="eventForm.status" :disabled="!canWriteEvents">
                  <option value="planning">Planung</option>
                  <option value="invited">Eingeladen</option>
                  <option value="confirmed">Bestätigt</option>
                  <option value="done">Durchgeführt</option>
                  <option value="cancelled">Abgesagt</option>
                </select>
              </label>
              <label>
                <span>Start</span>
                <input v-model="eventForm.startDate" type="date" :disabled="!canWriteEvents" />
              </label>
              <label>
                <span>Ende</span>
                <input v-model="eventForm.endDate" type="date" :disabled="!canWriteEvents" />
              </label>
              <label>
                <span>Frist</span>
                <input v-model="eventForm.registrationDeadline" type="date" :disabled="!canWriteEvents" />
              </label>
              <label>
                <span>Verantwortlich</span>
                <input v-model.trim="eventForm.location" type="text" :disabled="!canWriteEvents" placeholder="Ort oder Verantwortlicher" />
              </label>
              <label class="full-width">
                <span>Beschreibung / Nacharbeit</span>
                <textarea v-model.trim="eventForm.description" rows="3" :disabled="!canWriteEvents" placeholder="Fristen, Kommunikation, To-dos, Aufgabenbezug"></textarea>
              </label>
            </div>
          </section>

          <section v-if="moduleKey === 'events'" class="card section-card">
            <div class="section-header">
              <h3>Anstehende Veranstaltungen</h3>
            </div>
            <div v-if="calendarEvents.length === 0" class="state-banner">Keine eigenen Vereinstermine im gewählten Zeitraum.</div>
            <div v-else class="item-list">
              <article
                v-for="event in calendarEvents"
                :key="event.id"
                class="item-card clickable-card"
                :class="{ active: eventForm.id === event.id }"
                @click="selectEvent(event)"
              >
                <div class="item-topline">
                  <strong>{{ event.title }}</strong>
                  <span class="item-badge">{{ displayEventType(event.eventType) }}</span>
                </div>
                <div class="item-topline">
                  <span class="item-badge">{{ displayEventStatus(event.status) }}</span>
                  <span class="item-badge">{{ event.category || 'Termin' }}</span>
                </div>
                <p class="item-meta">
                  {{ formatDate(event.startDate) }} bis {{ formatDate(event.endDate || event.startDate) }}
                  <span v-if="event.registrationDeadline"> · Frist {{ formatDate(event.registrationDeadline) }}</span>
                </p>
                <p v-if="event.organizerUserId" class="item-meta">Zuständigkeit hinterlegt</p>
                <p v-if="event.location" class="item-meta">Ort/Verantwortlich: {{ event.location }}</p>
                <p v-if="event.description" class="item-meta">{{ event.description }}</p>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" :disabled="!canWriteEvents" @click.stop="selectEvent(event)">Bearbeiten</button>
                  <button type="button" class="btn-secondary" @click="openRoute('/calendar')">Kalender</button>
                  <button
                    type="button"
                    class="btn-danger"
                    :disabled="!canWriteEvents"
                    @click="deleteEvent(event)"
                  >
                    Löschen
                  </button>
                </div>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'sponsors'" class="card section-card">
            <div class="section-header">
              <h3>Sponsoring-Pipeline</h3>
            </div>
            <div v-if="sponsoringRequests.length === 0" class="state-banner">Keine Sponsoringanfragen vorhanden.</div>
            <div v-else class="item-list">
              <article v-for="request in sponsoringRequests" :key="request.id" class="item-card">
                <div class="item-topline">
                  <strong>{{ request.subject || displayPersonName(request) }}</strong>
                  <span class="item-badge">{{ displayRequestStatus(request.status) }}</span>
                </div>
                <p class="item-meta">{{ displayPersonName(request) }} · {{ request.email || 'Keine E-Mail' }}</p>
                <p v-if="request.workflowStage" class="item-meta">{{ request.workflowStage }}</p>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" :disabled="!canWriteSponsors" @click="prefillSponsorFromRequest(request)">
                    Als Sponsor übernehmen
                  </button>
                  <button type="button" class="btn-secondary" @click="openRoute('/club-requests')">
                    In Anfragen öffnen
                  </button>
                </div>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'sponsors'" class="card section-card">
            <div class="section-header">
              <h3>Sponsoren und Sponsoringrechnungen</h3>
            </div>
            <div class="two-column-grid sponsor-grid">
              <div>
                <div class="section-header">
                  <h4>Sponsorenstamm</h4>
                  <button type="button" class="btn-secondary" :disabled="!canWriteSponsors" @click="startCreateSponsorParty">
                    Neu
                  </button>
                </div>
                <form class="sponsor-form card" @submit.prevent="submitSponsorParty">
                  <div class="form-grid">
                    <label>
                      <span>Name</span>
                      <input v-model.trim="sponsorPartyForm.name" type="text" :disabled="!canWriteSponsors" placeholder="Sponsor, Firma oder Verband" required />
                    </label>
                    <label>
                      <span>Ansprechpartner</span>
                      <input v-model.trim="sponsorPartyForm.contactName" type="text" :disabled="!canWriteSponsors" />
                    </label>
                  </div>
                  <div class="form-grid">
                    <label>
                      <span>E-Mail</span>
                      <input v-model.trim="sponsorPartyForm.email" type="email" :disabled="!canWriteSponsors" />
                    </label>
                    <label>
                      <span>Telefon</span>
                      <input v-model.trim="sponsorPartyForm.phone" type="text" :disabled="!canWriteSponsors" />
                    </label>
                  </div>
                  <div class="form-grid">
                    <label>
                      <span>Status</span>
                      <select v-model="sponsorPartyForm.status" :disabled="!canWriteSponsors">
                        <option value="active">Aktiv</option>
                        <option value="prospect">Interessent</option>
                        <option value="paused">Pausiert</option>
                        <option value="ended">Beendet</option>
                      </select>
                    </label>
                    <label>
                      <span>Vertragsreferenz</span>
                      <input v-model.trim="sponsorPartyForm.contractReference" type="text" :disabled="!canWriteSponsors" />
                    </label>
                  </div>
                  <div class="form-grid">
                    <label>
                      <span>Vertragsbeginn</span>
                      <input v-model="sponsorPartyForm.validFrom" type="date" :disabled="!canWriteSponsors" />
                    </label>
                    <label>
                      <span>Vertragsende</span>
                      <input v-model="sponsorPartyForm.validTo" type="date" :disabled="!canWriteSponsors" />
                    </label>
                  </div>
                  <div class="form-grid">
                    <label>
                      <span>Ort</span>
                      <input v-model.trim="sponsorPartyForm.city" type="text" :disabled="!canWriteSponsors" />
                    </label>
                    <label>
                      <span>PLZ</span>
                      <input v-model.trim="sponsorPartyForm.postalCode" type="text" :disabled="!canWriteSponsors" />
                    </label>
                  </div>
                  <label>
                    <span>Adresse</span>
                    <input v-model.trim="sponsorPartyForm.street" type="text" :disabled="!canWriteSponsors" />
                  </label>
                  <label>
                    <span>Notizen</span>
                    <textarea v-model.trim="sponsorPartyForm.notes" rows="3" :disabled="!canWriteSponsors"></textarea>
                  </label>
                <div class="inline-actions">
                  <button type="submit" class="btn-primary" :disabled="!canWriteSponsors || sponsorSaving">
                    {{ sponsorSaving ? 'Speichert…' : 'Sponsor speichern' }}
                  </button>
                  <button type="button" class="btn-secondary" @click="startCreateSponsorParty">Zurücksetzen</button>
                  <button type="button" class="btn-secondary" :disabled="!selectedSponsorParty" @click="openSponsorInvoiceWorkflow(selectedSponsorParty)">
                    Rechnung vorbereiten
                  </button>
                  <button type="button" class="btn-danger" :disabled="!canWriteSponsors || !sponsorPartyForm.id" @click="deleteSponsorParty">
                    Löschen
                  </button>
                </div>
              </form>
                <h4>Rechnungsparteien</h4>
                <div v-if="sponsorParties.length === 0" class="state-banner">Keine Sponsorenparteien angelegt.</div>
                <div v-else class="item-list compact-list">
                  <article
                    v-for="party in sponsorParties"
                    :key="party.id"
                    class="item-card compact-card clickable-card"
                    :class="{ active: selectedSponsorPartyId === party.id }"
                    @click="selectSponsorParty(party)"
                  >
                    <div class="item-topline">
                      <strong>{{ party.name }}</strong>
                      <span class="item-badge">{{ displaySponsorStatus(party.status) }}</span>
                    </div>
                    <p class="item-meta">{{ party.contactName || party.email || 'Kein Kontakt gepflegt' }}</p>
                    <p class="item-meta">{{ displaySponsorPeriod(party) }}</p>
                    <p class="item-meta">{{ party.contractReference || 'Keine Vertragsreferenz' }}</p>
                    <p class="item-meta">{{ sponsorPartyInvoiceCount(party) }} Sponsoringrechnung{{ sponsorPartyInvoiceCount(party) === 1 ? '' : 'en' }}</p>
                    <div class="inline-actions">
                      <button type="button" class="btn-secondary" @click.stop="openSponsorInvoiceWorkflow(party)">Rechnung anlegen</button>
                    </div>
                    <small v-if="party.notes" class="item-meta">{{ party.notes }}</small>
                  </article>
                </div>
              </div>
              <div>
                <h4>Rechnungen</h4>
                <div v-if="selectedSponsorParty" class="state-banner sponsor-workflow-banner">
                  <strong>{{ selectedSponsorParty.name }}</strong>
                  <span>ist für die Rechnungsanlage ausgewählt. {{ selectedSponsorPartyInvoices.length }} zugehörige Rechnung{{ selectedSponsorPartyInvoices.length === 1 ? '' : 'en' }} vorhanden.</span>
                </div>
                <div v-if="sponsorInvoices.length === 0" class="state-banner">Keine Sponsoringrechnungen vorhanden.</div>
                <div v-else class="item-list compact-list">
                  <article v-for="invoice in sponsorInvoices" :key="invoice.id" class="item-card compact-card">
                    <strong>{{ invoice.invoiceNumber || `Rechnung #${invoice.id}` }}</strong>
                    <p class="item-meta">{{ invoice.party?.name || 'Ohne Partei' }} · {{ formatCurrency(invoice.grossAmountCents, invoice.currencyCode) }}</p>
                    <p v-if="invoice.partyId && selectedSponsorParty && Number(invoice.partyId) === Number(selectedSponsorParty.id)" class="item-meta">Diese Rechnung gehört zur aktuellen Auswahl.</p>
                  </article>
                </div>
              </div>
            </div>
          </section>

          <section v-if="moduleKey === 'fees'" class="card section-card">
            <div class="section-header">
              <h3>Beitragsgruppen aus Mitgliedsdaten</h3>
            </div>
            <div v-if="feeGroups.length === 0" class="state-banner">Noch keine Beitragsgruppen in Mitgliedsdaten erkennbar.</div>
            <div v-else class="table-wrap">
              <table class="data-table">
                <thead>
                  <tr>
                    <th>Gruppe</th>
                    <th>Mitglieder</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="group in feeGroups" :key="group.code">
                    <td>{{ group.code }}</td>
                    <td>{{ group.count }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </section>

          <section v-if="moduleKey === 'fees'" class="card section-card">
            <div class="section-header">
              <h3>Beitragsrelevante Qualitätshinweise</h3>
            </div>
            <div class="item-list">
              <article class="item-card">
                <div class="item-topline">
                  <strong>Mitglieder ohne Beitragsgruppe</strong>
                  <span class="item-badge">{{ membersWithoutFeeCode.length }}</span>
                </div>
                <p class="item-meta">Diese Mitglieder sind noch keiner erkennbaren Beitragslogik zugeordnet.</p>
              </article>
              <article class="item-card">
                <div class="item-topline">
                  <strong>Mitglieder ohne SEPA-Hinweis</strong>
                  <span class="item-badge">{{ membersMissingSepaReference.length }}</span>
                </div>
                <p class="item-meta">Diese Datensätze brauchen voraussichtlich noch eine saubere Zahlungszuordnung.</p>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'fees'" class="card section-card">
            <div class="section-header">
              <h3>Beitragsstamm</h3>
            </div>
            <div v-if="configuredFeeRules.length === 0" class="state-banner">Noch keine Beitragsregeln gespeichert.</div>
            <div v-else class="item-list">
              <article v-for="rule in configuredFeeRules" :key="rule.id" class="item-card">
                <div class="item-topline">
                  <strong>{{ rule.code || rule.label || 'Regel' }}</strong>
                  <span class="item-badge">{{ displayFeeCycle(rule.cycle) }}</span>
                </div>
                <p class="item-meta">
                  {{ rule.label || 'Ohne Bezeichnung' }} · {{ formatCurrency(rule.amountCents) }}
                </p>
                <p v-if="rule.note" class="item-meta">{{ rule.note }}</p>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'fees'" class="card section-card">
            <div class="section-header">
              <h3>Mitglieder mit Zahlungsbezug</h3>
            </div>
            <div v-if="memberFeeRows.length === 0" class="state-banner">Keine Mitgliederdaten für Beitragszuordnung vorhanden.</div>
            <div v-else class="table-wrap">
              <table class="data-table">
                <thead>
                  <tr>
                    <th>Mitglied</th>
                    <th>Beitragsgruppe</th>
                    <th>Offene Forderungen</th>
                    <th>Offener Betrag</th>
                    <th>Letzter Status</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="row in memberFeeRows" :key="row.id">
                    <td>{{ row.name }}</td>
                    <td>{{ row.feeCode }}</td>
                    <td>{{ row.openClaims }}</td>
                    <td>{{ formatCurrency(row.openAmountCents) }}</td>
                    <td>{{ row.latestStatus }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </section>

          <section v-if="moduleKey === 'payments'" class="card section-card">
            <div class="section-header">
              <h3>Zahlungs-Arbeitsliste</h3>
            </div>
            <div class="worklist-summary">
              <span class="worklist-chip">Überfällig: {{ overduePaymentClaims.length }}</span>
              <span class="worklist-chip">Bald fällig: {{ dueSoonPaymentClaims.length }}</span>
              <span class="worklist-chip">Mahnstufen > 0: {{ paymentClaims.filter((claim) => claim.reminderLevel > 0).length }}</span>
              <span class="worklist-chip">Rest offen: {{ formatCurrency(totalClaimRemainingCents) }}</span>
            </div>
            <div v-if="paymentActionRows.length === 0" class="state-banner">Aktuell keine überfälligen oder bald fälligen Forderungen.</div>
            <div v-else class="item-list">
              <article v-for="claim in paymentActionRows" :key="`work-${claim.id}`" class="item-card">
                <div class="item-topline">
                  <strong>{{ claim.member ? displayMemberName(claim.member) : 'Ohne Mitglied' }}</strong>
                  <span class="item-badge">{{ claimActionLabel(claim) }}</span>
                </div>
                <p class="item-meta">
                  {{ displayClaimType(claim.claimType) }} · {{ formatCurrency(claim.amountCents, claim.currencyCode) }} · fällig {{ formatDate(claim.dueOn) }}
                </p>
                <p class="item-meta">
                  Bezahlt {{ formatCurrency(claim.paidAmountCents, claim.currencyCode) }} · Rest {{ formatCurrency(claim.remainingAmountCents, claim.currencyCode) }}
                </p>
                <p v-if="claim.transactions.length > 0" class="item-meta">
                  Buchung verknüpft · {{ claim.transactions.length }} Kontobewegung{{ claim.transactions.length === 1 ? '' : 'en' }}
                </p>
                <p class="item-meta">
                  {{ formatReminderStage(claim.reminderLevel) }}<span v-if="claim.lastReminderAt"> · letzte Erinnerung {{ formatDate(claim.lastReminderAt) }}</span>
                </p>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments" @click="increaseReminderLevel(claim)">Mahnstufe erhöhen</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments" @click="markClaimPaid(claim)">Als bezahlt</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments" @click="cancelClaim(claim)">Stornieren</button>
                </div>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'payments'" class="card section-card">
            <div class="section-header">
              <h3>Beitragsforderungen anlegen</h3>
            </div>
            <div v-if="selectedClaimMember" class="state-banner worklist-selection-banner">
              <strong>{{ displayMemberName(selectedClaimMember) }}</strong>
              <span>ist für neue Forderungen vorausgewählt.</span>
              <button type="button" class="btn-secondary" @click="clearClaimMemberSelection">Auswahl lösen</button>
            </div>
            <div class="form-grid">
              <label>
                <span>Mitglied</span>
                <select v-model="claimForm.memberId">
                  <option value="">Bitte wählen</option>
                  <option v-for="member in members" :key="member.id" :value="String(member.id)">
                    {{ displayMemberName(member) }}
                  </option>
                </select>
              </label>
              <label>
                <span>Typ</span>
                <select v-model="claimForm.claimType">
                  <option value="membership_fee">Mitgliedsbeitrag</option>
                  <option value="additional_fee">Zusatzbeitrag</option>
                  <option value="course_fee">Kursgebühr</option>
                  <option value="penalty_fee">Mahn- oder Strafgebühr</option>
                  <option value="other">Sonstige</option>
                </select>
              </label>
              <label>
                <span>Fällig am</span>
                <input v-model="claimForm.dueOn" type="date" />
              </label>
              <label>
                <span>Betrag (EUR)</span>
                <input v-model.number="claimForm.amountEuro" type="number" min="0" step="0.01" />
              </label>
            </div>
            <label>
              <span>Notiz</span>
              <textarea v-model.trim="claimForm.notes" rows="3" placeholder="z. B. Halbjahresbeitrag Jugend"></textarea>
            </label>
            <div class="inline-actions">
              <button type="button" class="btn-primary" :disabled="claimSaving || !canWritePayments" @click="saveClaim">
                {{ claimSaving ? 'Speichert…' : 'Forderung anlegen' }}
              </button>
              <button type="button" class="btn-secondary" @click="resetClaimForm">Zurücksetzen</button>
            </div>
          </section>

          <section v-if="moduleKey === 'payments'" class="card section-card">
            <div class="section-header">
              <h3>Aktive Beitragsforderungen</h3>
            </div>
            <div v-if="paymentClaims.length === 0" class="state-banner">Noch keine Zahlungsforderungen vorhanden.</div>
            <div v-else class="item-list">
              <article v-for="claim in paymentClaims" :key="claim.id" class="item-card">
                <div class="item-topline">
                  <strong>{{ claim.member ? displayMemberName(claim.member) : 'Ohne Mitglied' }}</strong>
                  <span class="item-badge">{{ displayClaimStatus(claim.status) }}</span>
                </div>
                <p class="item-meta">
                  {{ displayClaimType(claim.claimType) }} · {{ formatCurrency(claim.amountCents, claim.currencyCode) }} · fällig {{ formatDate(claim.dueOn) }}
                </p>
                <p class="item-meta">
                  Bezahlt {{ formatCurrency(claim.paidAmountCents, claim.currencyCode) }} · Rest {{ formatCurrency(claim.remainingAmountCents, claim.currencyCode) }}
                  <span v-if="claim.lastPaidAt"> · letzte Zahlung {{ formatDate(claim.lastPaidAt) }}</span>
                </p>
                <p v-if="claim.transactions.length > 0" class="item-meta">
                  Buchung verknüpft · {{ claim.transactions.length }} Kontobewegung{{ claim.transactions.length === 1 ? '' : 'en' }}
                </p>
                <p class="item-meta">{{ formatReminderStage(claim.reminderLevel) }}<span v-if="claim.lastReminderAt"> · letzte Erinnerung {{ formatDate(claim.lastReminderAt) }}</span></p>
                <p v-if="claim.notes" class="item-meta">{{ claim.notes }}</p>
                <div class="form-grid">
                  <label>
                    <span>Teilzahlung (EUR)</span>
                    <input v-model.number="partialPaymentAmounts[claim.id]" type="number" min="0" step="0.01" :max="claim.remainingAmountCents / 100" />
                  </label>
                </div>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" :disabled="!claim.member?.id" @click="openMemberFromClaim(claim)">Mitglied öffnen</button>
                  <button type="button" class="btn-primary" :disabled="!canWritePayments || claim.remainingAmountCents <= 0 || partialPaymentCents(claim) <= 0" @click="registerPartialPayment(claim)">Teilzahlung buchen</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments || claim.status === 'open'" @click="setClaimStatus(claim, 'open')">Offen</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments || claim.status === 'partially_paid'" @click="setClaimStatus(claim, 'partially_paid')">Teilbezahlt</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments" @click="increaseReminderLevel(claim)">Mahnstufe erhöhen</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments" @click="markClaimPaid(claim)">Als bezahlt</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments || claim.status === 'written_off'" @click="setClaimStatus(claim, 'written_off')">Abschreiben</button>
                  <button type="button" class="btn-secondary" :disabled="!canWritePayments" @click="cancelClaim(claim)">Stornieren</button>
                  <button type="button" class="btn-danger" :disabled="!canWritePayments" @click="deleteClaim(claim)">Löschen</button>
                </div>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'payments'" class="card section-card">
            <div class="section-header">
              <h3>Offene Zahlungsbezüge</h3>
            </div>
            <div v-if="openInvoices.length === 0" class="state-banner">Keine offenen Rechnungen vorhanden.</div>
            <div v-else class="item-list">
              <article v-for="invoice in openInvoices.slice(0, 10)" :key="invoice.id" class="item-card">
                <div class="item-topline">
                  <strong>{{ invoice.invoiceNumber || `Rechnung #${invoice.id}` }}</strong>
                  <span class="item-badge">{{ displayInvoiceStatus(invoice.status) }}</span>
                </div>
                <p class="item-meta">
                  {{ invoice.party?.name || 'Ohne Partei' }} · {{ formatCurrency(invoice.grossAmountCents, invoice.currencyCode) }} · fällig {{ formatDate(invoice.dueOn) }}
                </p>
              </article>
            </div>
          </section>

          <section v-if="moduleKey === 'payments'" class="card section-card">
            <div class="section-header">
              <h3>Kontobewegungen</h3>
            </div>
            <div v-if="transactions.length === 0" class="state-banner">Keine Kontobewegungen vorhanden.</div>
            <div v-else class="table-wrap">
              <table class="data-table">
                <thead>
                  <tr>
                    <th>Datum</th>
                    <th>Referenz</th>
                    <th>Richtung</th>
                    <th>Status</th>
                    <th>Betrag</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="transaction in transactions.slice(0, 12)" :key="transaction.id">
                    <td>{{ formatDate(transaction.bookingDate) }}</td>
                    <td>{{ transaction.reference || 'Manuelle Buchung' }}</td>
                    <td>{{ displayTransactionDirection(transaction.direction) }}</td>
                    <td>{{ displayTransactionStatus(transaction.status) }}</td>
                    <td>{{ formatTransactionAmount(transaction) }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </section>

          <section v-if="moduleKey === 'reports'" class="card section-card">
            <div class="section-header">
              <h3>Berichtspakete</h3>
              <span class="item-badge">Exportierbar</span>
            </div>
            <div class="report-grid">
              <article v-for="report in reportPackages" :key="report.key" class="item-card report-card">
                <div class="item-topline">
                  <strong>{{ report.title }}</strong>
                  <span class="item-badge">{{ report.badge }}</span>
                </div>
                <p class="item-meta">{{ report.summary }}</p>
                <div class="report-metric-grid">
                  <div v-for="metric in report.metrics" :key="metric.label" class="report-metric">
                    <span>{{ metric.label }}</span>
                    <strong>{{ metric.value }}</strong>
                  </div>
                </div>
                <div class="inline-actions">
                  <button type="button" class="btn-secondary" :disabled="reportExportingKey === report.key" @click="exportReport(report, 'csv')">
                    {{ reportExportingKey === report.key ? 'Export…' : 'CSV' }}
                  </button>
                  <button type="button" class="btn-secondary" :disabled="reportExportingKey === report.key" @click="exportReport(report, 'pdf')">
                    PDF
                  </button>
                </div>
              </article>
            </div>
          </section>
        </div>

        <aside class="workspace-side">
          <section class="card section-card">
            <div class="section-header">
              <h3>Nächste Schritte</h3>
            </div>
            <div class="item-list compact-list">
              <article v-for="item in nextSteps" :key="item.title" class="item-card compact-card">
                <strong>{{ item.title }}</strong>
                <p class="item-meta">{{ item.text }}</p>
              </article>
            </div>
          </section>

          <section class="card section-card">
            <div class="section-header">
              <h3>Verknüpfte Bereiche</h3>
            </div>
            <div class="inline-actions stacked-actions">
              <button v-for="link in linkedRoutes" :key="link.to" type="button" class="btn-secondary" @click="openRoute(link.to)">
                {{ link.label }}
              </button>
            </div>
          </section>
        </aside>
      </section>
    </template>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';
import apiClient, { backendBaseUrl } from '../apiClient.js';
import RichTextEditor from '../components/RichTextEditor.vue';
import { safeErrorMessage } from '../utils/dialogUtils.js';
import { stripRichTextToText } from '../utils/richTextDocumentExport.js';
import { downloadCsvReport, exportReportPdf } from '../utils/reportExport.js';

function createEmptyStatistics() {
  return {
    overview: {},
    memberDevelopment: { monthly: [] },
    ageStructure: { buckets: [] },
    contributionDevelopment: { totals: {}, monthly: [] },
    sponsorDevelopment: { totals: {}, monthly: [] },
  };
}

function createEmptyArchive() {
  return {
    summary: {},
    inactiveMembers: [],
    archivedRequests: [],
    archivedTasks: [],
    archivedClaims: [],
  };
}

function normalizeInvoice(invoice = {}) {
  return {
    ...invoice,
    id: Number(invoice.id),
    grossAmountCents: Number(invoice.grossAmountCents || invoice.gross_amount_cents || 0),
    invoiceDirection: invoice.invoiceDirection || invoice.invoice_direction || 'outgoing',
    invoiceType: invoice.invoiceType || invoice.invoice_type || 'other',
    status: invoice.status || 'draft',
    dueOn: invoice.dueOn || invoice.due_on || '',
    issuedOn: invoice.issuedOn || invoice.issued_on || '',
  };
}

function normalizeTransaction(transaction = {}) {
  return {
    ...transaction,
    id: Number(transaction.id),
    amountCents: Number(transaction.amountCents || transaction.amount_cents || 0),
    direction: transaction.direction || 'credit',
    status: transaction.status || 'booked',
    currencyCode: transaction.currencyCode || transaction.currency_code || 'EUR',
    bookingDate: transaction.bookingDate || transaction.booking_date || '',
  };
}

function normalizeCalendarEvent(event = {}) {
  return {
    ...event,
    id: Number(event.id),
    eventType: event.eventType || event.event_type || 'club_event',
    status: event.status || 'planning',
    title: event.title || '',
    description: event.description || '',
    location: event.location || '',
    startDate: event.startDate || event.start_date || '',
    endDate: event.endDate || event.end_date || '',
    registrationDeadline: event.registrationDeadline || event.registration_deadline || '',
    organizerUserId: Number(event.organizerUserId || event.organizer_user_id || 0) || null,
    category: event.category || '',
    notes: event.notes || '',
  };
}

function normalizeRequest(request = {}) {
  return {
    ...request,
    id: Number(request.id),
    requestType: request.requestType || request.request_type || 'contact',
    status: request.status || 'open',
  };
}

function normalizeParty(party = {}) {
  return {
    ...party,
    id: Number(party.id),
    partyType: party.partyType || party.party_type || 'customer',
    status: party.status || 'active',
    name: party.name || '',
    contractReference: party.contractReference || party.contract_reference || '',
    validFrom: party.validFrom || party.valid_from || '',
    validTo: party.validTo || party.valid_to || '',
    contactName: party.contactName || party.contact_name || '',
    email: party.email || '',
    phone: party.phone || '',
    street: party.street || '',
    postalCode: party.postalCode || party.postal_code || '',
    city: party.city || '',
    notes: party.notes || '',
  };
}

function normalizeMember(member = {}) {
  return {
    ...member,
    id: Number(member.id),
  };
}

function normalizePaymentClaim(claim = {}) {
  const amountCents = Number(claim.amountCents || claim.amount_cents || 0);
  const paidAmountCents = Math.max(0, Number(claim.paidAmountCents || claim.paid_amount_cents || 0));
  return {
    ...claim,
    id: Number(claim.id),
    memberId: Number(claim.memberId || claim.member_id || claim.member?.id || 0) || null,
    amountCents,
    paidAmountCents: Math.min(amountCents, paidAmountCents),
    remainingAmountCents: Math.max(0, amountCents - paidAmountCents),
    currencyCode: claim.currencyCode || claim.currency_code || 'EUR',
    status: claim.status || 'open',
    claimType: claim.claimType || claim.claim_type || 'membership_fee',
    dueOn: claim.dueOn || claim.due_on || '',
    reminderLevel: Math.max(0, Number.parseInt(claim.reminderLevel || claim.reminder_level, 10) || 0),
    lastReminderAt: claim.lastReminderAt || claim.last_reminder_at || '',
    lastPaidAt: claim.lastPaidAt || claim.last_paid_at || '',
    transactions: Array.isArray(claim.transactions) ? claim.transactions : [],
  };
}

function normalizeDocument(document = {}) {
  const versions = Array.isArray(document.versions) ? document.versions : [];
  const latestVersion = document.latestVersion || document.latest_version || versions[0] || null;
  return {
    ...document,
    id: Number(document.id),
    documentType: document.documentType || document.document_type || 'other',
    title: document.title || '',
    description: document.description || '',
    status: document.status || 'active',
    visibilityScope: document.visibilityScope || document.visibility_scope || 'board',
    ownerUserId: Number(document.ownerUserId || document.owner_user_id || 0) || null,
    currentVersionNo: Number(document.currentVersionNo || document.current_version_no || 1),
    archivedAt: document.archivedAt || document.archived_at || '',
    versionCount: Number(document.versionCount || document.version_count || versions.length),
    linkedEntityCount: Number(document.linkedEntityCount || document.linked_entity_count || 0),
    latestVersion: latestVersion ? {
      ...latestVersion,
      versionNo: Number(latestVersion.versionNo || latestVersion.version_no || 1),
      fileName: latestVersion.fileName || latestVersion.file_name || '',
      mimeType: latestVersion.mimeType || latestVersion.mime_type || '',
      fileSizeBytes: Number(latestVersion.fileSizeBytes || latestVersion.file_size_bytes || 0),
      uploadedAt: latestVersion.uploadedAt || latestVersion.uploaded_at || '',
      changeNote: latestVersion.changeNote || latestVersion.change_note || '',
    } : null,
    versions: versions.map((version) => ({
      ...version,
      versionNo: Number(version.versionNo || version.version_no || 1),
      fileName: version.fileName || version.file_name || '',
      mimeType: version.mimeType || version.mime_type || '',
      fileSizeBytes: Number(version.fileSizeBytes || version.file_size_bytes || 0),
      uploadedAt: version.uploadedAt || version.uploaded_at || '',
      changeNote: version.changeNote || version.change_note || '',
    })),
    links: Array.isArray(document.links) ? document.links : [],
  };
}

export default {
  name: 'ClubOperationsWorkspaceView',
  components: {
    RichTextEditor,
  },
  props: {
    moduleKey: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      loading: false,
      loadError: '',
      reportExportingKey: '',
      clubDocuments: [],
      documentSaving: false,
      selectedDocumentId: null,
      documentSearch: '',
      documentTemplates: [
        {
          key: 'satzung',
          title: 'Satzungsvorlage',
          documentType: 'satzung',
          status: 'draft',
          visibilityScope: 'board',
          description: 'Grundlage für Satzung, Ordnungen und formale Vereinsfassung.',
          changeNote: 'Neue oder angepasste Satzungsvorlage hinterlegt.',
        },
        {
          key: 'protokoll',
          title: 'Protokollvorlage',
          documentType: 'protokoll',
          status: 'draft',
          visibilityScope: 'board',
          description: 'Vorlage für Sitzungs-, Versammlungs- und Beschlussprotokolle.',
          changeNote: 'Neue Protokollvorlage hinterlegt.',
        },
        {
          key: 'nachweis',
          title: 'Nachweisvorlage',
          documentType: 'nachweis',
          status: 'draft',
          visibilityScope: 'finance',
          description: 'Ablage für Nachweise, Bescheinigungen und prüfrelevante Belege.',
          changeNote: 'Neue Nachweisvorlage hinterlegt.',
        },
      ],
      documentForm: {
        id: null,
        title: '',
        documentType: 'other',
        status: 'active',
        visibilityScope: 'board',
        description: '',
        changeNote: '',
        linkedEntityType: '',
        linkedEntityId: '',
        file: null,
      },
      calendarEvents: [],
      billingTemplates: [],
      billingRuns: [],
      invoices: [],
      invoiceParties: [],
      accounts: [],
      transactions: [],
      requests: [],
      members: [],
      tasks: [],
      paymentClaims: [],
      clubProfile: null,
      feeRules: [],
      reportRequests: [],
      reportInvoices: [],
      reportPaymentClaims: [],
      reportDocuments: [],
      sponsorSaving: false,
      selectedSponsorPartyId: null,
      sponsorSourceRequestId: null,
      sponsorPartyForm: {
        id: null,
        name: '',
        status: 'active',
        contractReference: '',
        validFrom: '',
        validTo: '',
        contactName: '',
        email: '',
        phone: '',
        street: '',
        postalCode: '',
        city: '',
        notes: '',
      },
      partialPaymentAmounts: {},
      statistics: createEmptyStatistics(),
      archive: createEmptyArchive(),
      eventSaving: false,
      selectedEventId: null,
      claimSaving: false,
      eventForm: {
        id: null,
        title: '',
        eventType: 'club_event',
        status: 'planning',
        description: '',
        location: '',
        startDate: new Date().toISOString().slice(0, 10),
        endDate: new Date().toISOString().slice(0, 10),
        registrationDeadline: '',
      },
      claimForm: {
        memberId: '',
        claimType: 'membership_fee',
        dueOn: new Date().toISOString().slice(0, 10),
        amountEuro: 0,
        notes: '',
      },
    };
  },
  computed: {
    ...mapGetters(['currentClub', 'hasPermission', 'token']),
    moduleTitle() {
      return {
        documents: 'Dokumente',
        events: 'Veranstaltungen',
        sponsors: 'Sponsoren',
        fees: 'Beiträge',
        payments: 'Zahlungen',
        reports: 'Berichte',
      }[this.moduleKey] || 'Club-Bereich';
    },
    moduleSubtitle() {
      return {
        documents: 'Dokumentenarbeitsfläche mit Upload, Suche, Versionen und Belegbezug für Vereinsunterlagen.',
        events: 'Vereinstermine mit Fristen, Zuständigkeiten und Nacharbeit neben dem normalen Kalenderbetrieb.',
        sponsors: 'Sponsoringanfragen, Sponsorstamm und Rechnungsbezug in einer gemeinsamen Übersicht.',
        fees: 'Beitragslogik aus Mitgliedsdaten, Qualitätslücken und finanzrelevanten Zuordnungen.',
        payments: 'Zahlungsstatus aus offenen Rechnungen, Konten und vorhandenen Buchungen.',
        reports: 'Berichtszentrale für Vorstand, Finanzen, Archiv und Sponsoring auf Basis vorhandener Auswertungen.',
      }[this.moduleKey] || '';
    },
    canWriteEvents() {
      return this.hasPermission('schedule', 'write');
    },
    canWritePayments() {
      return this.hasPermission('finance_accounts', 'write');
    },
    canWriteSponsors() {
      return this.hasPermission('finance_invoices', 'write');
    },
    sponsorParties() {
      return this.invoiceParties.filter((party) => party.partyType === 'sponsor');
    },
    selectedSponsorParty() {
      return this.sponsorParties.find((party) => Number(party.id) === Number(this.selectedSponsorPartyId)) || null;
    },
    sponsorInvoices() {
      return this.invoices.filter((invoice) => invoice.invoiceType === 'sponsoring');
    },
    selectedSponsorPartyInvoices() {
      if (!this.selectedSponsorParty) return [];
      return this.sponsorInvoices.filter((invoice) => Number(invoice.partyId || 0) === Number(this.selectedSponsorParty.id));
    },
    filteredClubDocuments() {
      const query = String(this.documentSearch || '').trim().toLowerCase();
      return this.clubDocuments.filter((document) => {
        if (!query) return true;
        const haystack = [
          document.title,
          stripRichTextToText(document.description),
          document.documentType,
          document.status,
          document.visibilityScope,
          document.latestVersion?.fileName,
          stripRichTextToText(document.latestVersion?.changeNote),
        ].filter(Boolean).join(' ').toLowerCase();
        return haystack.includes(query);
      });
    },
    selectedDocument() {
      return this.clubDocuments.find((document) => Number(document.id) === Number(this.selectedDocumentId)) || null;
    },
    archivedClubDocumentCount() {
      return this.clubDocuments.filter((document) => document.status === 'archived' || document.archivedAt).length;
    },
    documentVersionCount() {
      return this.clubDocuments.reduce((sum, document) => sum + Number(document.versionCount || 0), 0);
    },
    sponsoringRequests() {
      return this.requests.filter((request) => request.requestType === 'sponsoring');
    },
    openInvoices() {
      return this.invoices.filter((invoice) => ['issued', 'partially_paid'].includes(invoice.status));
    },
    outgoingOpenInvoices() {
      return this.openInvoices.filter((invoice) => invoice.invoiceDirection === 'outgoing');
    },
    incomingOpenInvoices() {
      return this.openInvoices.filter((invoice) => invoice.invoiceDirection === 'incoming');
    },
    invoicesWithReferenceCount() {
      return this.invoices.filter((invoice) => invoice.documentId || invoice.externalReference).length;
    },
    recentDocumentArtifacts() {
      const documentArtifacts = this.clubDocuments.slice(0, 4).map((document) => ({
        key: `doc-${document.id}`,
        title: document.title || `Dokument #${document.id}`,
        type: this.displayDocumentType(document.documentType),
        meta: `${this.displayDocumentStatus(document.status)} · ${this.displayVisibilityScope(document.visibilityScope)} · V${document.currentVersionNo || document.versionCount || 1}`,
      }));
      const templateArtifacts = this.billingTemplates.slice(0, 4).map((template) => ({
        key: `template-${template.id}`,
        title: template.name || `Vorlage #${template.id}`,
        type: 'Vorlage',
        meta: template.description || 'Abrechnungsvorlage',
      }));
      const runArtifacts = this.billingRuns.slice(0, 4).map((run) => ({
        key: `run-${run.id}`,
        title: run.generatedFilename || run.name || `Abrechnung #${run.id}`,
        type: 'Abrechnung',
        meta: `${run.monthFrom || '–'} bis ${run.monthTo || '–'}`,
      }));
      return [...documentArtifacts, ...templateArtifacts, ...runArtifacts].slice(0, 8);
    },
    reportPackages() {
      const requestRows = this.requests.slice(0, 8).map((request) => ([
        request.subject || this.displayRequestType(request.requestType),
        this.displayRequestStatus(request.status),
        this.displayPersonName(request),
        request.workflowStage ? this.requestWorkflowStageLabel(request.workflowStage) : '–',
      ]));
      const openClaims = this.paymentClaims.filter((claim) => ['open', 'partially_paid'].includes(claim.status));
      const openInvoices = this.invoices.filter((invoice) => ['issued', 'partially_paid'].includes(invoice.status));
      const archivedDocuments = this.clubDocuments.filter((document) => document.status === 'archived' || document.archivedAt);

      return [
        {
          key: 'board',
          title: 'Vorstand',
          badge: 'Steuerung',
          summary: 'Kernzahlen für laufende Vorgänge, Prioritäten und offene Arbeitslast.',
          metrics: [
            { label: 'Aktive Mitglieder', value: this.statistics.overview?.activeMembers ?? 0 },
            { label: 'Offene Aufgaben', value: this.openTaskCount },
            { label: 'Überfällige Aufgaben', value: this.overdueTaskCount },
            { label: 'Offene Anfragen', value: this.requests.filter((request) => ['open', 'in_progress', 'waiting'].includes(request.status)).length },
          ],
          csvSections: [
            {
              title: 'Vorstand - Kennzahlen',
              headers: ['Kennzahl', 'Wert'],
              rows: [
                ['Aktive Mitglieder', this.statistics.overview?.activeMembers ?? 0],
                ['Offene Aufgaben', this.openTaskCount],
                ['Überfällige Aufgaben', this.overdueTaskCount],
                ['Offene Anfragen', this.requests.filter((request) => ['open', 'in_progress', 'waiting'].includes(request.status)).length],
              ],
            },
            {
              title: 'Aktuelle Anfragen',
              headers: ['Betreff', 'Status', 'Person', 'Workflow'],
              rows: requestRows.length > 0 ? requestRows : [['Keine Einträge', '–', '–', '–']],
            },
          ],
          pdfSections: [
            {
              title: 'Kennzahlen',
              lines: [
                `Aktive Mitglieder: ${this.statistics.overview?.activeMembers ?? 0}`,
                `Offene Aufgaben: ${this.openTaskCount}`,
                `Überfällige Aufgaben: ${this.overdueTaskCount}`,
                `Offene Anfragen: ${this.requests.filter((request) => ['open', 'in_progress', 'waiting'].includes(request.status)).length}`,
              ],
            },
            {
              title: 'Aktuelle Anfragen',
              table: {
                headers: ['Betreff', 'Status', 'Person', 'Workflow'],
                rows: requestRows.length > 0 ? requestRows : [['Keine Einträge', '–', '–', '–']],
              },
            },
          ],
        },
        {
          key: 'finance',
          title: 'Finanzen',
          badge: 'Kontrolle',
          summary: 'Beitragsforderungen, Rechnungen und Sponsoringumsätze im Blick.',
          metrics: [
            { label: 'Offene Forderungen', value: openClaims.length },
            { label: 'Offener Betrag', value: this.formatCurrency(this.statistics.contributionDevelopment?.totals?.openAmountCents || 0) },
            { label: 'Offene Rechnungen', value: openInvoices.length },
            { label: 'Sponsoringrechnungen', value: this.sponsorInvoices.length },
          ],
          csvSections: [
            {
              title: 'Finanzen - Forderungen',
              headers: ['Mitglied', 'Status', 'Fällig', 'Rest'],
              rows: openClaims.length > 0
                ? openClaims.map((claim) => [
                  claim.member?.id ? this.displayPersonName(claim.member) : `Forderung #${claim.id}`,
                  this.displayClaimStatus(claim.status),
                  this.formatDate(claim.dueOn),
                  this.formatCurrency(claim.remainingAmountCents, claim.currencyCode),
                ])
                : [['Keine offenen Forderungen', '–', '–', '–']],
            },
            {
              title: 'Finanzen - Rechnungen',
              headers: ['Rechnung', 'Partei', 'Status', 'Brutto'],
              rows: openInvoices.length > 0
                ? openInvoices.map((invoice) => [
                  invoice.invoiceNumber || `Rechnung #${invoice.id}`,
                  invoice.party?.name || 'Ohne Partei',
                  this.displayInvoiceStatus(invoice.status),
                  this.formatCurrency(invoice.grossAmountCents, invoice.currencyCode),
                ])
                : [['Keine offenen Rechnungen', '–', '–', '–']],
            },
          ],
          pdfSections: [
            {
              title: 'Kennzahlen',
              lines: [
                `Offene Forderungen: ${openClaims.length}`,
                `Offener Betrag: ${this.formatCurrency(this.statistics.contributionDevelopment?.totals?.openAmountCents || 0)}`,
                `Offene Rechnungen: ${openInvoices.length}`,
                `Sponsoringrechnungen: ${this.sponsorInvoices.length}`,
              ],
            },
            {
              title: 'Offene Forderungen',
              table: {
                headers: ['Mitglied', 'Status', 'Fällig', 'Rest'],
                rows: openClaims.length > 0
                  ? openClaims.map((claim) => [
                    claim.member?.id ? this.displayPersonName(claim.member) : `Forderung #${claim.id}`,
                    this.displayClaimStatus(claim.status),
                    this.formatDate(claim.dueOn),
                    this.formatCurrency(claim.remainingAmountCents, claim.currencyCode),
                  ])
                  : [['Keine offenen Forderungen', '–', '–', '–']],
              },
            },
            {
              title: 'Offene Rechnungen',
              table: {
                headers: ['Rechnung', 'Partei', 'Status', 'Brutto'],
                rows: openInvoices.length > 0
                  ? openInvoices.map((invoice) => [
                    invoice.invoiceNumber || `Rechnung #${invoice.id}`,
                    invoice.party?.name || 'Ohne Partei',
                    this.displayInvoiceStatus(invoice.status),
                    this.formatCurrency(invoice.grossAmountCents, invoice.currencyCode),
                  ])
                  : [['Keine offenen Rechnungen', '–', '–', '–']],
              },
            },
          ],
        },
        {
          key: 'archive',
          title: 'Archiv',
          badge: 'Ablage',
          summary: 'Archivierte Vorgänge und dokumentierte Altbestände für den Überblick.',
          metrics: [
            { label: 'Archivierte Anfragen', value: this.archive.summary?.archivedRequests ?? 0 },
            { label: 'Archivierte Aufgaben', value: this.archive.summary?.archivedTasks ?? 0 },
            { label: 'Historische Forderungen', value: this.archive.summary?.archivedClaims ?? 0 },
            { label: 'Archivierte Dokumente', value: archivedDocuments.length },
          ],
          csvSections: [
            {
              title: 'Archiv - Zusammenfassung',
              headers: ['Kennzahl', 'Wert'],
              rows: [
                ['Archivierte Anfragen', this.archive.summary?.archivedRequests ?? 0],
                ['Archivierte Aufgaben', this.archive.summary?.archivedTasks ?? 0],
                ['Historische Forderungen', this.archive.summary?.archivedClaims ?? 0],
                ['Archivierte Dokumente', archivedDocuments.length],
              ],
            },
            {
              title: 'Archivierte Dokumente',
              headers: ['Titel', 'Typ', 'Status', 'Version'],
              rows: archivedDocuments.length > 0
                ? archivedDocuments.map((document) => [
                  document.title,
                  this.displayDocumentType(document.documentType),
                  this.displayDocumentStatus(document.status),
                  document.currentVersionNo || document.versionCount || 1,
                ])
                : [['Keine archivierten Dokumente', '–', '–', '–']],
            },
          ],
          pdfSections: [
            {
              title: 'Zusammenfassung',
              lines: [
                `Archivierte Anfragen: ${this.archive.summary?.archivedRequests ?? 0}`,
                `Archivierte Aufgaben: ${this.archive.summary?.archivedTasks ?? 0}`,
                `Historische Forderungen: ${this.archive.summary?.archivedClaims ?? 0}`,
                `Archivierte Dokumente: ${archivedDocuments.length}`,
              ],
            },
            {
              title: 'Archivierte Dokumente',
              table: {
                headers: ['Titel', 'Typ', 'Status', 'Version'],
                rows: archivedDocuments.length > 0
                  ? archivedDocuments.map((document) => [
                    document.title,
                    this.displayDocumentType(document.documentType),
                    this.displayDocumentStatus(document.status),
                    document.currentVersionNo || document.versionCount || 1,
                  ])
                  : [['Keine archivierten Dokumente', '–', '–', '–']],
              },
            },
          ],
        },
        {
          key: 'sponsoring',
          title: 'Sponsoring',
          badge: 'Beziehungen',
          summary: 'Anfragen, laufende Beziehungen und Rechnungen im Sponsoringfluss.',
          metrics: [
            { label: 'Sponsoringanfragen', value: this.sponsoringRequests.length },
            { label: 'Offen', value: this.sponsoringRequests.filter((request) => ['open', 'in_progress', 'waiting'].includes(request.status)).length },
            { label: 'Sponsorenparteien', value: this.sponsorParties.length },
            { label: 'Sponsoringrechnungen', value: this.sponsorInvoices.length },
          ],
          csvSections: [
            {
              title: 'Sponsoring - Anfragen',
              headers: ['Betreff', 'Status', 'Person', 'Workflow'],
              rows: this.sponsoringRequests.length > 0
                ? this.sponsoringRequests.map((request) => [
                  request.subject || 'Ohne Betreff',
                  this.displayRequestStatus(request.status),
                  this.displayPersonName(request),
                  request.workflowStage ? this.requestWorkflowStageLabel(request.workflowStage) : '–',
                ])
                : [['Keine Sponsoringanfragen', '–', '–', '–']],
            },
            {
              title: 'Sponsoring - Parteien',
              headers: ['Name', 'Status', 'Vertrag', 'Laufzeit'],
              rows: this.sponsorParties.length > 0
                ? this.sponsorParties.map((party) => [
                  party.name,
                  this.displaySponsorStatus(party.status),
                  party.contractReference || '–',
                  this.displaySponsorPeriod(party),
                ])
                : [['Keine Sponsorenparteien', '–', '–', '–']],
            },
          ],
          pdfSections: [
            {
              title: 'Zusammenfassung',
              lines: [
                `Sponsoringanfragen: ${this.sponsoringRequests.length}`,
                `Offen: ${this.sponsoringRequests.filter((request) => ['open', 'in_progress', 'waiting'].includes(request.status)).length}`,
                `Sponsorenparteien: ${this.sponsorParties.length}`,
                `Sponsoringrechnungen: ${this.sponsorInvoices.length}`,
              ],
            },
            {
              title: 'Sponsoringanfragen',
              table: {
                headers: ['Betreff', 'Status', 'Person', 'Workflow'],
                rows: this.sponsoringRequests.length > 0
                  ? this.sponsoringRequests.map((request) => [
                    request.subject || 'Ohne Betreff',
                    this.displayRequestStatus(request.status),
                    this.displayPersonName(request),
                    request.workflowStage ? this.requestWorkflowStageLabel(request.workflowStage) : '–',
                  ])
                  : [['Keine Sponsoringanfragen', '–', '–', '–']],
              },
            },
          ],
        },
      ];
    },
    feeGroups() {
      const buckets = new Map();
      this.members.forEach((member) => {
        const code = String(member.contributionGroupCode || member.contribution_group_code || '').trim() || 'Nicht zugeordnet';
        buckets.set(code, (buckets.get(code) || 0) + 1);
      });
      return Array.from(buckets.entries())
        .map(([code, count]) => ({ code, count }))
        .sort((a, b) => b.count - a.count || a.code.localeCompare(b.code, 'de-DE'));
    },
    configuredFeeRules() {
      return this.feeRules
        .map((rule, index) => ({
          id: rule.id || `${index}`,
          code: String(rule.code || '').trim(),
          label: String(rule.label || '').trim(),
          amountCents: Number(rule.amountCents || 0),
          cycle: rule.cycle || 'monthly',
          note: String(rule.note || '').trim(),
        }))
        .filter((rule) => rule.code || rule.label || rule.amountCents > 0);
    },
    membersWithoutFeeCode() {
      return this.members.filter((member) => !String(member.contributionGroupCode || member.contribution_group_code || '').trim());
    },
    selectedClaimMember() {
      const routeMemberId = Number(this.$route?.query?.memberId || 0);
      if (!routeMemberId) {
        return null;
      }
      return this.members.find((member) => Number(member.id) === routeMemberId) || null;
    },
    membersMissingSepaReference() {
      return this.members.filter((member) => {
        const mandateReference = String(member.sepaMandateReference || member.sepa_mandate_reference || '').trim();
        return !mandateReference && member.needsSepaMandate !== false && member.needs_sepa_mandate !== false;
      });
    },
    memberFeeRows() {
      const claimsByMember = new Map();
      this.paymentClaims.forEach((claim) => {
        const memberId = Number(claim.memberId || claim.member_id || claim.member?.id || 0);
        if (!memberId) return;
        const rows = claimsByMember.get(memberId) || [];
        rows.push(claim);
        claimsByMember.set(memberId, rows);
      });

      return this.members
        .map((member) => {
          const memberId = Number(member.id);
          const memberClaims = claimsByMember.get(memberId) || [];
          const openClaims = memberClaims.filter((claim) => ['open', 'partially_paid'].includes(claim.status));
          const latestClaim = [...memberClaims].sort((a, b) => {
            const dateA = new Date(a.updatedAt || a.createdAt || 0).getTime();
            const dateB = new Date(b.updatedAt || b.createdAt || 0).getTime();
            return dateB - dateA;
          })[0];

          return {
            id: memberId,
            name: this.displayMemberName(member),
            feeCode: String(member.contributionGroupCode || member.contribution_group_code || '').trim() || 'Nicht zugeordnet',
            openClaims: openClaims.length,
            openAmountCents: openClaims.reduce((sum, claim) => sum + Number(claim.remainingAmountCents || 0), 0),
            latestStatus: latestClaim ? this.displayClaimStatus(latestClaim.status) : 'Keine Forderung',
          };
        })
        .sort((a, b) => {
          if (b.openClaims !== a.openClaims) return b.openClaims - a.openClaims;
          if (b.openAmountCents !== a.openAmountCents) return b.openAmountCents - a.openAmountCents;
          return a.name.localeCompare(b.name, 'de-DE');
        });
    },
    actionablePaymentClaims() {
      return this.paymentClaims.filter((claim) => ['open', 'partially_paid'].includes(claim.status));
    },
    overduePaymentClaims() {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      return this.actionablePaymentClaims.filter((claim) => {
        if (!claim.dueOn) return false;
        const dueDate = new Date(claim.dueOn);
        dueDate.setHours(0, 0, 0, 0);
        return !Number.isNaN(dueDate.getTime()) && dueDate.getTime() < today.getTime();
      });
    },
    dueSoonPaymentClaims() {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const nextWeek = new Date(today);
      nextWeek.setDate(nextWeek.getDate() + 7);
      return this.actionablePaymentClaims.filter((claim) => {
        if (!claim.dueOn) return false;
        const dueDate = new Date(claim.dueOn);
        dueDate.setHours(0, 0, 0, 0);
        if (Number.isNaN(dueDate.getTime())) return false;
        return dueDate.getTime() >= today.getTime() && dueDate.getTime() <= nextWeek.getTime();
      });
    },
    reminderActionClaims() {
      return this.actionablePaymentClaims.filter((claim) => Number(claim.reminderLevel || 0) > 0);
    },
    paymentActionRows() {
      const seen = new Set();
      return [...this.overduePaymentClaims, ...this.dueSoonPaymentClaims, ...this.reminderActionClaims]
        .filter((claim) => {
          if (seen.has(claim.id)) return false;
          seen.add(claim.id);
          return true;
        })
        .sort((a, b) => {
          const urgencyA = this.claimUrgencyScore(a);
          const urgencyB = this.claimUrgencyScore(b);
          if (urgencyB !== urgencyA) return urgencyB - urgencyA;
          return new Date(a.dueOn || 0).getTime() - new Date(b.dueOn || 0).getTime();
        });
    },
    openTaskCount() {
      return this.tasks.filter((task) => ['open', 'in_progress', 'waiting'].includes(task.status)).length;
    },
    overdueTaskCount() {
      const now = Date.now();
      return this.tasks.filter((task) => {
        if (!task?.dueAt) return false;
        if (!['open', 'in_progress', 'waiting'].includes(task.status)) return false;
        return new Date(task.dueAt).getTime() < now;
      }).length;
    },
    moduleStats() {
      const map = {
        documents: [
          { label: 'Dokumente', value: this.clubDocuments.length },
          { label: 'Versionen', value: this.documentVersionCount },
          { label: 'Archiviert', value: this.archivedClubDocumentCount },
          { label: 'Belegbezug', value: this.invoicesWithReferenceCount },
        ],
        events: [
          { label: 'Eigene Termine', value: this.calendarEvents.length },
          { label: 'Dieser Monat', value: this.eventsThisMonthCount },
          { label: 'Nächster Termin', value: this.nextEventLabel },
          { label: 'Kalenderbereich', value: 'Aktiv' },
        ],
        sponsors: [
          { label: 'Sponsoringanfragen', value: this.sponsoringRequests.length },
          { label: 'Offen', value: this.sponsoringRequests.filter((request) => ['open', 'in_progress', 'waiting'].includes(request.status)).length },
          { label: 'Sponsorenparteien', value: this.sponsorParties.length },
          { label: 'Sponsoringrechnungen', value: this.sponsorInvoices.length },
        ],
        fees: [
          { label: 'Mitglieder', value: this.members.length },
          { label: 'Beitragsgruppen', value: this.feeGroups.length },
          { label: 'Regeln', value: this.configuredFeeRules.length },
          { label: 'Ohne Zuordnung', value: this.membersWithoutFeeCode.length },
          { label: 'SEPA offen', value: this.membersMissingSepaReference.length },
        ],
        payments: [
          { label: 'Forderungen', value: this.paymentClaims.length },
          { label: 'Überfällig', value: this.overduePaymentClaims.length },
          { label: 'Mit Mahnstufe', value: this.paymentClaims.filter((claim) => claim.reminderLevel > 0).length },
          { label: 'Rest offen', value: formatCurrency(this.totalClaimRemainingCents) },
          { label: 'Konten', value: this.accounts.length },
          { label: 'Buchungen', value: this.transactions.length },
        ],
        reports: [
          { label: 'Aktive Mitglieder', value: this.statistics.overview?.activeMembers ?? 0 },
          { label: 'Offene Aufgaben', value: this.openTaskCount },
          { label: 'Überfällige Aufgaben', value: this.overdueTaskCount },
          { label: 'Archivierte Vorgänge', value: (this.archive.summary?.archivedRequests ?? 0) + (this.archive.summary?.archivedTasks ?? 0) },
        ],
      };
      return map[this.moduleKey] || [];
    },
    eventsThisMonthCount() {
      const now = new Date();
      return this.calendarEvents.filter((event) => {
        const date = new Date(event.startDate);
        return date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear();
      }).length;
    },
    nextEventLabel() {
      const upcoming = this.calendarEvents
        .filter((event) => new Date(event.startDate).getTime() >= Date.now())
        .sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())[0];
      return upcoming ? this.formatDate(upcoming.startDate) : '–';
    },
    totalOutgoingOpenCents() {
      return this.outgoingOpenInvoices.reduce((sum, invoice) => sum + Number(invoice.grossAmountCents || 0), 0);
    },
    totalIncomingOpenCents() {
      return this.incomingOpenInvoices.reduce((sum, invoice) => sum + Number(invoice.grossAmountCents || 0), 0);
    },
    totalClaimRemainingCents() {
      return this.actionablePaymentClaims.reduce((sum, claim) => sum + Number(claim.remainingAmountCents || 0), 0);
    },
    primaryPaymentAccount() {
      return this.accounts.find((account) => account.isDefault && account.status !== 'archived')
        || this.accounts.find((account) => account.status === 'active' && account.accountType === 'bank')
        || this.accounts.find((account) => account.status !== 'archived')
        || null;
    },
    linkedRoutes() {
      const map = {
        documents: [
          { to: '/billing', label: 'Abrechnung' },
          { to: '/club-invoices', label: 'Rechnungen' },
          { to: '/team-management', label: 'Mannschaften' },
        ],
        events: [
          { to: '/calendar', label: 'Kalender' },
          { to: '/club-tasks', label: 'Aufgaben' },
          { to: '/team-management', label: 'Mannschaften' },
        ],
        sponsors: [
          { to: '/club-requests', label: 'Anfragen' },
          { to: '/club-invoices', label: 'Rechnungen' },
          { to: '/club-communication', label: 'Kommunikation' },
        ],
        fees: [
          { to: '/members', label: 'Mitglieder' },
          { to: '/club-invoices', label: 'Rechnungen' },
          { to: '/club-accounts', label: 'Konten' },
        ],
        payments: [
          { to: '/club-fees', label: 'Beiträge' },
          { to: '/club-accounts', label: 'Konten' },
          { to: '/club-invoices', label: 'Rechnungen' },
          { to: '/club-tasks', label: 'Aufgaben' },
        ],
        reports: [
          { to: '/club-statistics', label: 'Statistiken' },
          { to: '/club-archive', label: 'Archiv' },
          { to: '/club-history', label: 'Historie' },
        ],
      };
      return map[this.moduleKey] || [];
    },
    nextSteps() {
      const map = {
        documents: [
          { title: 'Dokumente verschlagworten', text: 'Typen, Sichtbarkeit und Schnellfilter für Satzung, Protokolle und Nachweise weiter verdichten.' },
          { title: 'Versionen und Bezüge schärfen', text: 'Uploads, Nachträge und Verknüpfungen zu Rechnungen und Vorgängen stabiler machen.' },
        ],
        events: [
          { title: 'Verantwortlichkeiten trennen', text: 'Ort, Zuständigkeit und Nacharbeit als klare Terminfelder weiter schärfen.' },
          { title: 'Fristen und Aufgaben koppeln', text: 'Anmeldefristen und Nacharbeit noch enger mit der Aufgabenautomatik verbinden.' },
        ],
        sponsors: [
          { title: 'Laufzeiten erfassen', text: 'Vertragsstart, Laufzeit und Kündigungslogik am Sponsor-Stamm ergänzen.' },
          { title: 'Anfragen überführen', text: 'Sponsoringanfragen direkt in Pflegeeinträge und Rechnungsflüsse überführen.' },
        ],
        fees: [
          { title: 'Regelwerk implementieren', text: 'Eigene Beitragssätze, Familienregeln und Ermäßigungen als persistentes Modul ergänzen.' },
          { title: 'Mitgliedszuordnung festziehen', text: 'Mitglieder sauber mit Beitragslogik und Zahlungsverlauf verknüpfen.' },
        ],
        payments: [
          { title: 'Teilzahlungen ergänzen', text: 'Teilbezahlt und Restforderung noch mit echter Buchungszuordnung verbinden.' },
          { title: 'Serienläufe ergänzen', text: 'Zahlungserinnerungen und Sammelaktionen später aus dieser Arbeitsliste automatisieren.' },
        ],
        reports: [
          { title: 'Exportformate ergänzen', text: 'PDF- und CSV-Ausgaben für Vorstands- und Finanzberichte ergänzen.' },
          { title: 'Vorlagenfähig machen', text: 'Wiederkehrende Berichtspakete als abrufbare Reportsets hinterlegen.' },
        ],
      };
      return map[this.moduleKey] || [];
    },
  },
  watch: {
    currentClub: {
      immediate: true,
      async handler(clubId) {
        if (!clubId) return;
        await this.loadModuleData();
      },
    },
    moduleKey: {
      async handler() {
        if (!this.currentClub) return;
        await this.loadModuleData();
      },
    },
    '$route.query.memberId': {
      immediate: true,
      handler() {
        this.syncClaimFormMemberSelection();
      },
    },
  },
  methods: {
    async loadModuleData() {
      if (!this.currentClub) return;
      this.loading = true;
      this.loadError = '';
      try {
        this.resetModuleState();
        if (this.moduleKey === 'documents') {
          await Promise.all([this.loadClubDocuments(), this.loadBillingTemplates(), this.loadBillingRuns(), this.loadInvoices()]);
        } else if (this.moduleKey === 'events') {
          await this.loadCalendarEvents();
        } else if (this.moduleKey === 'sponsors') {
          await Promise.all([this.loadRequests(), this.loadInvoices()]);
        } else if (this.moduleKey === 'fees') {
          await Promise.all([this.loadMembers(), this.loadInvoices(), this.loadPaymentClaims(), this.loadClubProfile()]);
        } else if (this.moduleKey === 'payments') {
          await Promise.all([this.loadAccountsAndTransactions(), this.loadInvoices(), this.loadMembers(), this.loadPaymentClaims(), this.loadClubProfile()]);
          this.syncClaimFormMemberSelection();
        } else if (this.moduleKey === 'reports') {
          await Promise.all([
            this.loadStatistics(),
            this.loadArchive(),
            this.loadTasks(),
            this.loadRequests(),
            this.loadInvoices(),
            this.loadPaymentClaims(),
            this.loadClubDocuments(),
          ]);
        }
      } catch (error) {
        this.loadError = safeErrorMessage(error, `${this.moduleTitle} konnten nicht geladen werden.`);
      } finally {
        this.loading = false;
      }
    },
    resetModuleState() {
      this.loadError = '';
      this.reportExportingKey = '';
      this.documentSaving = false;
      this.clubDocuments = [];
      this.documentSearch = '';
      this.selectedDocumentId = null;
      this.documentForm = {
        id: null,
        title: '',
        documentType: 'other',
        status: 'active',
        visibilityScope: 'board',
        description: '',
        changeNote: '',
        linkedEntityType: '',
        linkedEntityId: '',
        file: null,
      };
      this.calendarEvents = [];
      this.billingTemplates = [];
      this.billingRuns = [];
      this.invoices = [];
      this.invoiceParties = [];
      this.accounts = [];
      this.transactions = [];
      this.requests = [];
      this.members = [];
      this.tasks = [];
      this.paymentClaims = [];
      this.clubProfile = null;
      this.feeRules = [];
      this.reportRequests = [];
      this.reportInvoices = [];
      this.reportPaymentClaims = [];
      this.reportDocuments = [];
      this.selectedEventId = null;
      this.eventForm = {
        id: null,
        title: '',
        eventType: 'club_event',
        status: 'planning',
        description: '',
        location: '',
        startDate: new Date().toISOString().slice(0, 10),
        endDate: new Date().toISOString().slice(0, 10),
        registrationDeadline: '',
      };
      this.sponsorSaving = false;
      this.selectedSponsorPartyId = null;
      this.sponsorSourceRequestId = null;
      this.sponsorPartyForm = {
        id: null,
        name: '',
        status: 'active',
        contractReference: '',
        validFrom: '',
        validTo: '',
        contactName: '',
        email: '',
        phone: '',
        street: '',
        postalCode: '',
        city: '',
        notes: '',
      };
      this.partialPaymentAmounts = {};
      this.statistics = createEmptyStatistics();
      this.archive = createEmptyArchive();
    },
    async loadClubDocuments() {
      const response = await apiClient.get(`/club-documents/${this.currentClub}`);
      this.clubDocuments = Array.isArray(response.data?.documents) ? response.data.documents.map(normalizeDocument) : [];
      if (this.moduleKey === 'reports') {
        this.reportDocuments = this.clubDocuments;
      }
    },
    async loadCalendarEvents() {
      const year = new Date().getFullYear();
      const response = await apiClient.get(`/calendar-events/${this.currentClub}`, { params: { year } });
      this.calendarEvents = Array.isArray(response.data) ? response.data.map(normalizeCalendarEvent) : [];
    },
    async loadBillingTemplates() {
      const response = await apiClient.get(`/billing/templates/${this.currentClub}`);
      this.billingTemplates = response.data?.success && Array.isArray(response.data.templates) ? response.data.templates : [];
    },
    async loadBillingRuns() {
      const response = await apiClient.get(`/billing/runs/${this.currentClub}`);
      this.billingRuns = response.data?.success && Array.isArray(response.data.runs) ? response.data.runs : [];
    },
    async loadInvoices() {
      const response = await apiClient.get(`/club-invoices/${this.currentClub}`);
      this.invoices = Array.isArray(response.data?.invoices) ? response.data.invoices.map(normalizeInvoice) : [];
      this.invoiceParties = Array.isArray(response.data?.parties) ? response.data.parties.map(normalizeParty) : [];
      if (this.moduleKey === 'reports') {
        this.reportInvoices = this.invoices;
      }
    },
    async loadRequests() {
      const response = await apiClient.get(`/club-requests/${this.currentClub}`);
      const entries = Array.isArray(response.data?.requests) ? response.data.requests : Array.isArray(response.data) ? response.data : [];
      this.requests = entries.map(normalizeRequest);
      if (this.moduleKey === 'reports') {
        this.reportRequests = this.requests;
      }
    },
    async loadClubProfile() {
      const response = await apiClient.get(`/clubs/${this.currentClub}`);
      this.clubProfile = response.data || null;
      this.feeRules = Array.isArray(response.data?.feeRules)
        ? response.data.feeRules
        : [];
    },
    async loadMembers() {
      const response = await apiClient.get(`/clubmembers/get/${this.currentClub}/true`);
      this.members = Array.isArray(response.data) ? response.data.map(normalizeMember) : [];
    },
    async loadAccountsAndTransactions() {
      const response = await apiClient.get(`/club-accounts/${this.currentClub}`);
      this.accounts = Array.isArray(response.data?.accounts) ? response.data.accounts : [];
      this.transactions = Array.isArray(response.data?.transactions) ? response.data.transactions.map(normalizeTransaction) : [];
    },
    async loadPaymentClaims() {
      const response = await apiClient.get(`/club-payment-claims/${this.currentClub}`);
      this.paymentClaims = Array.isArray(response.data?.claims) ? response.data.claims.map(normalizePaymentClaim) : [];
      if (this.moduleKey === 'reports') {
        this.reportPaymentClaims = this.paymentClaims;
      }
      this.partialPaymentAmounts = this.paymentClaims.reduce((acc, claim) => {
        acc[claim.id] = 0;
        return acc;
      }, {});
    },
    syncClaimFormMemberSelection() {
      const routeMemberId = Number(this.$route?.query?.memberId || 0);
      if (!routeMemberId) {
        return;
      }
      const match = this.members.find((member) => Number(member.id) === routeMemberId);
      if (!match) {
        return;
      }
      this.claimForm.memberId = String(match.id);
      if (!String(this.claimForm.notes || '').trim()) {
        const contributionGroup = String(match.contributionGroupCode || match.contribution_group_code || '').trim();
        this.claimForm.notes = contributionGroup
          ? `Beitragsforderung für ${this.displayMemberName(match)} (${contributionGroup})`
          : `Beitragsforderung für ${this.displayMemberName(match)}`;
      }
    },
    clearClaimMemberSelection() {
      const nextQuery = { ...this.$route.query };
      delete nextQuery.memberId;
      this.$router.replace({ path: this.$route.path, query: nextQuery });
      if (String(this.claimForm.memberId || '') === String(this.$route?.query?.memberId || '')) {
        this.claimForm.memberId = '';
      }
    },
    async loadStatistics() {
      const response = await apiClient.get(`/club-statistics/${this.currentClub}`);
      this.statistics = response.data && typeof response.data === 'object' ? response.data : createEmptyStatistics();
    },
    async loadArchive() {
      const response = await apiClient.get(`/club-archive/${this.currentClub}`);
      this.archive = response.data && typeof response.data === 'object' ? response.data : createEmptyArchive();
    },
    async loadTasks() {
      const response = await apiClient.get(`/club-tasks/${this.currentClub}`);
      this.tasks = Array.isArray(response.data?.tasks) ? response.data.tasks : [];
    },
    startCreateDocument() {
      this.selectedDocumentId = null;
      this.documentForm = {
        id: null,
        title: '',
        documentType: 'other',
        status: 'active',
        visibilityScope: 'board',
        description: '',
        changeNote: '',
        linkedEntityType: '',
        linkedEntityId: '',
        file: null,
      };
    },
    applyDocumentTemplate(template) {
      if (!template) return;
      this.selectedDocumentId = null;
      this.documentForm = {
        id: null,
        title: template.title || '',
        documentType: template.documentType || 'other',
        status: template.status || 'draft',
        visibilityScope: template.visibilityScope || 'board',
        description: template.description || '',
        changeNote: template.changeNote || '',
        linkedEntityType: '',
        linkedEntityId: '',
        file: null,
      };
    },
    selectDocument(document) {
      if (!document) return;
      this.selectedDocumentId = document.id;
      this.documentForm = {
        id: document.id,
        title: document.title || '',
        documentType: document.documentType || 'other',
        status: document.status || 'active',
        visibilityScope: document.visibilityScope || 'board',
        description: document.description || '',
        changeNote: '',
        linkedEntityType: '',
        linkedEntityId: '',
        file: null,
      };
    },
    onDocumentFileChange(event) {
      const file = event?.target?.files?.[0] || null;
      this.documentForm = {
        ...this.documentForm,
        file,
      };
    },
    async saveDocument() {
      if (!this.currentClub || !this.documentForm.title.trim()) return;
      if (!this.documentForm.id && !this.documentForm.file) {
        this.loadError = 'Für neue Dokumente muss eine Datei ausgewählt werden.';
        return;
      }
      this.documentSaving = true;
      try {
        const formData = new FormData();
        formData.append('title', this.documentForm.title);
        formData.append('documentType', this.documentForm.documentType);
        formData.append('status', this.documentForm.status);
        formData.append('visibilityScope', this.documentForm.visibilityScope);
        formData.append('description', this.documentForm.description || '');
        formData.append('changeNote', this.documentForm.changeNote || '');
        if (this.documentForm.linkedEntityType) {
          formData.append('linkedEntityType', this.documentForm.linkedEntityType);
        }
        if (this.documentForm.linkedEntityId) {
          formData.append('linkedEntityId', this.documentForm.linkedEntityId);
        }
        if (this.documentForm.file) {
          formData.append('document', this.documentForm.file);
        }

        if (this.documentForm.id) {
          await apiClient.put(`/club-documents/${this.currentClub}/${this.documentForm.id}`, formData, {
            headers: { 'Content-Type': 'multipart/form-data' },
          });
        } else {
          await apiClient.post(`/club-documents/${this.currentClub}`, formData, {
            headers: { 'Content-Type': 'multipart/form-data' },
          });
        }
        this.startCreateDocument();
        await this.loadClubDocuments();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Dokument konnte nicht gespeichert werden.');
      } finally {
        this.documentSaving = false;
      }
    },
    async archiveDocument(document) {
      if (!this.currentClub || !document?.id) return;
      try {
        await apiClient.patch(`/club-documents/${this.currentClub}/${document.id}/archive`);
        if (this.documentForm.id === document.id) {
          this.startCreateDocument();
        }
        await this.loadClubDocuments();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Dokument konnte nicht archiviert werden.');
      }
    },
    async deleteDocument(document) {
      if (!this.currentClub || !document?.id) return;
      try {
        await apiClient.delete(`/club-documents/${this.currentClub}/${document.id}`);
        if (this.documentForm.id === document.id) {
          this.startCreateDocument();
        }
        await this.loadClubDocuments();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Dokument konnte nicht gelöscht werden.');
      }
    },
    downloadDocument(document) {
      if (!document?.id) return;
      const tokenQuery = this.token ? `?authcode=${encodeURIComponent(this.token)}` : '';
      window.open(`${backendBaseUrl}/api/club-documents/${this.currentClub}/${document.id}/download${tokenQuery}`, '_blank', 'noopener');
    },
    downloadDocumentVersion(document, version) {
      if (!document?.id || !version?.versionNo) return;
      const tokenQuery = this.token ? `&authcode=${encodeURIComponent(this.token)}` : '';
      window.open(`${backendBaseUrl}/api/club-documents/${this.currentClub}/${document.id}/download?versionNo=${encodeURIComponent(version.versionNo)}${tokenQuery}`, '_blank', 'noopener');
    },
    async saveEvent() {
      if (!this.currentClub || !this.canWriteEvents || !this.eventForm.title || !this.eventForm.startDate) return;
      this.eventSaving = true;
      try {
        const payload = {
          title: this.eventForm.title,
          eventType: this.eventForm.eventType || 'club_event',
          status: this.eventForm.status || 'planning',
          description: this.eventForm.description || null,
          location: this.eventForm.location || null,
          startDate: this.eventForm.startDate,
          endDate: this.eventForm.endDate || this.eventForm.startDate,
        };
        if (this.eventForm.registrationDeadline) {
          payload.registrationDeadline = this.eventForm.registrationDeadline;
        }
        if (this.eventForm.id) {
          await apiClient.put(`/calendar-events/${this.currentClub}/${this.eventForm.id}`, payload);
        } else {
          await apiClient.post(`/calendar-events/${this.currentClub}`, payload);
        }
        this.resetEventForm();
        await this.loadCalendarEvents();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Veranstaltung konnte nicht gespeichert werden.');
      } finally {
        this.eventSaving = false;
      }
    },
    selectEvent(event) {
      if (!event) return;
      this.selectedEventId = event.id;
      this.eventForm = {
        id: event.id,
        title: event.title || '',
        eventType: event.eventType || 'club_event',
        status: event.status || 'planning',
        description: event.description || '',
        location: event.location || '',
        startDate: event.startDate || '',
        endDate: event.endDate || '',
        registrationDeadline: event.registrationDeadline || '',
      };
    },
    resetEventForm() {
      this.selectedEventId = null;
      this.eventForm = {
        id: null,
        title: '',
        eventType: 'club_event',
        status: 'planning',
        description: '',
        location: '',
        startDate: new Date().toISOString().slice(0, 10),
        endDate: new Date().toISOString().slice(0, 10),
        registrationDeadline: '',
      };
    },
    async deleteEvent(event) {
      if (!this.currentClub || !this.canWriteEvents || !event?.id) return;
      try {
        await apiClient.delete(`/calendar-events/${this.currentClub}/${event.id}`);
        if (this.eventForm.id === event.id) {
          this.resetEventForm();
        }
        await this.loadCalendarEvents();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Veranstaltung konnte nicht gelöscht werden.');
      }
    },
    openRoute(path) {
      this.$router.push(path);
    },
    resetClaimForm() {
      this.claimForm = {
        memberId: '',
        claimType: 'membership_fee',
        dueOn: new Date().toISOString().slice(0, 10),
        amountEuro: 0,
        notes: '',
      };
    },
    startCreateSponsorParty() {
      this.selectedSponsorPartyId = null;
      this.sponsorPartyForm = {
        id: null,
        name: '',
        contactName: '',
        email: '',
        phone: '',
        street: '',
        postalCode: '',
        city: '',
        notes: '',
      };
    },
    selectSponsorParty(party) {
      if (!party) return;
      this.selectedSponsorPartyId = party.id;
      this.sponsorSourceRequestId = null;
      this.sponsorPartyForm = {
        id: party.id,
        name: party.name || '',
        status: party.status || 'active',
        contractReference: party.contractReference || '',
        validFrom: party.validFrom || '',
        validTo: party.validTo || '',
        contactName: party.contactName || '',
        email: party.email || '',
        phone: party.phone || '',
        street: party.street || '',
        postalCode: party.postalCode || '',
        city: party.city || '',
        notes: party.notes || '',
      };
    },
    prefillSponsorFromRequest(request) {
      if (!request) return;
      this.selectedSponsorPartyId = null;
      this.sponsorSourceRequestId = request.id;
      this.sponsorPartyForm = {
        id: null,
        name: request.subject?.trim() || this.displayPersonName(request),
        status: 'active',
        contractReference: '',
        validFrom: '',
        validTo: '',
        contactName: this.displayPersonName(request),
        email: request.email || '',
        phone: request.phone || '',
        street: '',
        postalCode: '',
        city: '',
        notes: request.message || '',
      };
    },
    sponsorPartyInvoiceCount(party) {
      if (!party?.id) return 0;
      return this.sponsorInvoices.filter((invoice) => Number(invoice.partyId || 0) === Number(party.id)).length;
    },
    openSponsorInvoiceWorkflow(party) {
      if (!party?.id) return;
      this.$router.push({
        path: '/club-invoices',
        query: {
          partyId: String(party.id),
          invoiceType: 'sponsoring',
          invoiceDirection: 'outgoing',
          source: 'sponsor-workflow',
        },
      });
    },
    async submitSponsorParty() {
      if (!this.currentClub || !this.canWriteSponsors) return;
      const name = String(this.sponsorPartyForm.name || '').trim();
      if (!name) {
        this.loadError = 'Sponsorname fehlt.';
        return;
      }
      this.sponsorSaving = true;
      try {
        const payload = {
          name,
          partyType: 'sponsor',
          status: this.sponsorPartyForm.status,
          contractReference: this.sponsorPartyForm.contractReference,
          validFrom: this.sponsorPartyForm.validFrom || null,
          validTo: this.sponsorPartyForm.validTo || null,
          contactName: this.sponsorPartyForm.contactName,
          email: this.sponsorPartyForm.email,
          phone: this.sponsorPartyForm.phone,
          street: this.sponsorPartyForm.street,
          postalCode: this.sponsorPartyForm.postalCode,
          city: this.sponsorPartyForm.city,
          notes: this.sponsorPartyForm.notes,
        };
        let savedParty = null;
        if (this.sponsorPartyForm.id) {
          const response = await apiClient.put(`/club-invoices/${this.currentClub}/parties/${this.sponsorPartyForm.id}`, payload);
          savedParty = response.data?.party || null;
        } else {
          const response = await apiClient.post(`/club-invoices/${this.currentClub}/parties`, payload);
          savedParty = response.data?.party || null;
        }
        await this.loadInvoices();
        if (this.sponsorSourceRequestId && savedParty) {
          this.sponsorSourceRequestId = null;
          this.openSponsorInvoiceWorkflow(savedParty);
          this.loadError = '';
          return;
        }
        this.sponsorSourceRequestId = null;
        this.startCreateSponsorParty();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Sponsorenstamm konnte nicht gespeichert werden.');
      } finally {
        this.sponsorSaving = false;
      }
    },
    async deleteSponsorParty() {
      if (!this.currentClub || !this.canWriteSponsors || !this.sponsorPartyForm.id) return;
      try {
        await apiClient.delete(`/club-invoices/${this.currentClub}/parties/${this.sponsorPartyForm.id}`);
        await this.loadInvoices();
        this.startCreateSponsorParty();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Sponsorenpartei konnte nicht gelöscht werden.');
      }
    },
    partialPaymentCents(claim) {
      const amountEuro = Number(this.partialPaymentAmounts?.[claim?.id] || 0);
      return Math.round(amountEuro * 100);
    },
    async saveClaim() {
      if (!this.currentClub || !this.canWritePayments) return;
      this.claimSaving = true;
      try {
        await apiClient.post(`/club-payment-claims/${this.currentClub}`, {
          memberId: this.claimForm.memberId || null,
          claimType: this.claimForm.claimType,
          dueOn: this.claimForm.dueOn || null,
          amountCents: Math.round(Number(this.claimForm.amountEuro || 0) * 100),
          notes: this.claimForm.notes,
        });
        this.resetClaimForm();
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Zahlungsforderung konnte nicht gespeichert werden.');
      } finally {
        this.claimSaving = false;
      }
    },
    async setClaimStatus(claim, status) {
      if (!this.currentClub || !this.canWritePayments || !claim?.id) return;
      try {
        await apiClient.patch(`/club-payment-claims/${this.currentClub}/${claim.id}/status`, { status });
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Zahlungsstatus konnte nicht aktualisiert werden.');
      }
    },
    async markClaimPaid(claim) {
      if (!this.currentClub || !this.canWritePayments || !claim?.id) return;
      try {
        await apiClient.patch(`/club-payment-claims/${this.currentClub}/${claim.id}/status`, { status: 'paid' });
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Zahlungsstatus konnte nicht aktualisiert werden.');
      }
    },
    async increaseReminderLevel(claim) {
      if (!this.currentClub || !this.canWritePayments || !claim?.id) return;
      try {
        await apiClient.put(`/club-payment-claims/${this.currentClub}/${claim.id}`, {
          memberId: claim.memberId || claim.member?.id || null,
          feeRuleId: claim.feeRuleId || claim.fee_rule_id || null,
          claimType: claim.claimType,
          status: claim.status,
          dueOn: claim.dueOn,
          amountCents: claim.amountCents,
          currencyCode: claim.currencyCode || 'EUR',
          reminderLevel: Math.max(0, Number(claim.reminderLevel || 0) + 1),
          notes: claim.notes || '',
        });
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Mahnstufe konnte nicht erhöht werden.');
      }
    },
    async registerPartialPayment(claim) {
      if (!this.currentClub || !this.canWritePayments || !claim?.id) return;
      const amountCents = this.partialPaymentCents(claim);
      if (!Number.isFinite(amountCents) || amountCents <= 0) return;
      try {
        const accountId = this.primaryPaymentAccount?.id || null;
        await apiClient.patch(`/club-payment-claims/${this.currentClub}/${claim.id}/payment`, {
          amountCents,
          accountId,
          reference: this.primaryPaymentAccount?.name || '',
        });
        this.partialPaymentAmounts = {
          ...this.partialPaymentAmounts,
          [claim.id]: 0,
        };
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Teilzahlung konnte nicht gespeichert werden.');
      }
    },
    async cancelClaim(claim) {
      if (!this.currentClub || !this.canWritePayments || !claim?.id) return;
      try {
        await apiClient.patch(`/club-payment-claims/${this.currentClub}/${claim.id}/status`, { status: 'cancelled' });
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Zahlungsforderung konnte nicht storniert werden.');
      }
    },
    async deleteClaim(claim) {
      if (!this.currentClub || !this.canWritePayments || !claim?.id) return;
      try {
        await apiClient.delete(`/club-payment-claims/${this.currentClub}/${claim.id}`);
        await this.loadPaymentClaims();
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Zahlungsforderung konnte nicht gelöscht werden.');
      }
    },
    openMemberFromClaim(claim) {
      const memberId = Number(claim?.memberId || claim?.member?.id || 0);
      if (!memberId) {
        return;
      }
      this.$router.push({ path: '/members', query: { memberId: String(memberId) } });
    },
    displayMemberName(member) {
      return [member?.lastName, member?.firstName].filter(Boolean).join(', ') || member?.email || `Mitglied #${member?.id || '?'}`;
    },
    displayClaimType(type) {
      return {
        membership_fee: 'Mitgliedsbeitrag',
        additional_fee: 'Zusatzbeitrag',
        course_fee: 'Kursgebühr',
        penalty_fee: 'Mahn- oder Strafgebühr',
        other: 'Sonstige',
      }[type] || type || 'Forderung';
    },
    displayFeeCycle(cycle) {
      return {
        monthly: 'monatlich',
        quarterly: 'vierteljährlich',
        half_yearly: 'halbjährlich',
        yearly: 'jährlich',
        one_time: 'einmalig',
      }[cycle] || cycle || 'Rhythmus';
    },
    displayClaimStatus(status) {
      return {
        open: 'Offen',
        partially_paid: 'Teilbezahlt',
        paid: 'Bezahlt',
        written_off: 'Abgeschrieben',
        cancelled: 'Storniert',
      }[status] || status || 'Status';
    },
    claimUrgencyScore(claim) {
      const dueDate = new Date(claim?.dueOn || 0);
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      dueDate.setHours(0, 0, 0, 0);
      const isOverdue = !Number.isNaN(dueDate.getTime()) && dueDate.getTime() < today.getTime();
      return (isOverdue ? 1000 : 0) + Number(claim?.reminderLevel || 0) * 100 - dueDate.getTime() / 86400000;
    },
    claimActionLabel(claim) {
      const dueDate = new Date(claim?.dueOn || 0);
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      dueDate.setHours(0, 0, 0, 0);
      if (!Number.isNaN(dueDate.getTime()) && dueDate.getTime() < today.getTime()) {
        return claim.reminderLevel > 0 ? `Überfällig · ${this.formatReminderStage(claim.reminderLevel)}` : 'Überfällig';
      }
      return claim.reminderLevel > 0 ? `Erinnerung · ${this.formatReminderStage(claim.reminderLevel)}` : 'Bald fällig';
    },
    formatReminderStage(reminderLevel) {
      const level = Math.max(0, Number.parseInt(reminderLevel, 10) || 0);
      if (level <= 0) return 'Keine Mahnung';
      if (level === 1) return '1. Mahnung';
      if (level === 2) return '2. Mahnung';
      return 'Letzte Mahnung';
    },
    displayPersonName(item) {
      return [item?.firstName, item?.lastName].filter(Boolean).join(' ').trim() || item?.email || 'Unbekannt';
    },
    displayRequestType(requestType) {
      return {
        contact: 'Kontakt',
        trial_training: 'Probetraining',
        membership: 'Mitgliedschaft',
        sponsoring: 'Sponsoring',
      }[requestType] || requestType || 'Anfrage';
    },
    displayRequestStatus(status) {
      return {
        open: 'Offen',
        in_progress: 'In Bearbeitung',
        waiting: 'Wartend',
        converted: 'Überführt',
        rejected: 'Abgelehnt',
        archived: 'Archiviert',
      }[status] || status || 'Status';
    },
    requestWorkflowStageLabel(stage) {
      return {
        contact_replied: 'Kontakt beantwortet',
        trial_training_scheduled: 'Probetraining terminiert',
        trial_training_feedback_recorded: 'Probetraining nachbereitet',
        membership_reviewed: 'Mitgliedsanfrage geprüft',
        admission_prepared: 'Aufnahme vorbereitet',
        member_record_created: 'Mitglied angelegt',
        sepa_pending: 'SEPA ausstehend',
        onboarding_completed: 'Onboarding abgeschlossen',
        sponsoring_contacted: 'Sponsoring kontaktiert',
        sponsoring_offer_prepared: 'Sponsoringangebot vorbereitet',
        sponsoring_followed_up: 'Sponsoring nachgefasst',
      }[stage] || stage || 'Workflow';
    },
    displayDocumentType(documentType) {
      return {
        satzung: 'Satzung',
        protokoll: 'Protokoll',
        nachweis: 'Nachweis',
        formular: 'Formular',
        vertrag: 'Vertrag',
        rechnung: 'Rechnung',
        other: 'Sonstiges',
      }[documentType] || documentType || 'Dokument';
    },
    displayDocumentStatus(status) {
      return {
        active: 'Aktiv',
        draft: 'Entwurf',
        archived: 'Archiviert',
        obsolete: 'Veraltet',
      }[status] || status || 'Status';
    },
    displayVisibilityScope(scope) {
      return {
        board: 'Vorstand',
        finance: 'Finanzen',
        trainers: 'Trainer',
        all: 'Freigegeben',
      }[scope] || scope || 'Sichtbarkeit';
    },
    displayDocumentText(value) {
      return stripRichTextToText(value || '');
    },
    displayEventType(eventType) {
      return {
        club_event: 'Vereinsveranstaltung',
        meeting: 'Sitzung',
        tournament: 'Turnier',
        social: 'Geselligkeit',
        workshop: 'Workshop',
        other: 'Sonstige',
      }[eventType] || eventType || 'Termin';
    },
    displayEventStatus(status) {
      return {
        planning: 'Planung',
        invited: 'Eingeladen',
        confirmed: 'Bestätigt',
        done: 'Durchgeführt',
        cancelled: 'Abgesagt',
      }[status] || status || 'Status';
    },
    displaySponsorStatus(status) {
      return {
        active: 'Aktiv',
        prospect: 'Interessent',
        paused: 'Pausiert',
        ended: 'Beendet',
      }[status] || status || 'Status';
    },
    displaySponsorPeriod(party) {
      const from = party?.validFrom ? this.formatDate(party.validFrom) : 'Start offen';
      const to = party?.validTo ? this.formatDate(party.validTo) : 'ohne Ende';
      return `${from} · ${to}`;
    },
    displayInvoiceStatus(status) {
      return {
        draft: 'Entwurf',
        issued: 'Gestellt',
        partially_paid: 'Teilbezahlt',
        paid: 'Bezahlt',
        cancelled: 'Storniert',
        archived: 'Archiviert',
      }[status] || status || 'Status';
    },
    displayTransactionDirection(direction) {
      return direction === 'debit' ? 'Ausgabe' : 'Einnahme';
    },
    displayTransactionStatus(status) {
      return {
        planned: 'Geplant',
        booked: 'Gebucht',
        cancelled: 'Storniert',
      }[status] || status || 'Status';
    },
    formatDate(value) {
      if (!value) return '–';
      const date = new Date(value);
      if (Number.isNaN(date.getTime())) return '–';
      return new Intl.DateTimeFormat('de-DE', { dateStyle: 'medium' }).format(date);
    },
    formatCurrency(amountCents, currencyCode = 'EUR') {
      return formatCurrency(amountCents, currencyCode);
    },
    async exportReport(report, format) {
      if (!report || !format) return;
      this.reportExportingKey = report.key;
      try {
        const timestamp = new Date().toISOString().slice(0, 10);
        const filename = `bericht-${report.key}-${timestamp}.${format}`;
        if (format === 'csv') {
          downloadCsvReport(filename, report.csvSections || []);
        } else if (format === 'pdf') {
          exportReportPdf({
            filename,
            title: `${report.title} - Bericht`,
            subtitle: report.summary,
            sections: report.pdfSections || [],
          });
        }
      } catch (error) {
        this.loadError = safeErrorMessage(error, 'Bericht konnte nicht exportiert werden.');
      } finally {
        this.reportExportingKey = '';
      }
    },
    formatTransactionAmount(transaction) {
      const amount = Number(transaction?.amountCents || 0) / 100;
      const signedAmount = transaction?.direction === 'debit' ? amount * -1 : amount;
      return new Intl.NumberFormat('de-DE', {
        style: 'currency',
        currency: transaction?.currencyCode || 'EUR',
      }).format(signedAmount);
    },
    formatFileSize(bytes) {
      const value = Number(bytes || 0);
      if (!value) return '0 B';
      const units = ['B', 'KB', 'MB', 'GB'];
      const index = Math.min(units.length - 1, Math.floor(Math.log(value) / Math.log(1024)));
      return `${(value / (1024 ** index)).toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
    },
  },
};

function formatCurrency(amountCents, currencyCode = 'EUR') {
  return new Intl.NumberFormat('de-DE', {
    style: 'currency',
    currency: currencyCode || 'EUR',
  }).format(Number(amountCents || 0) / 100);
}
</script>

<style scoped>
.club-operations-page {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.page-header,
.empty-state,
.section-card {
  padding: 1.25rem;
}

.page-header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
}

.page-eyebrow {
  margin: 0 0 0.35rem;
  color: var(--text-light);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.8rem;
}

.page-subtitle {
  margin: 0.35rem 0 0;
  color: var(--text-secondary);
  max-width: 72ch;
}

.header-actions,
.inline-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.stacked-actions {
  flex-direction: column;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
}

.stat-card {
  padding: 1rem 1.1rem;
}

.stat-label,
.stat-meta,
label span,
.item-meta {
  color: var(--text-secondary);
}

.stat-value {
  display: block;
  margin-top: 0.35rem;
  font-size: 1.8rem;
}

.workspace-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(300px, 0.95fr);
  gap: 1rem;
}

.worklist-summary {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.9rem;
}

.worklist-chip {
  display: inline-flex;
  align-items: center;
  border: 1px solid rgba(24, 70, 54, 0.14);
  border-radius: 999px;
  padding: 0.35rem 0.7rem;
  background: rgba(255, 255, 255, 0.72);
  color: var(--text-primary);
  font-size: 0.88rem;
}

.worklist-selection-banner {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  justify-content: space-between;
  margin-bottom: 0.9rem;
}

.workspace-main,
.workspace-side,
.item-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.compact-list {
  gap: 0.75rem;
}

.section-header {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  align-items: center;
  margin-bottom: 1rem;
}

.template-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.85rem;
  margin-bottom: 1rem;
}

.report-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.85rem;
}

.template-card,
.selected-document-panel {
  border: 1px solid rgba(24, 70, 54, 0.12);
  border-radius: 16px;
  padding: 0.95rem 1rem;
  background: rgba(250, 253, 251, 0.9);
}

.report-card {
  display: grid;
  gap: 0.85rem;
}

.report-metric-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem;
}

.report-metric {
  display: grid;
  gap: 0.2rem;
  padding: 0.7rem 0.75rem;
  border-radius: 12px;
  background: rgba(24, 70, 54, 0.04);
}

.report-metric strong {
  font-size: 1.05rem;
  color: var(--text-primary);
}

.selected-document-panel {
  display: grid;
  gap: 0.9rem;
  margin-top: 0.5rem;
}

.state-banner {
  margin: 0;
}

.state-banner-error {
  color: #9a1d1d;
}

.form-grid,
.two-column-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.85rem;
}

label {
  display: grid;
  gap: 0.35rem;
}

.full-width {
  grid-column: 1 / -1;
}

input,
textarea,
select {
  width: 100%;
}

.table-wrap {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table th,
.data-table td {
  padding: 0.75rem;
  border-bottom: 1px solid rgba(15, 23, 42, 0.08);
  text-align: left;
  vertical-align: top;
}

.item-card {
  border: 1px solid rgba(24, 70, 54, 0.12);
  border-radius: 16px;
  padding: 0.95rem 1rem;
  background: rgba(255, 255, 255, 0.88);
}

.compact-card {
  padding: 0.8rem 0.9rem;
}

.clickable-card {
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
}

.clickable-card:hover,
.clickable-card.active {
  border-color: rgba(24, 70, 54, 0.32);
  background: rgba(245, 250, 247, 0.98);
  transform: translateY(-1px);
}

.sponsor-form {
  margin-bottom: 1rem;
  padding: 1rem;
}

.item-topline {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
}

.item-badge {
  display: inline-flex;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: rgba(24, 70, 54, 0.08);
  color: var(--primary-strong);
  font-size: 0.8rem;
  font-weight: 700;
}

@media (max-width: 980px) {
  .stats-grid,
  .workspace-grid,
  .form-grid,
  .two-column-grid,
  .template-grid,
  .report-grid,
  .report-metric-grid {
    grid-template-columns: 1fr;
  }

  .page-header {
    flex-direction: column;
  }
}
</style>
