<template>
    <div class="members-page">
        <MembersOverviewSection
            :member-form-is-open="memberFormIsOpen"
            :is-updating-ratings="isUpdatingRatings"
            :active-members-count="activeMembersCount"
            :test-members-count="testMembersCount"
            :inactive-members-count="inactiveMembersCount"
            :visible-members-count="filteredMembers.length"
            :search-query="searchQuery"
            :member-scope-options="memberScopeOptions"
            :selected-member-scope="selectedMemberScope"
            :show-inactive-members="showInactiveMembers"
            :show-training-participations-column="showTrainingParticipationsColumn"
            :selected-age-group="selectedAgeGroup"
            :selected-season-start-year="selectedSeasonStartYear"
            :season-filter-options="ttSeasonFilterOptions"
            :selected-age-from="selectedAgeFrom"
            :selected-age-to="selectedAgeTo"
            :selected-gender="selectedGender"
            :selected-training-group="selectedTrainingGroup"
            :selected-last-training-filter="selectedLastTrainingFilter"
            :training-group-options="trainingGroupFilterOptions"
            :selected-sort="selectedSort"
            :sort-direction="sortDirection"
            :filtered-members-without-form-count="filteredMembersWithoutForm.length"
            :filtered-test-members-count="filteredTestMembers.length"
            :filtered-members-with-phones-count="filteredMembersWithPhonesCount"
            :filtered-members-with-emails-count="filteredMembersWithEmailsCount"
            :export-preview-names="exportPreviewNames"
            @toggle-new-member="toggleNewMember"
            @create-phone-list="createPhoneList"
            @open-group-photo-crop="showGroupPhotoCropDialog = true"
            @update-ratings="updateRatingsFromMyTischtennis"
            @open-transfer-dialog="openTransferDialog"
            @update:search-query="searchQuery = $event"
            @update:selected-member-scope="selectedMemberScope = $event"
            @update:show-inactive-members="showInactiveMembers = $event"
            @update:show-training-participations-column="setShowTrainingParticipationsColumn"
            @update:selected-age-group="selectedAgeGroup = $event"
            @update:selected-season-start-year="selectedSeasonStartYear = $event"
            @update:selected-age-from="selectedAgeFrom = $event"
            @update:selected-age-to="selectedAgeTo = $event"
            @update:selected-gender="selectedGender = $event"
            @update:selected-training-group="selectedTrainingGroup = $event"
            @update:selected-last-training-filter="selectedLastTrainingFilter = $event"
            @clear-filters="clearFilters"
            @update:selected-sort="selectedSort = $event"
            @toggle-sort-direction="toggleSortDirection"
            @create-phone-list-for-filtered="createPhoneListForFiltered"
            @mark-filtered-forms-handed-over="markFilteredFormsHandedOver"
            @mark-filtered-as-regular="markFilteredAsRegular"
            @export-filtered-members-csv="exportFilteredMembersCsv"
            @copy-filtered-phones="copyFilteredPhones"
            @copy-filtered-emails="copyFilteredEmails"
            @compose-email-to-filtered="composeEmailToFiltered"
            @copy-filtered-contact-summary="copyFilteredContactSummary"
        />

        <div v-if="isLoadingMembers" class="members-state-banner members-state-banner-info">
            {{ $t('members.loadingMembers') }}
        </div>

        <div v-else-if="membersLoadError" class="members-state-banner members-state-banner-error">
            {{ membersLoadError }}
        </div>

        <section v-else class="card members-contribution-summary">
            <div class="section-header">
                <h3>Beiträge</h3>
            </div>
            <div class="members-summary-grid">
                <article class="members-summary-card">
                    <span class="members-summary-label">Mit Beitragsgruppe</span>
                    <strong class="members-summary-value">{{ memberContributionStats.assignedGroups }}</strong>
                </article>
                <article class="members-summary-card">
                    <span class="members-summary-label">Ohne Beitragsgruppe</span>
                    <strong class="members-summary-value">{{ memberContributionStats.missingGroups }}</strong>
                </article>
                <article class="members-summary-card">
                    <span class="members-summary-label">Offene Forderungen</span>
                    <strong class="members-summary-value">{{ memberContributionStats.openClaims }}</strong>
                </article>
                <article class="members-summary-card">
                    <span class="members-summary-label">Offener Rest</span>
                    <strong class="members-summary-value">{{ formatCurrency(memberContributionStats.openAmountCents) }}</strong>
                </article>
            </div>
            <p class="members-summary-note">
                {{ memberContributionStats.missingGroups }} Mitglieder sind noch keiner Beitragsgruppe zugeordnet.
            </p>
        </section>

        <BaseDialog
            :model-value="!!selectedMemberPreview"
            :title="$t('members.memberDetails')"
            size="large"
            :width="1080"
            max-width="95vw"
            @update:model-value="!$event && closeMemberPreviewDialog()"
            @close="closeMemberPreviewDialog"
        >
            <div v-if="selectedMemberPreview" class="member-preview-panel">
                <div class="member-preview-header">
                    <div>
                        <div class="member-preview-label">{{ $t('members.memberDetails') }}</div>
                        <h3 class="member-preview-name">{{ selectedMemberPreview.firstName }} {{ selectedMemberPreview.lastName }}</h3>
                    </div>
                    <div class="member-preview-actions">
                        <button type="button" class="btn-primary" @click="editMember(selectedMemberPreview)">{{ $t('members.editMember') }}</button>
                        <button type="button" @click="openPaymentsForMember(selectedMemberPreview)">Forderung anlegen</button>
                        <button
                            v-if="canShowClickTtRegistration(selectedMemberPreview)"
                            type="button"
                            @click="requestClickTtRegistration(selectedMemberPreview)"
                            :disabled="isClickTtRegistrationPending(selectedMemberPreview)"
                        >
                            {{ isClickTtRegistrationPending(selectedMemberPreview) ? 'Anmeldung läuft…' : 'HTTV-Anmeldung' }}
                        </button>
                        <button type="button" @click="openOrdersDialog(selectedMemberPreview)">{{ $t('orders.title') }}</button>
                        <button type="button" @click="openImageModal(selectedMemberPreview)">{{ $t('members.memberImages') }}</button>
                        <button type="button" @click="openNotesModal(selectedMemberPreview)">{{ $t('members.notes') }}</button>
                        <button type="button" @click="openActivitiesModal(selectedMemberPreview)">{{ $t('members.exercises') }}</button>
                    </div>
                </div>
                <div class="member-preview-grid">
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.status') }}</span>
                        <div class="member-status-stack">
                            <span
                                v-for="badge in getMemberStatusBadges(selectedMemberPreview)"
                                :key="badge.key"
                                class="member-status-badge"
                                :class="`member-status-badge-${badge.tone}`"
                            >
                                {{ badge.label }}
                            </span>
                        </div>
                    </div>
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.contact') }}</span>
                        <div>{{ getFormattedPhoneNumbers(selectedMemberPreview) }}</div>
                        <div>{{ getFormattedEmails(selectedMemberPreview) }}</div>
                        <div class="member-preview-muted">{{ getCompactAddress(selectedMemberPreview) || $t('members.noAddressShort') }}</div>
                    </div>
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.birthdate') }}</span>
                        <div>{{ getFormattedBirthdate(selectedMemberPreview.birthDate) }}</div>
                    </div>
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.previewLastTraining') }}</span>
                        <div>{{ getOptionalFormattedDate(selectedMemberPreview.lastTraining, 'members.previewNoLastTraining') }}</div>
                        <div class="member-preview-muted" v-if="selectedMemberPreview.notInTraining">{{ $t('members.notInTrainingTooltip', { weeks: selectedMemberPreview.missedTrainingWeeks || 0 }) }}</div>
                    </div>
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.trainingGroups') }}</span>
                        <div v-if="getPreviewTrainingGroups(selectedMemberPreview).length" class="member-preview-tags">
                            <span
                                v-for="group in getPreviewTrainingGroups(selectedMemberPreview)"
                                :key="group.id"
                                class="member-preview-tag"
                            >
                                {{ group.name }}
                            </span>
                        </div>
                        <div v-else class="member-preview-muted">{{ $t('members.noGroupsAssigned') }}</div>
                    </div>
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.dataQuality') }}</span>
                        <div v-if="getMemberDataQualityIssues(selectedMemberPreview).length" class="member-preview-tags">
                            <span
                                v-for="issue in getMemberDataQualityIssues(selectedMemberPreview)"
                                :key="issue.key"
                                class="member-preview-tag member-preview-tag-warning"
                            >
                                {{ issue.label }}
                            </span>
                        </div>
                        <div v-else class="member-preview-muted">{{ $t('members.dataQualityComplete') }}</div>
                    </div>
                    <div class="member-preview-item">
                        <span class="member-preview-item-label">{{ $t('members.openTasks') }}</span>
                        <div v-if="getMemberOpenTasks(selectedMemberPreview).length" class="member-preview-tasklist">
                            <div
                                v-for="task in getMemberOpenTasks(selectedMemberPreview)"
                                :key="task.key"
                                class="member-preview-task"
                            >
                                <span>{{ task.label }}</span>
                                <button
                                    v-if="task.action"
                                    type="button"
                                    class="member-preview-task-action"
                                    @click="runPreviewTask(task.action, selectedMemberPreview)"
                                >
                                    {{ task.actionLabel }}
                                </button>
                            </div>
                        </div>
                        <div v-else class="member-preview-muted">{{ $t('members.noOpenTasks') }}</div>
                    </div>
                </div>
            </div>
            <template #footer>
                <button type="button" class="cancel-action" @click="closeMemberPreviewDialog">{{ $t('common.close') }}</button>
            </template>
        </BaseDialog>

        <BaseDialog
            :model-value="memberFormIsOpen"
            :title="memberToEdit === null ? $t('members.newMember') : $t('members.editMember')"
            size="large"
            :width="1120"
            max-width="95vw"
            @update:model-value="!$event && closeMemberFormDialog()"
            @close="closeMemberFormDialog"
        >
            <div class="newmember" v-if="memberFormIsOpen">
                <div class="toggle-new-member">
                    <div class="member-editor-heading">
                        <span class="add">{{ memberToEdit === null ? '+' : '✎' }}</span>
                        <div>
                            <div class="member-editor-title">{{ memberToEdit === null ? $t('members.newMember') : $t('members.editMember') }}</div>
                            <div class="member-editor-subtitle">
                                {{ memberToEdit === null ? $t('members.editorCreateHint') : $t('members.editorEditHint', { name: `${memberToEdit.firstName} ${memberToEdit.lastName}` }) }}
                            </div>
                        </div>
                    </div>
                    <button v-if="memberToEdit !== null" @click="resetToNewMember">{{ $t('members.createNewMember') }}</button>
                </div>
                <div class="new-member-form">
                <label><span>{{ $t('members.firstName') }}:</span> <input type="text" v-model="newFirstname"></label>
                <label><span>{{ $t('members.lastName') }}:</span> <input type="text" v-model="newLastname"></label>
                <label :class="{ 'member-field-warning': editorHasIssue('street') }">
                    <span>{{ $t('members.street') }}:</span>
                    <input type="text" v-model="newStreet" :class="{ 'input-warning': editorHasIssue('street') }">
                    <small v-if="editorHasIssue('street')" class="member-editor-field-hint">{{ $t('members.editorRecommendedEntry') }}</small>
                </label>
                <label :class="{ 'member-field-warning': editorHasIssue('postal-code') }">
                    <span>{{ $t('members.postalCode') }}:</span>
                    <input type="text" v-model="newPostalCode" maxlength="10" :class="{ 'input-warning': editorHasIssue('postal-code') }">
                    <small v-if="editorHasIssue('postal-code')" class="member-editor-field-hint">{{ $t('members.editorRecommendedEntry') }}</small>
                </label>
                <label :class="{ 'member-field-warning': editorHasIssue('city') }">
                    <span>{{ $t('members.city') }}:</span>
                    <input type="text" v-model="newCity" :class="{ 'input-warning': editorHasIssue('city') }">
                    <small v-if="editorHasIssue('city')" class="member-editor-field-hint">{{ $t('members.editorRecommendedEntry') }}</small>
                </label>
                <label :class="{ 'member-field-warning': editorHasIssue('birthdate') }">
                    <span>{{ $t('members.birthdate') }}:</span>
                    <input type="date" v-model="newBirthdate" :class="{ 'input-warning': editorHasIssue('birthdate') }">
                    <small v-if="editorHasIssue('birthdate')" class="member-editor-field-hint">{{ $t('members.editorRecommendedEntry') }}</small>
                </label>
                
                <!-- Telefonnummern -->
                <div class="contact-section" :class="{ 'member-field-warning-box': editorHasIssue('phone') }">
                    <label><span>{{ $t('members.phones') }}:</span></label>
                    <div v-if="editorHasIssue('phone')" class="member-editor-field-hint member-editor-frame-hint">{{ $t('members.editorRecommendedEntry') }}</div>
                    <div v-for="(phone, index) in memberContacts.phones" :key="'phone-' + index" class="contact-item">
                        <input type="text" v-model="phone.value" :placeholder="$t('members.phoneNumber')" class="contact-input" :class="{ 'input-warning': editorHasIssue('phone') && !phone.value }">
                        <label class="checkbox-inline">
                            <input type="checkbox" v-model="phone.isParent"> {{ $t('members.parent') }}
                        </label>
                        <input v-if="phone.isParent" type="text" v-model="phone.parentName" :placeholder="$t('members.parentName')" class="parent-name-input">
                        <label class="checkbox-inline">
                            <input type="checkbox" v-model="phone.isPrimary"> {{ $t('members.primary') }}
                        </label>
                        <button type="button" @click="removeContact('phone', index)" class="btn-remove-contact">✕</button>
                    </div>
                    <button type="button" @click="addContact('phone')" class="btn-add-contact">+ {{ $t('members.addPhone') }}</button>
                </div>
                
                <!-- E-Mail-Adressen -->
                <div class="contact-section" :class="{ 'member-field-warning-box': editorHasIssue('email') }">
                    <label><span>{{ $t('members.emails') }}:</span></label>
                    <div v-if="editorHasIssue('email')" class="member-editor-field-hint member-editor-frame-hint">{{ $t('members.editorRecommendedEntry') }}</div>
                    <div v-for="(email, index) in memberContacts.emails" :key="'email-' + index" class="contact-item">
                        <input type="email" v-model="email.value" :placeholder="$t('members.emailAddress')" class="contact-input" :class="{ 'input-warning': editorHasIssue('email') && !email.value }">
                        <label class="checkbox-inline">
                            <input type="checkbox" v-model="email.isParent"> {{ $t('members.parent') }}
                        </label>
                        <input v-if="email.isParent" type="text" v-model="email.parentName" :placeholder="$t('members.parentName')" class="parent-name-input">
                        <label class="checkbox-inline">
                            <input type="checkbox" v-model="email.isPrimary"> {{ $t('members.primary') }}
                        </label>
                        <button type="button" @click="removeContact('email', index)" class="btn-remove-contact">✕</button>
                    </div>
                    <button type="button" @click="addContact('email')" class="btn-add-contact">+ {{ $t('members.addEmail') }}</button>
                </div>
                <label :class="{ 'member-field-warning': editorHasIssue('gender') }"><span>{{ $t('members.gender') }}:</span>
                    <select v-model="newGender" :class="{ 'input-warning': editorHasIssue('gender') }">
                        <option value="unknown">{{ $t('members.genderUnknown') }}</option>
                        <option value="male">{{ $t('members.genderMale') }}</option>
                        <option value="female">{{ $t('members.genderFemale') }}</option>
                        <option value="diverse">{{ $t('members.genderDiverse') }}</option>
                    </select>
                    <small v-if="editorHasIssue('gender')" class="member-editor-field-hint">{{ $t('members.editorRecommendedEntry') }}</small>
                </label>
                <label class="checkbox-item"><span>{{ $t('members.active') }}:</span> <input type="checkbox" v-model="newActive"></label>
                <label class="checkbox-item"><span>{{ $t('members.picsInInternetAllowed') }}:</span> <input type="checkbox" v-model="newPicsInInternetAllowed"></label>
                <label class="checkbox-item"><span>{{ $t('members.testMembership') }}:</span> <input type="checkbox" v-model="testMembership"></label>
                <label class="checkbox-item"><span>{{ $t('members.memberFormHandedOver') }}:</span> <input type="checkbox" v-model="newMemberFormHandedOver"></label>
                <label class="checkbox-item"><span>{{ $t('members.adultReleaseApproved') }}:</span> <input type="checkbox" v-model="newAdultReleaseApproved"></label>
                <label class="checkbox-item"><span>{{ $t('members.adultReserveApproved') }}:</span> <input type="checkbox" v-model="newAdultReserveApproved"></label>

                <div class="contact-section">
                    <label><span>Beitragsgruppe:</span></label>
                    <input
                        type="text"
                        v-model.trim="newContributionGroupCode"
                        placeholder="z. B. Jugend, Erwachsene, Familie"
                    >
                    <div class="member-editor-field-hint">
                        {{ getContributionGroupHint(getContributionGroupContext()) }}
                    </div>
                    <div class="member-contribution-presets">
                        <button
                            v-for="preset in getContributionGroupPresets(getContributionGroupContext())"
                            :key="preset.code"
                            type="button"
                            class="btn-secondary member-contribution-preset"
                            @click="applyContributionGroupPreset(preset.code)"
                        >
                            {{ preset.label }}
                        </button>
                    </div>
                </div>

                <!-- Trainingsgruppen -->
                <div class="contact-section" :class="{ 'member-field-warning-box': editorHasIssue('training-group') }" v-if="memberToEdit">
                    <label><span>{{ $t('members.trainingGroups') }}:</span></label>
                    <div v-if="memberTrainingGroups.length > 0" class="member-groups-list">
                        <span 
                            v-for="group in memberTrainingGroups" 
                            :key="group.id"
                            class="member-group-tag"
                        >
                            {{ group.name }}
                            <button 
                                @click="removeMemberFromGroup(group.id)"
                                class="remove-group-btn"
                                :title="$t('members.remove')"
                            >
                                ×
                            </button>
                        </span>
                    </div>
                    <div v-else class="no-groups-hint">{{ $t('members.noGroupsAssigned') }}</div>
                    <div v-if="editorHasIssue('training-group')" class="member-editor-field-hint">{{ $t('members.editorAssignTrainingGroupHint') }}</div>
                    <select 
                        v-model="selectedGroupToAdd"
                        class="group-select"
                        :class="{ 'input-warning': editorHasIssue('training-group') }"
                        @change="addMemberToGroup($event.target.value)"
                        :disabled="availableGroupsForMember.length === 0"
                    >
                        <option value="">
                            {{ availableGroupsForMember.length === 0 ? $t('members.noGroupsAvailable') : $t('members.addGroup') }}
                        </option>
                        <option 
                            v-for="group in availableGroupsForMember" 
                            :key="group.id"
                            :value="group.id"
                        >
                            {{ group.name }}
                        </option>
                    </select>
                </div>

                <div v-if="memberToEdit && canEditMemberBankAccount" class="contact-section member-sepa-section">
                    <label><span>{{ $t('members.bankAccountSection') }}:</span></label>
                    <div v-if="memberSepaMandateLoading" class="no-groups-hint">{{ $t('members.bankAccountLoading') }}</div>
                    <div v-else class="member-sepa-grid">
                        <label>
                            <span>{{ $t('members.accountHolder') }}:</span>
                            <input v-model="memberSepaMandateForm.debtorName" type="text" autocomplete="off">
                        </label>
                        <label>
                            <span>{{ $t('members.iban') }}:</span>
                            <input v-model="memberSepaMandateForm.iban" type="text" maxlength="34" autocomplete="off">
                        </label>
                        <label>
                            <span>{{ $t('members.bic') }}:</span>
                            <input v-model="memberSepaMandateForm.bic" type="text" maxlength="11" autocomplete="off">
                        </label>
                        <label>
                            <span>{{ $t('members.mandateReference') }}:</span>
                            <input v-model="memberSepaMandateForm.mandateReference" type="text" maxlength="80" autocomplete="off">
                        </label>
                        <label>
                            <span>{{ $t('members.signedOn') }}:</span>
                            <input v-model="memberSepaMandateForm.signedOn" type="date">
                        </label>
                        <label>
                            <span>{{ $t('members.validFrom') }}:</span>
                            <input v-model="memberSepaMandateForm.validFrom" type="date">
                        </label>
                        <label>
                            <span>{{ $t('members.bankAccountStatus') }}:</span>
                            <select v-model="memberSepaMandateForm.status">
                                <option value="active">{{ $t('members.bankAccountStatusActive') }}</option>
                                <option value="pending">{{ $t('members.bankAccountStatusPending') }}</option>
                                <option value="revoked">{{ $t('members.bankAccountStatusRevoked') }}</option>
                            </select>
                        </label>
                        <label class="member-sepa-grid-full">
                            <span>{{ $t('members.bankAccountNote') }}:</span>
                            <textarea v-model="memberSepaMandateForm.historyNote" rows="3"></textarea>
                        </label>
                    </div>
                    <div v-if="memberSepaMandateError" class="members-state-banner members-state-banner-error">
                        {{ memberSepaMandateError }}
                    </div>
                    <div class="member-sepa-actions">
                        <button type="button" @click="saveMemberSepaMandate" :disabled="memberSepaMandateLoading">
                            {{ $t('members.saveBankAccount') }}
                        </button>
                    </div>
                </div>

                <div v-if="memberToEdit && canManageMembers" class="contact-section member-clicktt-section">
                    <label><span>HTTV-/click-TT</span></label>
                    <div class="member-clicktt-actions">
                        <button
                            v-if="canShowClickTtRegistration(memberToEdit)"
                            type="button"
                            class="member-clicktt-button"
                            @click="requestClickTtRegistration(memberToEdit)"
                            :disabled="isClickTtRegistrationPending(memberToEdit)"
                        >
                            {{ isClickTtRegistrationPending(memberToEdit) ? 'Anmeldung läuft…' : 'Spielberechtigung / Anmeldung übermitteln' }}
                        </button>
                        <div v-else class="member-clicktt-hint">
                            {{ clickTtRegistrationAvailabilityText(memberToEdit) }}
                        </div>
                        <div v-if="memberToEdit.clickTtApplicationSubmitted" class="member-clicktt-status">
                            Antrag bereits an click-TT übermittelt.
                        </div>
                    </div>
                </div>
                
                    <label><span>{{ $t('members.image') }}:</span>
                        <div style="display: flex; gap: 10px; align-items: center;">
                            <input type="file" accept="image/*" @change="onFileSelected" ref="fileInput" style="display: none;" id="member-image-file">
                            <label for="member-image-file" style="cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; background: #f5f5f5;">📁 {{ $t('members.selectFile') }}</label>
                            <input type="file" accept="image/*" capture="environment" @change="onFileSelected" ref="cameraInput" style="display: none;" id="member-image-camera">
                            <label for="member-image-camera" style="cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; background: #f5f5f5;">📷 {{ $t('members.camera') }}</label>
                        </div>
                    </label>
                    <div v-if="memberImagePreview">
                        <img :src="memberImagePreview" :alt="$t('members.imagePreview')"
                            style="max-width: 200px; max-height: 200px;">
                    </div>
                    <div>
                        <button @click="addNewMember">{{ memberToEdit ? $t('members.change') : $t('members.create') }}</button>
                        <button @click="resetNewMember" v-if="memberToEdit === null" class="cancel-action">{{ $t('members.clearFields') }}</button>
                    </div>
                </div>
            </div>
        </BaseDialog>
        <div class="members-table-panel">
            <div class="members-table-wrap">
            <table>
                <thead>
                    <tr>
                        <th>{{ $t('members.imageInternet') }}</th>
                        <th>{{ $t('members.name') }}</th>
                        <th>{{ $t('members.status') }}</th>
                        <th>Beiträge</th>
                        <th>{{ $t('members.ttrQttr') }}</th>
                        <th>{{ $t('members.contact') }}</th>
                        <th>{{ $t('members.birthdate') }}</th>
                        <th>{{ $t('members.age') }}</th>
                        <th>{{ $t('members.ttAgeClassCol') }}</th>
                        <th>{{ $t('members.previewLastTraining') }}</th>
                        <th v-if="showTrainingParticipationsColumn">{{ $t('members.trainingParticipations') }}</th>
                        <th>{{ $t('members.actions') }}</th>
                    </tr>
                </thead>
                <tbody>
                    <template v-for="member in sortedFilteredMembers" :key="member.id">
                        <tr
                            class="member-row"
                            :class="{ 'row-selected': selectedMemberPreview && selectedMemberPreview.id === member.id, 'row-inactive': !member.active, 'row-test': member.testMembership && !member.memberFormHandedOver, 'row-test-form': member.testMembership && member.memberFormHandedOver }"
                            :title="memberRowHoverTooltip(member)"
                            @click="selectMember(member)"
                        >
                            <td>
                                <div @click.stop="openImageModal(member)">
                                    <img
                                        v-if="member.latestImageUrl"
                                        :src="member.latestImageUrl"
                                        :alt="$t('members.memberImage')"
                                        class="member-image-thumb-small"
                                    >
                                    <div v-else class="member-image-thumb__placeholder">+</div>
                                </div>
                                <span class="pics-allowed-indicator">{{ member.picsInInternetAllowed ? '✓' : '' }}</span>
                            </td>
                            <td>
                                <div class="member-name-cell">
                                    <div class="member-name-line">
                                        <span class="gender-symbol" :class="['gender-' + (member.gender || 'unknown'), { 'is-inactive': !member.active }]" :title="labelGender(member.gender)">{{ genderSymbol(member.gender) }}</span>
                                        <span class="gender-name" :class="['gender-' + (member.gender || 'unknown'), { 'is-inactive': !member.active }]">
                                            {{ member.lastName }}, {{ member.firstName }}
                                        </span>
                                    </div>
                                    <div class="member-name-meta">
                                        <span v-if="member.notInTraining" class="warning-icon warning-icon-severe" :title="$t('members.notInTrainingTooltip', { weeks: member.missedTrainingWeeks || 0 })">📭</span>
                                        <span v-if="member.testMembership && member.trainingParticipations >= 6" class="warning-icon warning-icon-severe" :title="$t('members.sixOrMoreParticipations')">🛑</span>
                                        <span v-else-if="member.testMembership && member.trainingParticipations >= 3" class="warning-icon" :title="$t('members.threeOrMoreParticipations')">⚠️</span>
                                        <span class="member-meta-text">{{ labelGender(member.gender) }}</span>
                                    </div>
                                    <div v-if="member.adultReleaseApproved || member.adultReserveApproved" class="member-name-flags">
                                        <span
                                            v-if="member.adultReleaseApproved"
                                            class="member-eligibility-flag"
                                            :title="$t('members.adultReleaseApproved')"
                                        >FE</span>
                                        <span
                                            v-if="member.adultReserveApproved"
                                            class="member-eligibility-flag"
                                            :title="$t('members.adultReserveApproved')"
                                        >EE</span>
                                    </div>
                                </div>
                            </td>
                            <td>
                                <div class="member-status-stack">
                                    <span
                                        v-for="badge in getMemberStatusBadges(member)"
                                        :key="badge.key"
                                        class="member-status-badge"
                                        :class="`member-status-badge-${badge.tone}`"
                                    >
                                        {{ badge.label }}
                                    </span>
                                </div>
                            </td>
                            <td>
                                <div class="member-contribution-cell">
                                    <div class="member-contribution-line">
                                        <strong>{{ getMemberContributionLabel(member) }}</strong>
                                    </div>
                                    <div class="member-contribution-line">
                                        {{ getMemberContributionOpenSummary(member) }}
                                    </div>
                                    <div class="member-contribution-line member-contribution-muted">
                                        {{ getMemberContributionHint(member) }}
                                    </div>
                                </div>
                            </td>
                            <td class="rating-cell">
                                <button
                                    type="button"
                                    class="rating-button"
                                    :disabled="!member.myTischtennisPlayerId && !member.ttr && !member.qttr"
                                    :title="member.myTischtennisHistoryPlayerId || member.myTischtennisPlayerId || member.ttr || member.qttr ? $t('members.showTtrHistory') : $t('members.missingTtrHistoryId')"
                                    @click.stop="openTtrHistoryDialog(member)"
                                >
                                    <span v-if="member.ttr || member.qttr">
                                        <span v-if="member.ttr" class="ttr-value">{{ member.ttr }}</span>
                                        <span v-if="member.ttr && member.qttr" class="rating-separator">/</span>
                                        <span v-if="member.qttr" class="qttr-value">{{ member.qttr }}</span>
                                    </span>
                                    <span v-else class="no-rating">-</span>
                                </button>
                            </td>
                            <td>
                                <div class="member-contact-cell">
                                    <div class="member-contact-line">{{ getFormattedPhoneNumbers(member) }}</div>
                                    <div class="member-contact-line">{{ getFormattedEmails(member) }}</div>
                                    <div class="member-contact-meta">{{ getCompactAddress(member) || $t('members.noAddressShort') }}</div>
                                </div>
                            </td>
                            <td>{{ getFormattedBirthdate(member.birthDate) }}</td>
                            <td>{{ getAgeLabel(member.birthDate) }}</td>
                            <td class="tt-age-class-cell">
                                <div
                                    v-for="(line, idx) in formatMemberTtAgeClassLines(member)"
                                    :key="idx"
                                    class="tt-age-class-line"
                                    :class="{ 'tt-age-class-line--next': idx === 1 }"
                                >
                                    {{ line }}
                                </div>
                            </td>
                            <td class="last-training-cell">
                                <span class="last-training-compact">{{ getOptionalFormattedDate(member.lastTraining, 'members.previewNoLastTraining') }}</span>
                            </td>
                            <td v-if="showTrainingParticipationsColumn" class="training-participations-cell">
                                {{ trainingParticipationsDisplay(member) }}
                            </td>
                            <td>
                                <div class="member-actions-cell">
                                    <div v-if="member.testMembership" class="member-actions-primary">
                                        <button
                                            v-if="member.testMembership"
                                            type="button"
                                            class="member-action-button member-action-button-success"
                                            @click.stop="quickRemoveTestMembership(member)"
                                        >
                                            {{ $t('members.markRegular') }}
                                        </button>
                                    </div>
                                    <div class="member-actions-secondary">
                                        <button
                                            v-if="!member.memberFormHandedOver"
                                            type="button"
                                            class="member-icon-button member-icon-button-success"
                                            :title="$t('members.markFormReceived')"
                                            @click.stop="quickMarkFormHandedOver(member)"
                                        >
                                            ✅
                                        </button>
                                        <button
                                            v-if="member.active && !member.ttr && !member.qttr && !member.clickTtApplicationSubmitted"
                                            type="button"
                                            class="member-icon-button member-icon-button-info"
                                            :disabled="clickTtPendingMemberIds.includes(member.id)"
                                            @click.stop="requestClickTtRegistration(member)"
                                            :title="clickTtPendingMemberIds.includes(member.id) ? $t('members.clickTtRequestPending') : $t('members.clickTtRequestAction')"
                                        >
                                            {{ clickTtPendingMemberIds.includes(member.id) ? '⏳' : '🏓' }}
                                        </button>
                                        <button type="button" class="member-icon-button member-icon-button-edit" :title="$t('members.editMember')" @click.stop="editMember(member)">🪶</button>
                                        <button type="button" class="member-icon-button" :title="$t('orders.title')" @click.stop="openOrdersDialog(member)">📦</button>
                                        <button type="button" class="member-icon-button" :title="$t('members.notes')" @click.stop="openNotesModal(member)">📝</button>
                                        <button type="button" class="member-icon-button" :title="$t('members.exercises')" @click.stop="openActivitiesModal(member)">🏃</button>
                                        <button
                                            v-if="member.active"
                                            type="button"
                                            class="member-icon-button member-icon-button-danger"
                                            :title="$t('members.deactivateMember')"
                                            @click.stop="quickDeactivateMember(member)"
                                        >
                                            ⛔
                                        </button>
                                    </div>
                                </div>
                            </td>
                        </tr>
</template>
                </tbody>
            </table>
            </div>
        </div>

        <!-- Image Viewer Dialog -->
        <ImageViewerDialog
            v-model="showImageModal"
            :title="$t('members.memberImages')"
            :images="selectedMemberImages"
            :active-image-id="selectedImageId"
            :member-id="selectedMemberId"
            :show-actions="true"
            :allow-rotate="true"
            :show-delete="true"
            :show-set-primary="true"
            :allow-upload="true"
            @update:activeImageId="selectedImageId = $event"
            @rotate="handleRotate"
            @delete-image="handleDeleteImage"
            @set-primary="handleSetPrimaryImage"
            @upload-images="handleUploadImages"
            @close="onGalleryClosed"
        />

        <!-- Notes Modal -->
        <MemberNotesDialog
            v-model="showNotesModal"
            :member="memberToEdit"
            :notes="notes"
            v-model:note-content="newNoteContent"
            :selected-tags="[]"
            :available-tags="[]"
            @add-note="addNote"
            @delete-note="deleteNote"
            @close="closeNotesModal"
        />
    
    <!-- Info Dialog -->
    <InfoDialog
        v-model="infoDialog.isOpen"
        :title="infoDialog.title"
        :message="infoDialog.message"
        :details="infoDialog.details"
        :details-html="infoDialog.detailsHtml"
        :type="infoDialog.type"
    />
    
    <!-- Confirm Dialog -->
    <ConfirmDialog
        v-model="confirmDialog.isOpen"
        :title="confirmDialog.title"
        :message="confirmDialog.message"
        :details="confirmDialog.details"
        :type="confirmDialog.type"
        @confirm="handleConfirmResult(true)"
        @cancel="handleConfirmResult(false)"
    />
        
        <!-- Member Activities Dialog -->
        <MemberActivitiesDialog
            v-model="showActivitiesModal"
            :member="selectedMemberForActivities"
            :club-id="currentClub"
        />

        <!-- Member Transfer Dialog -->
        <MemberTransferDialog
            v-model="showTransferDialog"
            @success="handleTransferSuccess"
            @error="handleTransferError"
        />

        <MemberTtrHistoryDialog
            v-if="showMemberTtrHistoryDialog && selectedMemberForTtrHistory"
            v-model="showMemberTtrHistoryDialog"
            :member="selectedMemberForTtrHistory"
            :club-id="currentClub"
            @close="closeTtrHistoryDialog"
        />

        <MemberOrdersDialog
            v-if="showMemberOrdersDialog && selectedMemberForOrders"
            v-model="showMemberOrdersDialog"
            :member="selectedMemberForOrders"
            :club-id="currentClub"
            @close="closeOrdersDialog"
        />

        <GroupPhotoCropDialog
            v-model="showGroupPhotoCropDialog"
            :club-id="currentClub"
            :members="members"
            @member-image-updated="handleGroupPhotoMemberImageUpdated"
        />
    </div>
</template>

<script>
import { mapGetters } from 'vuex';
import apiClient from '../apiClient.js';
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
import { safeLocalStorage } from '../utils/storage.js';
import {
    getSeasonStartYearFromDate,
    memberMatchesTtAgeClass,
    formatMemberTtAgeClassLabels,
    formatSeasonSlash
} from '../utils/ttAgeClass.js';
import PDFGenerator from '../components/PDFGenerator.js';

import InfoDialog from '../components/InfoDialog.vue';
import ConfirmDialog from '../components/ConfirmDialog.vue';
import ImageViewerDialog from '../components/ImageViewerDialog.vue';
import BaseDialog from '../components/BaseDialog.vue';
import MemberNotesDialog from '../components/MemberNotesDialog.vue';
import MemberActivitiesDialog from '../components/MemberActivitiesDialog.vue';
import MemberTransferDialog from '../components/MemberTransferDialog.vue';
import MemberTtrHistoryDialog from '../components/MemberTtrHistoryDialog.vue';
import MemberOrdersDialog from '../components/MemberOrdersDialog.vue';
import MembersOverviewSection from '../components/members/MembersOverviewSection.vue';
import GroupPhotoCropDialog from '../components/members/GroupPhotoCropDialog.vue';
import { debounce } from '../utils/debounce.js';
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage, sanitizeText } from '../utils/dialogUtils.js';

const defaultMemberDataQualityRequirements = () => ({
    requireStreet: true,
    requirePostalCode: true,
    requireCity: true,
    requirePhone: true,
    requireEmail: true,
});

export default {
    name: 'MembersView',
    components: {
        InfoDialog,
        ConfirmDialog,
        ImageViewerDialog,
        BaseDialog,
        MemberNotesDialog,
        MemberActivitiesDialog,
        MemberTransferDialog,
        MemberTtrHistoryDialog,
        MemberOrdersDialog,
        MembersOverviewSection,
        GroupPhotoCropDialog
    },
    computed: {
        ...mapGetters(['isAuthenticated', 'currentClub', 'token', 'hasPermission']),
        
        activeMembersCount() {
            return this.members.filter(member => member.active && !member.testMembership).length;
        },
        
        testMembersCount() {
            return this.members.filter(member => member.testMembership && member.active).length;
        },
        
        inactiveMembersCount() {
            return this.members.filter(member => !member.active).length;
        },

        memberContributionStats() {
            const assignedGroups = this.members.filter((member) => String(member.contributionGroupCode || member.contribution_group_code || '').trim()).length;
            const missingGroups = this.members.length - assignedGroups;
            const openClaims = this.paymentClaims.filter((claim) => ['open', 'partially_paid'].includes(claim.status)).length;
            const openAmountCents = this.paymentClaims
                .filter((claim) => ['open', 'partially_paid'].includes(claim.status))
                .reduce((sum, claim) => sum + Number(claim.remainingAmountCents || 0), 0);
            return {
                assignedGroups,
                missingGroups,
                openClaims,
                openAmountCents,
            };
        },

        canEditMemberBankAccount() {
            return this.hasPermission('members', 'write');
        },

        canManageMembers() {
            return this.hasPermission('members', 'write');
        },

        trainingGroupFilterOptions() {
            const groups = new Map();
            this.members.forEach((member) => {
                this.getMemberTrainingGroups(member).forEach((group) => {
                    if (!group || group.id == null) {
                        return;
                    }
                    groups.set(String(group.id), group.name || `#${group.id}`);
                });
            });

            return Array.from(groups.entries())
                .map(([id, name]) => ({ id, name }))
                .sort((a, b) => a.name.localeCompare(b.name, 'de-DE'));
        },

        ttSeasonFilterOptions() {
            const y0 = getSeasonStartYearFromDate();
            return [
                {
                    value: y0,
                    label: `${formatSeasonSlash(y0)} (${this.$t('members.ttSeasonCurrentTag')})`
                },
                {
                    value: y0 + 1,
                    label: `${formatSeasonSlash(y0 + 1)} (${this.$t('members.ttSeasonNextTag')})`
                }
            ];
        },
        
        filteredMembers() {
            return this.members.filter(member => {
                const search = this.searchQuery.trim().toLowerCase();
                const scopeIncludesInactive = this.selectedMemberScope === 'test' || this.selectedMemberScope === 'inactive' || this.selectedMemberScope === 'dataIncomplete';

                // Inaktive Mitglieder Filter
                if (!member.active && !this.showInactiveMembers && !scopeIncludesInactive) {
                    return false;
                }

                if (this.selectedMemberScope === 'active' && (!member.active || member.testMembership)) {
                    return false;
                }

                if (this.selectedMemberScope === 'test' && !member.testMembership) {
                    return false;
                }

                if (this.selectedMemberScope === 'activeTest' && (!member.active || !member.testMembership)) {
                    return false;
                }

                if (this.selectedMemberScope === 'inactive' && member.active) {
                    return false;
                }

                if (this.selectedMemberScope === 'notTraining' && !member.notInTraining) {
                    return false;
                }

                if (this.selectedMemberScope === 'needsForm' && member.memberFormHandedOver) {
                    return false;
                }

                if (this.selectedMemberScope === 'dataIncomplete' && this.getMemberDataQualityIssues(member).length === 0) {
                    return false;
                }

                if (this.selectedMemberScope === 'activeDataIncomplete' && (!member.active || member.testMembership || this.getMemberDataQualityIssues(member).length === 0)) {
                    return false;
                }
                
                // Altersklasse (TT, Stichtag 1.1.)
                if (this.selectedAgeGroup && this.selectedAgeGroup !== 'range') {
                    if (!memberMatchesTtAgeClass(member, this.selectedAgeGroup, this.selectedSeasonStartYear)) {
                        return false;
                    }
                }

                const age = this.getAge(member.birthDate);
                const minAge = this.selectedAgeFrom !== '' ? Number.parseInt(this.selectedAgeFrom, 10) : null;
                const maxAge = this.selectedAgeTo !== '' ? Number.parseInt(this.selectedAgeTo, 10) : null;

                if (this.selectedAgeGroup === 'range' && (minAge !== null || maxAge !== null)) {
                    if (age === null) {
                        return false;
                    }
                    if (Number.isFinite(minAge) && age < minAge) {
                        return false;
                    }
                    if (Number.isFinite(maxAge) && age > maxAge) {
                        return false;
                    }
                }
                
                // Geschlecht Filter
                if (this.selectedGender) {
                    if (member.gender !== this.selectedGender) {
                        return false;
                    }
                }

                if (this.selectedTrainingGroup) {
                    const memberGroupIds = this.getMemberTrainingGroups(member).map(group => String(group.id));
                    if (!memberGroupIds.includes(String(this.selectedTrainingGroup))) {
                        return false;
                    }
                }

                if (this.selectedLastTrainingFilter === 'hasDate' && !this.hasValidLastTrainingDate(member)) {
                    return false;
                }
                if (this.selectedLastTrainingFilter === 'noDate' && this.hasValidLastTrainingDate(member)) {
                    return false;
                }
                if (this.selectedLastTrainingFilter === 'notInTraining' && !member.notInTraining) {
                    return false;
                }

                if (search) {
                    const haystack = [
                        member.firstName,
                        member.lastName,
                        member.street,
                        member.city,
                        member.postalCode,
                        member.phone,
                        member.email,
                        this.getFormattedPhoneNumbers(member),
                        this.getFormattedEmails(member)
                    ]
                        .filter(Boolean)
                        .join(' ')
                        .toLowerCase();

                    if (!haystack.includes(search)) {
                        return false;
                    }
                }
                
                return true;
            });
        },

        memberScopeOptions() {
            return [
                { value: 'all', label: this.$t('members.scopeAll'), count: this.members.length },
                { value: 'active', label: this.$t('members.scopeActive'), count: this.members.filter(member => member.active && !member.testMembership).length },
                { value: 'test', label: this.$t('members.scopeTest'), count: this.members.filter(member => member.testMembership).length },
                { value: 'activeTest', label: this.$t('members.scopeActiveTest'), count: this.members.filter(member => member.active && member.testMembership).length },
                { value: 'notTraining', label: this.$t('members.scopeNotTraining'), count: this.members.filter(member => member.notInTraining).length },
                { value: 'needsForm', label: this.$t('members.scopeNeedsForm'), count: this.members.filter(member => !member.memberFormHandedOver).length },
                { value: 'activeDataIncomplete', label: this.$t('members.scopeActiveDataIncomplete'), count: this.members.filter(member => member.active && !member.testMembership && this.getMemberDataQualityIssues(member).length > 0).length },
                { value: 'dataIncomplete', label: this.$t('members.scopeDataIncomplete'), count: this.members.filter(member => this.getMemberDataQualityIssues(member).length > 0).length },
                { value: 'inactive', label: this.$t('members.scopeInactive'), count: this.members.filter(member => !member.active).length }
            ];
        },

        filteredTestMembers() {
            return this.filteredMembers.filter(member => member.testMembership);
        },

        filteredMembersWithoutForm() {
            return this.filteredMembers.filter(member => !member.memberFormHandedOver);
        },

        filteredMembersWithPhonesCount() {
            return this.filteredMembers.filter(member => this.getFormattedPhoneNumbers(member) !== '–').length;
        },

        filteredMembersWithEmailsCount() {
            return this.filteredMembers.filter(member => this.getFormattedEmails(member) !== '–').length;
        },

        filteredEmailAddresses() {
            const emails = [];
            for (const member of this.sortedFilteredMembers) {
                const formatted = this.getFormattedEmails(member);
                if (formatted === '–') continue;
                formatted.split(',').map(part => part.trim()).forEach(entry => {
                    const email = entry.replace(/\s*\(.*?\)\s*$/, '').trim();
                    if (email && !emails.includes(email)) {
                        emails.push(email);
                    }
                });
            }
            return emails;
        },

        hasEditorPhone() {
            return this.memberContacts.phones.some(contact => contact?.value && String(contact.value).trim() !== '');
        },

        hasEditorEmail() {
            return this.memberContacts.emails.some(contact => contact?.value && String(contact.value).trim() !== '');
        },

        hasEditorAddress() {
            return [this.newStreet, this.newPostalCode, this.newCity].some(value => value && String(value).trim() !== '');
        },

        editorDataQualityIssues() {
            const issues = [];
            const requirements = this.memberDataQualityRequirements;

            if (!this.newBirthdate) {
                issues.push({ key: 'birthdate', label: this.$t('members.dataIssueBirthdate') });
            }
            if (requirements.requirePhone && !this.hasEditorPhone) {
                issues.push({ key: 'phone', label: this.$t('members.dataIssuePhone') });
            }
            if (requirements.requireEmail && !this.hasEditorEmail) {
                issues.push({ key: 'email', label: this.$t('members.dataIssueEmail') });
            }
            if (requirements.requireStreet && !this.hasTextValue(this.newStreet)) {
                issues.push({ key: 'street', label: this.$t('members.dataIssueStreet') });
            }
            if (requirements.requirePostalCode && !this.hasTextValue(this.newPostalCode)) {
                issues.push({ key: 'postal-code', label: this.$t('members.dataIssuePostalCode') });
            }
            if (requirements.requireCity && !this.hasTextValue(this.newCity)) {
                issues.push({ key: 'city', label: this.$t('members.dataIssueCity') });
            }
            if (!this.newGender || this.newGender === 'unknown') {
                issues.push({ key: 'gender', label: this.$t('members.dataIssueGender') });
            }
            if (this.memberToEdit && this.memberTrainingGroups.length === 0) {
                issues.push({ key: 'training-group', label: this.$t('members.dataIssueTrainingGroup') });
            }

            return issues;
        },

        exportPreviewNames() {
            return this.sortedFilteredMembers
                .slice(0, 5)
                .map(member => `${member.firstName} ${member.lastName}`.trim());
        },

        sortedFilteredMembers() {
            const members = [...this.filteredMembers];
            const getBirthMonthDayValue = (member) => {
                if (!member?.birthDate) return Number.MAX_SAFE_INTEGER;
                const date = new Date(member.birthDate);
                if (Number.isNaN(date.getTime())) return Number.MAX_SAFE_INTEGER;
                return ((date.getMonth() + 1) * 100) + date.getDate();
            };
            const getAgeValue = (member) => {
                const age = this.getAge(member?.birthDate);
                return age === null ? Number.MAX_SAFE_INTEGER : age;
            };
            const getLastTrainingValue = (member) => {
                if (!member?.lastTraining) return Number.MAX_SAFE_INTEGER;
                const date = new Date(member.lastTraining);
                return Number.isNaN(date.getTime()) ? Number.MAX_SAFE_INTEGER : date.getTime();
            };
            const getQttrValue = (member) => {
                const parsed = Number.parseFloat(member?.qttr);
                return Number.isFinite(parsed) ? parsed : null;
            };
            const compareStrings = (a, b) => String(a || '').localeCompare(String(b || ''), 'de-DE');
            const direction = this.sortDirection === 'desc' ? -1 : 1;

            members.sort((a, b) => {
                let result = 0;
                switch (this.selectedSort) {
                    case 'firstName':
                        result = compareStrings(a.firstName, b.firstName) || compareStrings(a.lastName, b.lastName);
                        break;
                    case 'birthday':
                        result = getBirthMonthDayValue(a) - getBirthMonthDayValue(b) || compareStrings(a.lastName, b.lastName);
                        break;
                    case 'age':
                        result = getAgeValue(a) - getAgeValue(b) || compareStrings(a.lastName, b.lastName);
                        break;
                    case 'qttr': {
                        const qttrA = getQttrValue(a);
                        const qttrB = getQttrValue(b);
                        const hasQttrA = qttrA !== null;
                        const hasQttrB = qttrB !== null;

                        // Fehlende QTTR-Werte immer nach unten sortieren, unabhängig von der Sortierrichtung.
                        if (!hasQttrA && hasQttrB) {
                            result = 1;
                        } else if (hasQttrA && !hasQttrB) {
                            result = -1;
                        } else if (!hasQttrA && !hasQttrB) {
                            result = compareStrings(a.lastName, b.lastName) || compareStrings(a.firstName, b.firstName);
                        } else {
                            result = (qttrA - qttrB) * direction || compareStrings(a.lastName, b.lastName) || compareStrings(a.firstName, b.firstName);
                        }
                        break;
                    }
                    case 'lastTraining':
                        result = getLastTrainingValue(a) - getLastTrainingValue(b) || compareStrings(a.lastName, b.lastName);
                        break;
                    case 'openTasks':
                        result = this.getMemberOpenTasks(b).length - this.getMemberOpenTasks(a).length || compareStrings(a.lastName, b.lastName);
                        break;
                    case 'lastName':
                    default:
                        result = compareStrings(a.lastName, b.lastName) || compareStrings(a.firstName, b.firstName);
                        break;
                }
                // QTTR berücksichtigt die Sortierrichtung bereits im Zweig selbst.
                if (this.selectedSort === 'qttr') {
                    return result;
                }
                return result * direction;
            });

            return members;
        },
        
        
        availableGroupsForMember() {
            if (!Array.isArray(this.trainingGroups) || this.trainingGroups.length === 0) {
                return [];
            }
            const memberGroupIds = new Set(this.memberTrainingGroups.map(g => g && g.id).filter(id => id != null));
            return this.trainingGroups.filter(g => g && g.id && !memberGroupIds.has(g.id));
        }
    },
    watch: {
        '$route.query': {
            immediate: true,
            handler() {
                this.applyRouteQuery();
            }
        }
    },
    data() {
        return {
            // Dialog States
            infoDialog: {
                isOpen: false,
                title: '',
                message: '',
                details: '',
                detailsHtml: '',
                type: 'info'
            },
            confirmDialog: {
                isOpen: false,
                title: '',
                message: '',
                details: '',
                type: 'info',
                resolveCallback: null
            },
            members: [],
            memberFormIsOpen: false,
            newFirstname: '',
            newLastname: '',
            newStreet: '',
            newPostalCode: '',
            newCity: '',
            newBirthdate: '',
            newContributionGroupCode: '',
            newPhone: '',
            newEmail: '',
            memberContacts: {
                phones: [],
                emails: []
            },
            newGender: 'unknown',
            newActive: true,
            memberToEdit: null,
            memberImage: null, 
            memberImagePreview: null,
            notes: [],
            newNoteContent: '',
            showNotesModal: false,
            showImageModal: false,
            selectedMemberId: null,
            selectedMemberImages: [],
            selectedImageId: null,
            selectedMemberForImages: null,
            testMembership: false,
            showInactiveMembers: false,
            showTrainingParticipationsColumn: false,
            newPicsInInternetAllowed: false,
            newMemberFormHandedOver: false,
            newAdultReleaseApproved: false,
            newAdultReserveApproved: false,
            isUpdatingRatings: false,
            showMemberInfo: false,
            showActivitiesModal: false,
            showMemberTtrHistoryDialog: false,
            showMemberOrdersDialog: false,
            showGroupPhotoCropDialog: false,
            selectedMemberForActivities: null,
            selectedMemberForTtrHistory: null,
            selectedMemberForOrders: null,
            memberTrainingGroups: [],
            trainingGroups: [],
            selectedGroupToAdd: '',
            showTransferDialog: false,
            selectedAgeGroup: '',
            selectedSeasonStartYear: getSeasonStartYearFromDate(),
            selectedAgeFrom: '',
            selectedAgeTo: '',
            selectedGender: '',
            selectedTrainingGroup: '',
            selectedLastTrainingFilter: '',
            clickTtPendingMemberIds: [],
            searchQuery: '',
            selectedMemberScope: 'active',
            selectedSort: 'lastName',
            sortDirection: 'asc',
            isLoadingMembers: false,
            membersLoadError: '',
            selectedMemberPreview: null,
            selectedPreviewTrainingGroups: [],
            paymentClaims: [],
            memberDataQualityRequirements: defaultMemberDataQualityRequirements(),
            memberSepaMandateLoading: false,
            memberSepaMandateError: '',
            memberSepaMandateForm: {
                debtorName: '',
                iban: '',
                bic: '',
                mandateReference: '',
                signedOn: '',
                validFrom: '',
                status: 'active',
                historyNote: ''
            }
        }
    },
    created() {
        const v = safeLocalStorage.getItem('membersShowTrainingParticipationsColumn');
        if (v === '1') {
            this.showTrainingParticipationsColumn = true;
        }
    },
    async mounted() {
        await this.loadTrainingGroups();
        await this.loadMemberDataQualityRequirements();
        await this.init();
    },
    methods: {
        // Dialog Helper Methods
        async showInfo(title, message, details = '', type = 'info') {
            this.infoDialog = buildInfoConfig({ title, message, details, type });
        },

        async showInfoHtml(title, message, detailsHtml = '', type = 'info') {
            this.infoDialog = {
                ...buildInfoConfig({ title, message, details: '', type }),
                detailsHtml
            };
        },
        
        async showConfirm(title, message, details = '', type = 'info', options = {}) {
            return new Promise((resolve) => {
                this.confirmDialog = buildConfirmConfig({
                    title,
                    message,
                    details,
                    type,
                    resolveCallback: resolve,
                    ...options,
                });
            });
        },
        
        handleConfirmResult(confirmed) {
            if (this.confirmDialog.resolveCallback) {
                this.confirmDialog.resolveCallback(confirmed);
                this.confirmDialog.resolveCallback = null;
            }
            this.confirmDialog.isOpen = false;
        },
        
        async init() {
            await this.loadMembers();
        },
        applyRouteQuery() {
            const routeScope = typeof this.$route?.query?.scope === 'string' ? this.$route.query.scope : '';
            const routeMemberId = this.$route?.query?.memberId;
            const routeMode = typeof this.$route?.query?.mode === 'string' ? this.$route.query.mode : '';
            const validScopes = new Set(['all', 'active', 'test', 'activeTest', 'notTraining', 'needsForm', 'activeDataIncomplete', 'dataIncomplete', 'inactive']);

            if (validScopes.has(routeScope)) {
                this.selectedMemberScope = routeScope;
            }

            if (routeMemberId) {
                const member = this.members.find(entry => String(entry.id) === String(routeMemberId));
                if (member) {
                    if (routeMode === 'edit') {
                        this.editMember(member);
                    } else {
                        this.selectMember(member);
                    }
                }
            }
        },
        async loadMemberDataQualityRequirements() {
            if (!this.currentClub) {
                this.memberDataQualityRequirements = defaultMemberDataQualityRequirements();
                return;
            }
            try {
                const response = await apiClient.get(`/clubs/${this.currentClub}`);
                this.memberDataQualityRequirements = this.normalizeMemberDataQualityRequirements(response.data?.memberDataQualityRequirements);
            } catch (error) {
                console.error('[loadMemberDataQualityRequirements] error:', error);
                this.memberDataQualityRequirements = defaultMemberDataQualityRequirements();
            }
        },
        normalizeMemberDataQualityRequirements(settings) {
            const defaults = defaultMemberDataQualityRequirements();
            const parsed = typeof settings === 'string' ? this.parseJsonSetting(settings) : settings;
            if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
                return defaults;
            }
            return Object.fromEntries(
                Object.keys(defaults).map(key => [
                    key,
                    typeof parsed[key] === 'boolean' ? parsed[key] : defaults[key],
                ])
            );
        },
        parseJsonSetting(value) {
            try {
                return JSON.parse(value);
            } catch (error) {
                return null;
            }
        },
        hasTextValue(value) {
            return value !== null && value !== undefined && String(value).trim() !== '';
        },
        async loadMembers() {
            this.isLoadingMembers = true;
            this.membersLoadError = '';
            try {
                const response = await apiClient.get(`/clubmembers/get/${this.currentClub}/true`);
                const payload = response.data;
                const ok =
                    response.status >= 200 &&
                    response.status < 300 &&
                    Array.isArray(payload);
                if (!ok) {
                    const raw =
                        payload &&
                        typeof payload === 'object' &&
                        !Array.isArray(payload) &&
                        (payload.error ?? payload.message);
                    this.membersLoadError = getSafeMessage(
                        typeof raw === 'string' ? raw : null,
                        this.$t('members.errorLoadingMembers')
                    );
                    this.members = [];
                    return;
                }
                this.members = payload
                    .map(member => this.applyBackendBaseUrlToMember(member))
                    .sort((a, b) => {
                    const lastNameA = a.lastName ? a.lastName.toLowerCase() : '';
                    const lastNameB = b.lastName ? b.lastName.toLowerCase() : '';
                    if (lastNameA && !lastNameB) return -1;
                    if (!lastNameA && lastNameB) return 1; 
                    if (lastNameA && lastNameB) {
                        const lastNameCompare = lastNameA.localeCompare(lastNameB, 'de-DE');
                        if (lastNameCompare !== 0) return lastNameCompare;
                    }
                    const firstNameA = a.firstName ? a.firstName.toLowerCase() : '';
                    const firstNameB = b.firstName ? b.firstName.toLowerCase() : '';
                    return firstNameA.localeCompare(firstNameB, 'de-DE');
                });
                
                await this.loadTrainingParticipations();
                await this.loadPaymentClaims();
                await Promise.allSettled(this.members.map(member => this.prefetchMemberPrimaryImage(member)));
                await Promise.allSettled(this.members.map(member => this.prefetchMemberLatestImage(member)));
                this.applyRouteQuery();
                if (this.selectedMemberPreview) {
                    this.selectedMemberPreview = this.members.find(member => member.id === this.selectedMemberPreview.id) || null;
                    if (!this.selectedMemberPreview) {
                        this.selectedPreviewTrainingGroups = [];
                    }
                }
            } catch (error) {
                console.error(this.$t('members.errorLoadingMembers'), error);
                this.membersLoadError = getSafeErrorMessage(error, this.$t('members.errorLoadingMembers'));
                this.members = [];
            } finally {
                this.isLoadingMembers = false;
            }
        },
        async loadPaymentClaims() {
            if (!this.currentClub) {
                this.paymentClaims = [];
                return;
            }

            try {
                const response = await apiClient.get(`/club-payment-claims/${this.currentClub}`);
                const claims = Array.isArray(response.data?.claims) ? response.data.claims : [];
                this.paymentClaims = claims.map((claim) => this.normalizePaymentClaim(claim));
            } catch (error) {
                console.error('[loadPaymentClaims] error:', error);
                this.paymentClaims = [];
            }
        },
        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',
            };
        },
        
        async loadTrainingParticipations() {
            try {
                const response = await apiClient.get(`/training-stats/${this.currentClub}`);
                if (response.status < 200 || response.status >= 300 || !response.data || typeof response.data !== 'object') {
                    throw new Error('training-stats invalid response');
                }
                const trainingStats = Array.isArray(response.data.members) ? response.data.members : [];
                
                // Erstelle eine Map für schnellen Zugriff: memberId -> participationTotal
                // Speichere sowohl String- als auch Number-Keys, um Typ-Probleme zu vermeiden
                const participationMap = new Map();
                    trainingStats.forEach(stat => {
                    const participationTotal = stat.participationTotal || 0;
                    // Speichere sowohl als String als auch als Number
                    const idAsString = String(stat.id);
                    const idAsNumber = typeof stat.id === 'string' ? parseInt(stat.id, 10) : stat.id;
                    
                    participationMap.set(idAsString, participationTotal);
                    participationMap.set(idAsNumber, participationTotal);
                });
                
                this.members.forEach(member => {
                    // Versuche sowohl String- als auch Number-ID
                    const idAsString = String(member.id);
                    const idAsNumber = typeof member.id === 'string' ? parseInt(member.id, 10) : member.id;
                    
                    // Versuche zuerst mit der ursprünglichen ID (String oder Number)
                    let count = participationMap.get(member.id);
                    
                    // Falls nicht gefunden, versuche mit konvertierter ID
                    if (count === undefined) {
                        count = participationMap.get(idAsString);
                    }
                    if (count === undefined) {
                        count = participationMap.get(idAsNumber);
                    }
                    
                    // Setze den Wert, wenn gefunden, sonst 0
                    const finalCount = count !== undefined ? count : 0;
                    const matchingStat = trainingStats.find(stat => String(stat.id) === String(member.id));
                    member.trainingParticipations = finalCount;
                    member.missedTrainingWeeks = matchingStat?.missedTrainingWeeks || 0;
                    member.notInTraining = !!matchingStat?.notInTraining;
                    member.lastTraining = matchingStat?.lastTraining || null;
                    member.trainingGroups = Array.isArray(matchingStat?.trainingGroups) ? matchingStat.trainingGroups : [];
                });
            } catch (error) {
                console.error(this.$t('members.errorLoadingTrainingParticipations'), error);
                this.members.forEach(member => {
                    member.trainingParticipations = 0;
                    member.missedTrainingWeeks = 0;
                    member.notInTraining = false;
                    member.lastTraining = null;
                    member.trainingGroups = [];
                });
            }
        },
        async selectMember(member) {
            this.selectedMemberPreview = member;
            try {
                const response = await apiClient.get(`/training-groups/${this.currentClub}/member/${member.id}`);
                this.selectedPreviewTrainingGroups = Array.isArray(response.data) ? response.data : [];
            } catch (error) {
                console.error('[selectMember] preview training groups error:', error);
                this.selectedPreviewTrainingGroups = [];
            }
        },
        closeMemberPreviewDialog() {
            this.selectedMemberPreview = null;
            this.selectedPreviewTrainingGroups = [];
        },
        async quickRemoveTestMembershipInternal(member, { silent = false } = {}) {
            const response = await apiClient.post(`/clubmembers/quick-update-test-membership/${this.currentClub}/${member.id}`);
            if (!response.data.success) {
                throw new Error(sanitizeText(response.data.error, this.$t('members.errorRemovingTestMembership'), 300));
            }
            member.testMembership = false;
            member.trainingParticipations = undefined;
            if (!silent) {
                await this.showInfo(this.$t('messages.success'), sanitizeText(response.data.message, this.$t('members.testMembershipRemoved'), 300), '', 'success');
            }
        },
        
        async quickRemoveTestMembership(member) {
            try {
                await this.quickRemoveTestMembershipInternal(member);
            } catch (error) {
                console.error(this.$t('members.errorRemovingTestMembership'), error);
                const errorMessage = getSafeErrorMessage(error, this.$t('members.errorRemovingTestMembership'));
                this.showInfo(this.$t('messages.error'), errorMessage, '', 'error');
            }
        },

        async quickMarkFormHandedOverInternal(member, { silent = false } = {}) {
            const response = await apiClient.post(`/clubmembers/quick-update-member-form/${this.currentClub}/${member.id}`);
            if (!response.data.success) {
                throw new Error(sanitizeText(response.data.error, this.$t('members.errorMarkingForm'), 300));
            }
            member.memberFormHandedOver = true;
            if (!silent) {
                await this.showInfo(this.$t('messages.success'), sanitizeText(response.data.message, this.$t('members.formMarkedAsHandedOver'), 300), '', 'success');
            }
        },
        
        async quickMarkFormHandedOver(member) {
            try {
                await this.quickMarkFormHandedOverInternal(member);
            } catch (error) {
                console.error(this.$t('members.errorMarkingForm'), error);
                const errorMessage = getSafeErrorMessage(error, this.$t('members.errorMarkingForm'));
                this.showInfo(this.$t('messages.error'), errorMessage, '', 'error');
            }
        },
        async runBatchMemberUpdate(members, worker, successKey, emptyKey) {
            if (members.length === 0) {
                await this.showInfo(this.$t('messages.note'), this.$t(emptyKey), '', 'info');
                return;
            }

            const results = await Promise.allSettled(members.map(member => worker(member)));
            const successCount = results.filter(result => result.status === 'fulfilled').length;
            const failed = results.filter(result => result.status === 'rejected');

            if (failed.length === 0) {
                await this.showInfo(this.$t('messages.success'), this.$t(successKey, { count: successCount }), '', 'success');
                return;
            }

            await this.showInfo(
                this.$t('messages.warning'),
                this.$t('members.batchPartialFailure', { success: successCount, failed: failed.length }),
                failed.map(entry => entry.reason?.message || this.$t('messages.error')).join('\n'),
                'warning'
            );
        },
        async createPhoneListForFiltered() {
            const members = this.filteredMembers.filter(member => member.active && !member.testMembership);
            const pdfGenerator = new PDFGenerator(20, 10, this.$t);
            pdfGenerator.addPhoneList(members);
            pdfGenerator.save(this.$t('members.phoneListSelectionFile'));
        },
        buildFilteredMemberCsvRows() {
            const header = [
                this.$t('members.firstName'),
                this.$t('members.lastName'),
                this.$t('members.status'),
                this.$t('members.birthdate'),
                this.$t('members.age'),
                this.$t('members.ttAgeClassCol'),
                this.$t('members.contact'),
                this.$t('members.emailAddress')
            ];
            const rows = this.sortedFilteredMembers.map(member => [
                member.firstName || '',
                member.lastName || '',
                this.getMemberStatusBadges(member).map(badge => badge.label).join(' | '),
                this.getFormattedBirthdate(member.birthDate),
                this.getAgeLabel(member.birthDate),
                this.formatMemberTtAgeClassCsv(member),
                this.getFormattedPhoneNumbers(member),
                this.getFormattedEmails(member)
            ]);
            return [header, ...rows];
        },
        downloadCsv(filename, rows) {
            const csv = rows
                .map(row => row.map(value => `"${String(value ?? '').replace(/"/g, '""')}"`).join(';'))
                .join('\n');
            const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = url;
            link.download = filename;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
        },
        async exportFilteredMembersCsv() {
            if (this.filteredMembers.length === 0) {
                await this.showInfo(this.$t('messages.note'), this.$t('members.exportPreviewEmpty'), '', 'info');
                return;
            }
            this.downloadCsv(this.$t('members.exportCsvFile'), this.buildFilteredMemberCsvRows());
        },
        async copyTextToClipboard(text, successMessageKey, emptyMessageKey) {
            if (!text.trim()) {
                await this.showInfo(this.$t('messages.note'), this.$t(emptyMessageKey), '', 'info');
                return;
            }
            await navigator.clipboard.writeText(text);
            await this.showInfo(this.$t('messages.success'), this.$t(successMessageKey), '', 'success');
        },
        async copyFilteredPhones() {
            const text = this.sortedFilteredMembers
                .map(member => {
                    const phones = this.getFormattedPhoneNumbers(member);
                    return phones !== '–' ? `${member.firstName} ${member.lastName}: ${phones}` : '';
                })
                .filter(Boolean)
                .join('\n');
            await this.copyTextToClipboard(text, 'members.copyPhonesSuccess', 'members.copyPhonesEmpty');
        },
        async copyFilteredEmails() {
            const text = this.sortedFilteredMembers
                .map(member => {
                    const emails = this.getFormattedEmails(member);
                    return emails !== '–' ? `${member.firstName} ${member.lastName}: ${emails}` : '';
                })
                .filter(Boolean)
                .join('\n');
            await this.copyTextToClipboard(text, 'members.copyEmailsSuccess', 'members.copyEmailsEmpty');
        },
        async composeEmailToFiltered() {
            if (this.filteredEmailAddresses.length === 0) {
                await this.showInfo(this.$t('messages.note'), this.$t('members.copyEmailsEmpty'), '', 'info');
                return;
            }
            const bcc = encodeURIComponent(this.filteredEmailAddresses.join(','));
            window.location.href = `mailto:?bcc=${bcc}`;
        },
        async copyFilteredContactSummary() {
            const text = this.sortedFilteredMembers
                .map(member => {
                    const name = `${member.firstName} ${member.lastName}`.trim();
                    const phones = this.getFormattedPhoneNumbers(member);
                    const emails = this.getFormattedEmails(member);
                    const parts = [name];
                    if (phones !== '–') parts.push(`${this.$t('members.exportPhones')}: ${phones}`);
                    if (emails !== '–') parts.push(`${this.$t('members.exportEmails')}: ${emails}`);
                    return parts.join(' | ');
                })
                .filter(Boolean)
                .join('\n');
            await this.copyTextToClipboard(text, 'members.copyContactSummarySuccess', 'members.exportPreviewEmpty');
        },
        async markFilteredFormsHandedOver() {
            await this.runBatchMemberUpdate(
                this.filteredMembersWithoutForm,
                (member) => this.quickMarkFormHandedOverInternal(member, { silent: true }),
                'members.batchFormsMarkedSuccess',
                'members.batchFormsMarkedEmpty'
            );
        },
        async markFilteredAsRegular() {
            await this.runBatchMemberUpdate(
                this.filteredTestMembers,
                (member) => this.quickRemoveTestMembershipInternal(member, { silent: true }),
                'members.batchMarkedRegularSuccess',
                'members.batchMarkedRegularEmpty'
            );
        },
        
        async quickDeactivateMember(member) {
            const confirmed = await this.showConfirm(
                this.$t('members.deactivateMemberTitle'),
                this.$t('members.deactivateMemberConfirm', { name: `${member.firstName} ${member.lastName}` }),
                '',
                'warning'
            );
            if (!confirmed) {
                return;
            }
            
            try {
                const response = await apiClient.post(`/clubmembers/quick-deactivate/${this.currentClub}/${member.id}`);
                if (response.data.success) {
                    member.active = false;
                    this.showInfo(this.$t('messages.success'), getSafeMessage(response.data.message, this.$t('members.memberDeactivated')), '', 'success');
                } else {
                    this.showInfo(this.$t('messages.error'), getSafeMessage(response.data.error, this.$t('members.errorDeactivatingMember')), '', 'error');
                }
            } catch (error) {
                console.error(this.$t('members.errorDeactivatingMember'), error);
                const errorMessage = getSafeErrorMessage(error, this.$t('members.errorDeactivatingMember'));
                this.showInfo(this.$t('messages.error'), errorMessage, '', 'error');
            }
        },
        async requestClickTtRegistration(member) {
            if (this.clickTtPendingMemberIds.includes(member.id)) {
                return;
            }

            const confirmed = await this.showConfirm(
                this.$t('members.clickTtRequestTitle'),
                this.$t('members.clickTtRequestConfirm', { name: `${member.firstName} ${member.lastName}` }),
                this.$t('members.clickTtRequestHint'),
                'info'
            );
            if (!confirmed) {
                return;
            }

            this.clickTtPendingMemberIds = [...this.clickTtPendingMemberIds, member.id];
            try {
                const response = await apiClient.post(`/clubmembers/clicktt-registration/${this.currentClub}/${member.id}`);
                if (response.data?.success) {
                    member.clickTtApplicationSubmitted = true;
                    await this.showInfoHtml(
                        this.$t('members.clickTtRequestTitle'),
                        this.$t('members.clickTtRequestSuccess'),
                        '<a href="https://httv.click-tt.de" target="_blank" rel="noopener noreferrer">click-tt</a>',
                        'success'
                    );
                } else {
                    await this.showInfo(
                        this.$t('members.clickTtRequestTitle'),
                        getSafeMessage(response.data?.error, this.$t('members.clickTtRequestError')),
                        '',
                        'error'
                    );
                }
            } catch (error) {
                console.error(this.$t('members.clickTtRequestFailedLog'), error);
                const errorMessage = getSafeErrorMessage(error, this.$t('members.clickTtRequestError'));
                await this.showInfo(
                    this.$t('members.clickTtRequestTitle'),
                    errorMessage,
                    '',
                    'error'
                );
            } finally {
                this.clickTtPendingMemberIds = this.clickTtPendingMemberIds.filter(id => id !== member.id);
            }
        },
        openPaymentsForMember(member) {
            if (!member?.id) {
                return;
            }
            this.$router.push({
                path: '/club-payments',
                query: { memberId: String(member.id) },
            });
            this.closeMemberPreviewDialog();
        },
        getMemberContributionClaims(member) {
            const memberId = Number(member?.id);
            if (!memberId) return [];
            return this.paymentClaims.filter((claim) => Number(claim.memberId || claim.member?.id || 0) === memberId);
        },
        getMemberContributionOpenClaims(member) {
            return this.getMemberContributionClaims(member).filter((claim) => ['open', 'partially_paid'].includes(claim.status));
        },
        getMemberContributionLabel(member) {
            return String(member?.contributionGroupCode || member?.contribution_group_code || '').trim() || 'Nicht zugeordnet';
        },
        getMemberContributionOpenSummary(member) {
            const openClaims = this.getMemberContributionOpenClaims(member);
            const openAmountCents = openClaims.reduce((sum, claim) => sum + Number(claim.remainingAmountCents || 0), 0);
            if (openClaims.length === 0) {
                return 'Keine offenen Forderungen';
            }
            return `${openClaims.length} offen · ${this.formatCurrency(openAmountCents)}`;
        },
        getMemberContributionHint(member) {
            const contributionGroup = String(member?.contributionGroupCode || member?.contribution_group_code || '').trim();
            const mandateReference = String(member?.sepaMandateReference || member?.sepa_mandate_reference || '').trim();
            if (!contributionGroup) {
                return 'Beitragsgruppe fehlt';
            }
            if (!mandateReference) {
                return 'SEPA-Hinweis fehlt';
            }
            return 'Beitragszuordnung vollständig';
        },
        formatCurrency(amountCents, currencyCode = 'EUR') {
            return new Intl.NumberFormat('de-DE', {
                style: 'currency',
                currency: currencyCode || 'EUR',
            }).format(Number(amountCents || 0) / 100);
        },
        isClickTtRegistrationPending(member) {
            return !!member?.id && this.clickTtPendingMemberIds.includes(member.id);
        },
        canShowClickTtRegistration(member) {
            if (!this.canManageMembers || !member?.id) {
                return false;
            }
            if (!member.active) {
                return false;
            }
            if (member.clickTtApplicationSubmitted) {
                return false;
            }
            if (member.ttr || member.qttr) {
                return false;
            }
            return true;
        },
        clickTtRegistrationAvailabilityText(member) {
            if (!member?.id) {
                return 'Die HTTV-Anmeldung ist erst für gespeicherte Mitglieder verfügbar.';
            }
            if (!member.active) {
                return 'Die HTTV-Anmeldung ist nur für aktive Mitglieder verfügbar.';
            }
            if (member.clickTtApplicationSubmitted) {
                return 'Für dieses Mitglied wurde bereits ein Antrag an click-TT übermittelt.';
            }
            if (member.ttr || member.qttr) {
                return 'Für dieses Mitglied sind bereits TTR/QTTR-Daten vorhanden.';
            }
            return 'Die HTTV-Anmeldung kann hier direkt ausgelöst werden.';
        },
        toggleNewMember() {
            this.memberFormIsOpen = !this.memberFormIsOpen;
            if (!this.memberFormIsOpen && this.memberToEdit) {
                this.resetToNewMember();
            }
        },
        closeMemberFormDialog() {
            this.memberFormIsOpen = false;
            this.resetToNewMember();
        },
        
        toggleMemberInfo() {
            this.showMemberInfo = !this.showMemberInfo;
        },
        resetNewMember() {
            this.newFirstname = '';
            this.newLastname = '';
            this.newStreet = '';
            this.newPostalCode = '';
            this.newCity = '';
            this.newBirthdate = '';
            this.newContributionGroupCode = '';
            this.newPhone = '';
            this.newEmail = '';
            this.newActive = true;
            this.newPicsInInternetAllowed = false;
            this.testMembership = true;
            this.newGender = 'unknown';
            this.memberImage = null;
            this.memberImagePreview = null;
            this.newMemberFormHandedOver = false;
            this.newAdultReleaseApproved = false;
            this.newAdultReserveApproved = false;
            this.memberContacts = {
                phones: [{ value: '', isParent: false, parentName: '', isPrimary: false }],
                emails: [{ value: '', isParent: false, parentName: '', isPrimary: false }]
            };
            this.resetMemberSepaMandateForm();
        },
        resetMemberSepaMandateForm() {
            this.memberSepaMandateLoading = false;
            this.memberSepaMandateError = '';
            this.memberSepaMandateForm = {
                debtorName: '',
                iban: '',
                bic: '',
                mandateReference: '',
                signedOn: '',
                validFrom: '',
                status: 'active',
                historyNote: ''
            };
        },
        applyMemberSepaMandateForm(mandate) {
            if (!mandate) {
                this.resetMemberSepaMandateForm();
                return;
            }
            this.memberSepaMandateError = '';
            this.memberSepaMandateForm = {
                debtorName: mandate.debtorName || '',
                iban: mandate.iban || '',
                bic: mandate.bic || '',
                mandateReference: mandate.mandateReference || '',
                signedOn: this.formatDateForInput(mandate.signedOn),
                validFrom: this.formatDateForInput(mandate.validFrom),
                status: mandate.status || 'active',
                historyNote: mandate.historyNote || ''
            };
        },
        getMemberSepaApiError(response, fallbackKey) {
            const status = Number(response?.status || 0);
            if (status >= 200 && status < 300) {
                return '';
            }

            const responseError = getSafeMessage(
                response?.data?.error || response?.data?.message || response?.data?.code,
                ''
            );

            return responseError || this.$t(fallbackKey);
        },
        async loadMemberSepaMandate(memberId) {
            this.resetMemberSepaMandateForm();
            if (!memberId || !this.canEditMemberBankAccount) {
                return;
            }

            this.memberSepaMandateLoading = true;
            try {
                const response = await apiClient.get(`/clubmembers/sepa/${this.currentClub}/${memberId}`);
                const responseError = this.getMemberSepaApiError(response, 'members.bankAccountLoadError');
                if (responseError) {
                    throw new Error(responseError);
                }
                const mandate = response.data?.mandate;
                this.applyMemberSepaMandateForm(mandate);
            } catch (error) {
                console.error('[loadMemberSepaMandate] error:', error);
                this.memberSepaMandateError = getSafeErrorMessage(error, this.$t('members.bankAccountLoadError'));
            } finally {
                this.memberSepaMandateLoading = false;
            }
        },
        async saveMemberSepaMandate() {
            if (!this.memberToEdit || !this.canEditMemberBankAccount) {
                return;
            }

            this.memberSepaMandateLoading = true;
            this.memberSepaMandateError = '';
            try {
                const response = await apiClient.put(`/clubmembers/sepa/${this.currentClub}/${this.memberToEdit.id}`, {
                    ...this.memberSepaMandateForm
                });
                const responseError = this.getMemberSepaApiError(response, 'members.bankAccountSaveError');
                if (responseError) {
                    throw new Error(responseError);
                }
                if (response.data?.success !== true) {
                    throw new Error(getSafeMessage(response.data?.error, this.$t('members.bankAccountSaveError')));
                }

                const reloadResponse = await apiClient.get(`/clubmembers/sepa/${this.currentClub}/${this.memberToEdit.id}`);
                const reloadError = this.getMemberSepaApiError(reloadResponse, 'members.bankAccountLoadError');
                if (reloadError) {
                    throw new Error(reloadError);
                }
                const mandate = reloadResponse.data?.mandate;
                if (!mandate) {
                    throw new Error(this.$t('members.bankAccountMissingAfterSave'));
                }

                this.applyMemberSepaMandateForm(mandate);
                await this.showInfo(this.$t('messages.success'), this.$t('members.bankAccountSaved'), '', 'success');
            } catch (error) {
                console.error('[saveMemberSepaMandate] error:', error);
                this.memberSepaMandateError = getSafeErrorMessage(error, this.$t('members.bankAccountSaveError'));
            } finally {
                this.memberSepaMandateLoading = false;
            }
        },
        getContributionGroupContext() {
            return {
                birthDate: this.memberToEdit?.birthDate || this.newBirthdate || '',
                testMembership: this.memberToEdit?.testMembership ?? this.testMembership,
                contributionGroupCode: this.newContributionGroupCode,
            };
        },
        getContributionGroupPresets(member = null) {
            const age = this.getAge(member?.birthDate || this.newBirthdate);
            if (member?.testMembership || this.testMembership) {
                return [
                    { code: 'Test', label: 'Test' },
                    { code: 'Jugend', label: 'Jugend' },
                    { code: 'Erwachsene', label: 'Erwachsene' },
                ];
            }
            if (Number.isFinite(age) && age < 18) {
                return [
                    { code: 'Jugend', label: 'Jugend' },
                    { code: 'Ermäßigt', label: 'Ermäßigt' },
                    { code: 'Familie', label: 'Familie' },
                ];
            }
            return [
                { code: 'Erwachsene', label: 'Erwachsene' },
                { code: 'Familie', label: 'Familie' },
                { code: 'Passiv', label: 'Passiv' },
            ];
        },
        getSuggestedContributionGroupCode(member = null) {
            const age = this.getAge(member?.birthDate || this.newBirthdate);
            if (member?.testMembership || this.testMembership) {
                return 'Test';
            }
            if (Number.isFinite(age) && age < 18) {
                return 'Jugend';
            }
            if (Number.isFinite(age) && age >= 18) {
                return 'Erwachsene';
            }
            return '';
        },
        getContributionGroupHint(member = null) {
            const currentCode = String(member?.contributionGroupCode || member?.contribution_group_code || member?.contributionGroupCode || this.newContributionGroupCode || '').trim();
            const suggestedCode = this.getSuggestedContributionGroupCode(member);
            if (!currentCode && suggestedCode) {
                return `Vorschlag: ${suggestedCode}`;
            }
            if (!currentCode) {
                return 'Noch keine Beitragsgruppe gesetzt.';
            }
            return suggestedCode && currentCode !== suggestedCode
                ? `Gesetzt: ${currentCode} · Vorschlag: ${suggestedCode}`
                : `Gesetzt: ${currentCode}`;
        },
        applyContributionGroupPreset(code) {
            this.newContributionGroupCode = String(code || '').trim();
        },
        addContact(type) {
            if (type === 'phone') {
                this.memberContacts.phones.push({
                    value: '',
                    isParent: false,
                    parentName: '',
                    isPrimary: false
                });
            } else if (type === 'email') {
                this.memberContacts.emails.push({
                    value: '',
                    isParent: false,
                    parentName: '',
                    isPrimary: false
                });
            }
        },
        removeContact(type, index) {
            if (type === 'phone') {
                this.memberContacts.phones.splice(index, 1);
            } else if (type === 'email') {
                this.memberContacts.emails.splice(index, 1);
            }
        },
        onFileSelected(event) {
            const file = event.target.files[0];
            if (!file) {
                this.memberImage = null;
                this.memberImagePreview = null;
                return;
            }
            const maxSize = 5 * 1024 * 1024; // 5 MB
            if (!file.type.startsWith('image/')) {
                this.showInfo(this.$t('messages.invalidFile'), this.$t('messages.pleaseSelectImageFile'), '', 'warning');
                event.target.value = '';
                return;
            }
            if (file.size > maxSize) {
                this.showInfo(this.$t('messages.fileTooLarge'), this.$t('messages.imageMaxSize'), '', 'warning');
                event.target.value = '';
                return;
            }
            this.memberImage = file;
            const reader = new FileReader();
            reader.onload = (readerEvent) => {
                this.memberImagePreview = readerEvent.target.result;
            };
            reader.readAsDataURL(file);
        },
        standardizePhoneNumber(phoneNumber) {
            if (!phoneNumber || typeof phoneNumber !== 'string') {
                return phoneNumber || '';
            }

            // Trennzeichen, die die Vorwahl abkapseln können
            const separators = [' ', '.', '-', '_', '/'];
            
            // Entferne alle Zeichen außer Zahlen und Trennzeichen
            let cleaned = phoneNumber.replace(/[^\d\s.\-_\/]/g, '');
            
            // Extrahiere nur die Zahlen
            const digitsOnly = cleaned.replace(/[^\d]/g, '');
            
            if (digitsOnly.length === 0) {
                return phoneNumber; // Falls keine Zahlen gefunden, Original zurückgeben
            }
            
            // Finde das erste Trennzeichen zwischen Zahlen
            let areaCodeEndIndex = -1;
            for (let i = 0; i < cleaned.length - 1; i++) {
                if (separators.includes(cleaned[i])) {
                    // Prüfe, ob vor und nach dem Trennzeichen eine Zahl steht
                    const beforeIsDigit = i > 0 && /\d/.test(cleaned[i - 1]);
                    const afterIsDigit = /\d/.test(cleaned[i + 1]);
                    if (beforeIsDigit && afterIsDigit) {
                        areaCodeEndIndex = i;
                        break;
                    }
                }
            }
            
            let areaCode = '';
            let rest = '';
            
            if (areaCodeEndIndex !== -1) {
                // Extrahiere die Vorwahl direkt aus dem String vor dem Trennzeichen
                const beforeSeparator = cleaned.substring(0, areaCodeEndIndex);
                areaCode = beforeSeparator.replace(/[^\d]/g, ''); // Nur Ziffern aus dem Teil vor dem Trennzeichen
                
                // Prüfe, ob die Vorwahl plausibel ist (typische deutsche Vorwahlen: 3-5 Ziffern)
                // Wenn die Vorwahl zu lang ist (>=5 Ziffern) oder wenn nach dem Trennzeichen zu wenige Ziffern sind,
                // versuche eine kürzere Vorwahl
                if (digitsOnly.startsWith('0')) {
                    const afterSeparator = cleaned.substring(areaCodeEndIndex + 1);
                    const restDigits = afterSeparator.replace(/[^\d]/g, '');
                    
                    // Wenn die Vorwahl >= 5 Ziffern hat oder wenn nach dem Trennzeichen zu wenige Ziffern sind (<6),
                    // versuche eine kürzere Vorwahl
                    if (areaCode.length >= 5 || (areaCode.length > 3 && restDigits.length < 6)) {
                        // Versuche typische Vorwahl-Längen: 4 für Mobilnummern (01xx), 3 für Festnetz
                        if (digitsOnly.substring(0, 2) === '01' && digitsOnly.length >= 4) {
                            areaCode = digitsOnly.substring(0, 4);
                        } else if (digitsOnly.length >= 3) {
                            areaCode = digitsOnly.substring(0, 3);
                        }
                    }
                }
                
                // Finde die Position der Vorwahl in digitsOnly und extrahiere den Rest
                // Die Vorwahl sollte am Anfang von digitsOnly stehen
                if (digitsOnly.startsWith(areaCode)) {
                    rest = digitsOnly.substring(areaCode.length);
                } else {
                    // Fallback: Wenn die Vorwahl nicht am Anfang steht, verwende die Länge
                    rest = digitsOnly.substring(areaCode.length);
                }
            } else {
                // Kein Trennzeichen gefunden - versuche, die Vorwahl zu erraten
                // Typische deutsche Vorwahlen: 3-5 Ziffern (z.B. 0179, 030, 040)
                // Wenn die Nummer mit 0 beginnt, ist es wahrscheinlich eine Mobilnummer (4 Ziffern) oder Festnetz (3-5 Ziffern)
                if (digitsOnly.startsWith('0')) {
                    // Mobilnummern beginnen meist mit 01 und haben 4 Ziffern Vorwahl
                    if (digitsOnly.length >= 4 && digitsOnly.substring(0, 2) === '01') {
                        areaCode = digitsOnly.substring(0, 4);
                        rest = digitsOnly.substring(4);
                    } else if (digitsOnly.length >= 3) {
                        // Festnetz: 3-5 Ziffern, versuche 3 als Standard
                        areaCode = digitsOnly.substring(0, 3);
                        rest = digitsOnly.substring(3);
                    } else {
                        // Zu kurz, keine Vorwahl
                        areaCode = '';
                        rest = digitsOnly;
                    }
                } else {
                    // Keine führende 0 - wahrscheinlich keine Vorwahl oder internationale Nummer
                    // Nehme die ersten 3-4 Ziffern als Vorwahl
                    if (digitsOnly.length >= 4) {
                        areaCode = digitsOnly.substring(0, 4);
                        rest = digitsOnly.substring(4);
                    } else {
                        areaCode = '';
                        rest = digitsOnly;
                    }
                }
            }
            
            // Formatiere den Rest in Zweierblöcke
            let formattedRest = '';
            for (let i = 0; i < rest.length; i += 2) {
                if (i > 0) {
                    formattedRest += ' ';
                }
                // Wenn es die letzte Gruppe ist und nur eine Ziffer übrig ist, füge sie ohne Leerzeichen hinzu
                if (i + 2 >= rest.length) {
                    formattedRest += rest.substring(i);
                    break;
                } else {
                    formattedRest += rest.substring(i, i + 2);
                }
            }
            
            // Kombiniere Vorwahl und Rest
            if (areaCode && formattedRest) {
                return `${areaCode} - ${formattedRest}`;
            } else if (areaCode) {
                return areaCode;
            } else if (formattedRest) {
                return formattedRest;
            } else {
                return phoneNumber; // Fallback: Original zurückgeben
            }
        },
        async addNewMember() {
            // Prepare contacts array
            const contacts = [];
            this.memberContacts.phones.forEach(phone => {
                if (phone.value && phone.value.trim()) {
                    contacts.push({
                        type: 'phone',
                        value: this.standardizePhoneNumber(phone.value.trim()),
                        isParent: phone.isParent || false,
                        parentName: phone.isParent ? (phone.parentName || null) : null,
                        isPrimary: phone.isPrimary || false
                    });
                }
            });
            this.memberContacts.emails.forEach(email => {
                if (email.value && email.value.trim()) {
                    contacts.push({
                        type: 'email',
                        value: email.value.trim(),
                        isParent: email.isParent || false,
                        parentName: email.isParent ? (email.parentName || null) : null,
                        isPrimary: email.isPrimary || false
                    });
                }
            });
            
            const memberData = {
                firstname: this.newFirstname,
                lastname: this.newLastname,
                street: this.newStreet,
                city: this.newCity,
                postalCode: this.newPostalCode || null,
                birthdate: this.newBirthdate,
                phone: this.newPhone ? this.standardizePhoneNumber(this.newPhone) : this.newPhone, // Keep for backward compatibility
                email: this.newEmail, // Keep for backward compatibility
                gender: this.newGender,
                active: this.newActive,
                id: this.memberToEdit ? this.memberToEdit.id : null,
                testMembership: this.testMembership,
                picsInInternetAllowed: this.newPicsInInternetAllowed,
                memberFormHandedOver: this.newMemberFormHandedOver,
                adultReleaseApproved: this.newAdultReleaseApproved,
                adultReserveApproved: this.newAdultReserveApproved,
                contributionGroupCode: this.newContributionGroupCode,
                contacts: contacts
            };

            let response;
            try {
                response = await apiClient.post(`/clubmembers/set/${this.currentClub}`, memberData);
                this.loadMembers();
            } catch (error) {
                this.showInfo(this.$t('messages.error'), this.$t('members.errorSavingMember'), '', 'error');
                return;
            }

            if (this.memberImage) {
                const formData = new FormData();
                formData.append('image', this.memberImage);
                formData.append('clubId', this.currentClub);  
                try {
                    await apiClient.post(`/clubmembers/image/${this.currentClub}/${this.memberToEdit.id}`, formData, {
                        headers: {
                            'Content-Type': 'multipart/form-data'
                        }
                    });
                } catch (error) {
                    // Kein Alert - es ist normal, dass nicht alle Mitglieder Bilder haben
                }
            }

            this.resetNewMember();
            this.memberFormIsOpen = false;
        },
        async editMember(member) {
            const birthDate = member.birthDate;
            this.closeMemberPreviewDialog();
            this.memberToEdit = member;
            this.memberFormIsOpen = true;
            this.newFirstname = member.firstName;
            this.newLastname = member.lastName;
            this.newStreet = member.street;
            this.newPostalCode = member.postalCode || '';
            this.newCity = member.city;
            this.newPhone = member.phone;
            this.newEmail = member.email;
            this.newGender = member.gender || 'unknown';
            this.newActive = member.active;
            this.newBirthdate = this.formatDateForInput(birthDate);
            this.newContributionGroupCode = String(member.contributionGroupCode || member.contribution_group_code || '').trim();
            this.testMembership = member.testMembership;
            this.newPicsInInternetAllowed = member.picsInInternetAllowed;
            this.newMemberFormHandedOver = !!member.memberFormHandedOver;
            this.newAdultReleaseApproved = !!member.adultReleaseApproved;
            this.newAdultReserveApproved = !!member.adultReserveApproved;
            
            // Load contacts
            if (member.contacts && Array.isArray(member.contacts)) {
                this.memberContacts.phones = member.contacts
                    .filter(c => c.type === 'phone')
                    .map(c => ({
                        value: c.value || '',
                        isParent: c.isParent || false,
                        parentName: c.parentName || '',
                        isPrimary: c.isPrimary || false
                    }));
                this.memberContacts.emails = member.contacts
                    .filter(c => c.type === 'email')
                    .map(c => ({
                        value: c.value || '',
                        isParent: c.isParent || false,
                        parentName: c.parentName || '',
                        isPrimary: c.isPrimary || false
                    }));
                
                // If no contacts exist, add empty ones
                if (this.memberContacts.phones.length === 0) {
                    this.memberContacts.phones.push({ value: '', isParent: false, parentName: '', isPrimary: false });
                }
                if (this.memberContacts.emails.length === 0) {
                    this.memberContacts.emails.push({ value: '', isParent: false, parentName: '', isPrimary: false });
                }
            } else {
                // Fallback: use old phone/email fields
                this.memberContacts.phones = [{ value: member.phone || '', isParent: false, parentName: '', isPrimary: true }];
                this.memberContacts.emails = [{ value: member.email || '', isParent: false, parentName: '', isPrimary: true }];
            }
            
            // Load training groups for this member
            await this.loadMemberTrainingGroups(member.id);
            await this.loadMemberSepaMandate(member.id);
            try {
                const response = await apiClient.get(`/clubmembers/image/${member.id}`, { 
                    responseType: 'blob'
                });
                this.memberImagePreview = URL.createObjectURL(response.data);
            } catch (error) {
                // Kein Alert - es ist normal, dass nicht alle Mitglieder Bilder haben
                this.memberImagePreview = null;
            }
        },
        formatDateForInput(value) {
            if (!value || typeof value !== 'string') return '';
            const v = value.trim();
            // dd.mm.yyyy
            const m1 = v.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
            if (m1) {
                const yyyy = m1[3];
                const mm = String(Number(m1[2])).padStart(2, '0');
                const dd = String(Number(m1[1])).padStart(2, '0');
                return `${yyyy}-${mm}-${dd}`;
            }
            // ISO/yyy-mm-dd
            const d = new Date(v);
            if (!isNaN(d.getTime())) return d.toISOString().split('T')[0];
            return '';
        },
        resetToNewMember() {
            this.memberToEdit = null;
            this.memberTrainingGroups = [];
            this.selectedGroupToAdd = '';
            this.resetNewMember();
        },
        
        async loadTrainingGroups() {
            try {
                const response = await apiClient.get(`/training-groups/${this.currentClub}`);
                const groups = Array.isArray(response.data) ? response.data : [];
                this.trainingGroups = groups.map(group => ({
                    ...group,
                    members: Array.isArray(group.members) ? group.members : []
                }));
            } catch (error) {
                console.error('[loadTrainingGroups] Error:', error);
                this.trainingGroups = [];
            }
        },
        
        async loadMemberTrainingGroups(memberId) {
            try {
                const response = await apiClient.get(`/training-groups/${this.currentClub}/member/${memberId}`);
                this.memberTrainingGroups = Array.isArray(response.data) ? response.data : [];
            } catch (error) {
                console.error('[loadMemberTrainingGroups] Error:', error);
                this.memberTrainingGroups = [];
            }
        },
        
        async addMemberToGroup(groupId) {
            if (!groupId || !this.memberToEdit) {
                return;
            }
            
            try {
                await apiClient.post(`/training-groups/${this.currentClub}/${groupId}/member/${this.memberToEdit.id}`);
                this.selectedGroupToAdd = '';
                await this.loadMemberTrainingGroups(this.memberToEdit.id);
            } catch (error) {
                console.error('[addMemberToGroup] Error:', error);
                const msg = getSafeErrorMessage(error, this.$t('members.errorAddingToGroup'));
                this.showInfo(this.$t('messages.error'), msg, '', 'error');
            }
        },
        
        async removeMemberFromGroup(groupId) {
            if (!this.memberToEdit) {
                return;
            }
            
            try {
                await apiClient.delete(`/training-groups/${this.currentClub}/${groupId}/member/${this.memberToEdit.id}`);
                await this.loadMemberTrainingGroups(this.memberToEdit.id);
            } catch (error) {
                console.error('[removeMemberFromGroup] Error:', error);
                const msg = getSafeErrorMessage(error, this.$t('members.errorRemovingFromGroup'));
                this.showInfo(this.$t('messages.error'), msg, '', 'error');
            }
        },
        
        async loadNotes(member) {
            this.selectedMember = member;
            const response = await apiClient.get(`/membernotes/${member.id}`, {
                params: { clubId: this.currentClub }
            });
            this.notes = response.data;
            this.showNotesModal = true;
        },
        async addNote() {
            const response = await apiClient.post('/membernotes', {
                memberId: this.selectedMember.id,
                content: this.newNoteContent,
                clubId: this.currentClub
            });
            this.notes = response.data;
            this.newNoteContent = '';
        },
        async deleteNote(noteId) {
            await apiClient.delete(`/membernotes/${noteId}`, {
                data: { clubId: this.currentClub }
            });
            this.notes = response.data;
        },
        openNotesModal(member) {
            this.memberToEdit = member;
            this.showNotesModal = true;
        },
        closeNotesModal() {
            this.showNotesModal = false;
        },
        openActivitiesModal(member) {
            this.selectedMemberForActivities = member;
            this.showActivitiesModal = true;
        },
        async openImageModal(member, imageId = null) {
            if (!member) return;
            this.selectedMemberForImages = member;
            this.selectedMemberId = member.id;
            const images = Array.isArray(member.images) ? [...member.images] : [];
            await Promise.allSettled(images.map(image => this.ensureImageObjectUrl(image)));
            this.selectedMemberImages = images;
            this.selectedImageId = imageId || member.primaryImageId || (this.selectedMemberImages[0]?.id ?? null);
            this.showImageModal = true;
        },
        closeImageModal() {
            this.showImageModal = false;
            this.selectedMemberId = null;
            this.selectedMemberImages = [];
            this.selectedImageId = null;
            this.selectedMemberForImages = null;
        },
        async handleRotate(event) {
            const { direction, memberId, imageId } = event || {};
            if (!memberId || !imageId) return;

            try {
                const response = await apiClient.post(`/clubmembers/rotate-image/${this.currentClub}/${memberId}/${imageId}`, {
                    direction
                });

                const member = this.members.find(m => m.id === memberId);

                if (response.data?.success && member) {
                    await this.applyMemberImageUpdate(member, response.data);
                    this.showInfo(this.$t('messages.success'), getSafeMessage(response.data.message, this.$t('members.imageUpdated')), '', 'success');
                } else {
                    const msg = getSafeErrorMessage(response.data?.error || null, this.$t('members.errorRotatingImage'));
                    this.showInfo(this.$t('messages.error'), msg, '', 'error');
                }
            } catch (error) {
                console.error(this.$t('members.errorRotatingImage'), error);
                this.showInfo(this.$t('messages.error'), getSafeErrorMessage(error, this.$t('members.errorRotatingImage')), '', 'error');
            }
        },
        async handleDeleteImage(event) {
            const { memberId, imageId } = event || {};
            if (!memberId || !imageId) return;
            const member = this.members.find(m => m.id === memberId);
            if (!member) return;

            const confirmed = await this.showConfirm(
                this.$t('members.deleteImageTitle'),
                this.$t('members.deleteImageConfirm'),
                '',
                'danger'
            );
            if (!confirmed) {
                return;
            }

            try {
                const response = await apiClient.delete(`/clubmembers/image/${this.currentClub}/${memberId}/${imageId}`);
                if (response.data?.success) {
                    this.applyMemberImageUpdate(member, response.data);
                    this.showInfo(this.$t('messages.success'), this.$t('members.imageDeleted'), '', 'success');
                } else {
                    const msg = getSafeMessage(response.data?.error, this.$t('members.errorDeletingImage'));
                    this.showInfo(this.$t('messages.error'), msg, '', 'error');
                }
            } catch (error) {
                console.error(this.$t('members.errorDeletingImage'), error);
                this.showInfo(this.$t('messages.error'), getSafeErrorMessage(error, this.$t('members.errorDeletingImage')), '', 'error');
            }
        },
        async handleSetPrimaryImage(event) {
            const { memberId, imageId } = event || {};
            if (!memberId || !imageId) return;
            const member = this.members.find(m => m.id === memberId);
            if (!member) return;

            try {
                const response = await apiClient.post(`/clubmembers/image/${this.currentClub}/${memberId}/${imageId}/primary`);
                if (response.data?.success) {
                    this.applyMemberImageUpdate(member, response.data);
                    this.showInfo(this.$t('messages.success'), this.$t('members.primaryImageUpdated'), '', 'success');
                } else {
                    const msg = getSafeMessage(response.data?.error, this.$t('members.errorSettingPrimaryImage'));
                    this.showInfo(this.$t('messages.error'), msg, '', 'error');
                }
            } catch (error) {
                console.error(this.$t('members.errorSettingPrimaryImage'), error);
                this.showInfo(this.$t('messages.error'), getSafeErrorMessage(error, this.$t('members.errorSettingPrimaryImage')), '', 'error');
            }
        },
        async handleUploadImages(event) {
            const { memberId, files } = event || {};
            if (!memberId || !files || files.length === 0) return;
            const member = this.members.find(m => m.id === memberId);
            if (!member) return;

            const fileArray = Array.from(files);
            for (let index = 0; index < fileArray.length; index += 1) {
                const file = fileArray[index];
                const formData = new FormData();
                formData.append('image', file);
                const shouldBePrimary = (!member.images || member.images.length === 0) && index === 0;
                if (shouldBePrimary) {
                    formData.append('makePrimary', 'true');
                }

                try {
                    const response = await apiClient.post(`/clubmembers/image/${this.currentClub}/${memberId}`, formData, {
                        headers: {
                            'Content-Type': 'multipart/form-data'
                        }
                    });
                    if (response.data?.success) {
                        await this.applyMemberImageUpdate(member, response.data);
                    } else {
                        const msg = getSafeMessage(response.data?.error, this.$t('members.errorUploadingImage'));
                        this.showInfo(this.$t('messages.error'), msg, '', 'error');
                    }
                } catch (error) {
                    console.error(this.$t('members.errorUploadingImage'), error);
                    this.showInfo(this.$t('messages.error'), getSafeErrorMessage(error, this.$t('members.errorUploadingImage')), '', 'error');
                }
            }
        },
        async handleGroupPhotoMemberImageUpdated(event) {
            const { memberId, payload } = event || {};
            const member = this.members.find(m => String(m.id) === String(memberId));
            if (!member) return;
            await this.applyMemberImageUpdate(member, payload);
            await this.showInfo(this.$t('messages.success'), this.$t('members.groupPhotoCropSaved'), '', 'success');
        },
        async applyMemberImageUpdate(member, payload) {
            if (!member || !payload) return;
            const images = Array.isArray(payload.images) ? payload.images.map(image => this.createImageObject(image)) : [];
            member.images = images;
            member.primaryImageId = payload.primaryImageId || (images[0]?.id ?? null);
            member.primaryImageUrl = null;
            member.imageUrl = null;
            member.latestImageId = this.getLatestImageId(images);
            member.latestImageUrl = null;
            member.hasImage = images.length > 0;

            await this.prefetchMemberPrimaryImage(member);
            await this.prefetchMemberLatestImage(member);

            if (this.selectedMemberForImages && this.selectedMemberForImages.id === member.id) {
                this.selectedMemberForImages.images = images;
                await Promise.allSettled(images.map(image => this.ensureImageObjectUrl(image)));
                this.selectedMemberImages = [...images];
                if (!this.selectedImageId || !images.some(img => img.id === this.selectedImageId)) {
                    this.selectedImageId = member.primaryImageId || (images[0]?.id ?? null);
                }
            }
        },
        applyBackendBaseUrlToMember(member) {
            if (!member) return member;
            const cloned = { ...member };
            cloned.images = Array.isArray(member.images) ? member.images.map(image => this.createImageObject(image)) : [];
            cloned.primaryImageId = member.primaryImageId || (cloned.images[0]?.id ?? null);
            cloned.primaryImageUrl = null;
            cloned.imageUrl = null;
            cloned.latestImageId = this.getLatestImageId(cloned.images);
            cloned.latestImageUrl = null;
            return cloned;
        },
        createImageObject(image) {
            const apiPath = (image?.url || '').replace(/^\/?api\//i, '').replace(/^\//, '');
            return {
                ...image,
                apiPath,
                objectUrl: image?.objectUrl || null,
                url: image?.objectUrl || null
            };
        },
        async prefetchMemberPrimaryImage(member) {
            if (!member || !member.primaryImageId) {
                member.primaryImageUrl = null;
                member.imageUrl = null;
                return;
            }
            const primaryImage = Array.isArray(member.images)
                ? member.images.find(img => img.id === member.primaryImageId) || member.images[0]
                : null;
            if (!primaryImage) {
                member.primaryImageUrl = null;
                member.imageUrl = null;
                return;
            }
            const objectUrl = await this.ensureImageObjectUrl(primaryImage);
            member.primaryImageUrl = objectUrl;
            member.imageUrl = objectUrl;
        },
        async prefetchMemberLatestImage(member) {
            if (!member || !member.latestImageId) {
                member.latestImageUrl = member.primaryImageUrl || null;
                return;
            }
            const latestImage = Array.isArray(member.images)
                ? member.images.find(img => img.id === member.latestImageId) || member.images[member.images.length - 1]
                : null;
            if (!latestImage) {
                member.latestImageUrl = member.primaryImageUrl || null;
                return;
            }
            const objectUrl = await this.ensureImageObjectUrl(latestImage);
            member.latestImageUrl = objectUrl || member.primaryImageUrl || null;
        },
        getLatestImageId(images) {
            if (!Array.isArray(images) || images.length === 0) {
                return null;
            }
            return images
                .filter(image => typeof image.id === 'number' || typeof image.id === 'string')
                .map(image => Number(image.id))
                .filter(id => !Number.isNaN(id))
                .sort((a, b) => a - b)
                .pop() || images[images.length - 1].id;
        },
        async ensureImageObjectUrl(image) {
            if (!image || image.objectUrl) {
                return image?.objectUrl || null;
            }
            if (!image.apiPath) {
                return null;
            }
            try {
                const response = await apiClient.get(image.apiPath, { responseType: 'blob' });
                const objectUrl = URL.createObjectURL(response.data);
                image.objectUrl = objectUrl;
                image.url = objectUrl;
                return objectUrl;
            } catch (error) {
                console.error(this.$t('members.errorLoadingImage'), error);
                return null;
            }
        },
        onGalleryClosed() {
            this.closeImageModal();
        },
        async createPhoneList() {
            const activeMembers = this.members.filter(member => member.active && !member.testMembership);
            const pdfGenerator = new PDFGenerator(20, 10, this.$t);
            pdfGenerator.addPhoneList(activeMembers);
            pdfGenerator.save(this.$t('members.phoneList'));
        },
        getFormattedBirthdate(birthDate) {
            if (!birthDate) return '–';
            const date = new Date(birthDate);
            if (isNaN(date.getTime())) return '–';
            return `${String(date.getDate()).padStart(2, '0')}.${String(date.getMonth() + 1).padStart(2, '0')}.${date.getFullYear()}`;
        },
        getAgeLabel(birthDate) {
            const age = this.getAge(birthDate);
            return age === null ? '–' : String(age);
        },
        getOptionalFormattedDate(value, fallbackKey) {
            const formatted = this.getFormattedBirthdate(value);
            if (formatted === '–') {
                return this.$t(fallbackKey);
            }
            return formatted;
        },
        getCompactAddress(member) {
            if (!member) return '';
            const parts = [
                member.street || '',
                [member.postalCode, member.city].filter(Boolean).join(' ')
            ].filter(Boolean);
            return parts.join(', ');
        },
        getFormattedPhoneNumbers(member) {
            if (!member) return '–';
            
            // Sammle alle Telefonnummern aus contacts
            let phoneNumbers = [];
            if (member.contacts && Array.isArray(member.contacts)) {
                const phoneContacts = member.contacts
                    .filter(c => c.type === 'phone' && c.value && String(c.value).trim() !== '')
                    .sort((a, b) => {
                        // Primäre Telefonnummer zuerst
                        if (a.isPrimary && !b.isPrimary) return -1;
                        if (!a.isPrimary && b.isPrimary) return 1;
                        return 0;
                    });
                
                phoneContacts.forEach(contact => {
                    let phoneText = contact.value;
                    if (contact.isParent) {
                        if (contact.parentName) {
                            phoneText += ` (${contact.parentName})`;
                        } else {
                            phoneText += ` (${this.$t('members.parentFallback')})`;
                        }
                    }
                    phoneNumbers.push(phoneText);
                });
            }
            
            // Fallback auf altes phone-Feld für Rückwärtskompatibilität
            if (phoneNumbers.length === 0 && member.phone) {
                phoneNumbers.push(member.phone);
            }
            
            return phoneNumbers.length > 0 ? phoneNumbers.join(', ') : '–';
        },
        getFormattedEmails(member) {
            if (!member) return '–';
            
            // Sammle alle E-Mail-Adressen aus contacts
            let emails = [];
            if (member.contacts && Array.isArray(member.contacts)) {
                const emailContacts = member.contacts
                    .filter(c => c.type === 'email' && c.value && String(c.value).trim() !== '')
                    .sort((a, b) => {
                        // Primäre E-Mail zuerst
                        if (a.isPrimary && !b.isPrimary) return -1;
                        if (!a.isPrimary && b.isPrimary) return 1;
                        return 0;
                    });
                
                emailContacts.forEach(contact => {
                    let emailText = contact.value;
                    if (contact.isParent) {
                        if (contact.parentName) {
                            emailText += ` (${contact.parentName})`;
                        } else {
                            emailText += ` (${this.$t('members.parentFallback')})`;
                        }
                    }
                    emails.push(emailText);
                });
            }
            
            // Fallback auf altes email-Feld für Rückwärtskompatibilität
            if (emails.length === 0 && member.email) {
                emails.push(member.email);
            }
            
            return emails.length > 0 ? emails.join(', ') : '–';
        },
        labelGender(g) {
            const v = (g || 'unknown');
            if (v === 'male') return this.$t('members.genderMale');
            if (v === 'female') return this.$t('members.genderFemale');
            if (v === 'diverse') return this.$t('members.genderDiverse');
            return this.$t('members.genderUnknown');
        },
        genderSymbol(g) {
            const v = (g || 'unknown');
            if (v === 'male') return '♂';
            if (v === 'female') return '♀';
            if (v === 'diverse') return '⚧';
            return '';
        },
        getMemberStatusBadges(member) {
            const badges = [];
            if (!member.active) {
                badges.push({ key: 'inactive', label: this.$t('members.inactive'), tone: 'muted' });
            } else {
                badges.push({ key: 'active', label: this.$t('members.active'), tone: 'positive' });
            }
            if (member.testMembership) {
                badges.push({ key: 'test', label: this.$t('members.scopeTest'), tone: 'warning' });
            }
            if (!member.memberFormHandedOver) {
                badges.push({ key: 'form', label: this.$t('members.scopeNeedsForm'), tone: 'warning' });
            }
            if (this.getMemberDataQualityIssues(member).length) {
                badges.push({ key: 'data-quality', label: this.$t('members.scopeDataIncomplete'), tone: 'warning' });
            }
            if (!this.getMemberTrainingGroups(member).length) {
                badges.push({ key: 'no-group', label: this.$t('members.noGroupsAssigned'), tone: 'warning' });
            }
            if (member.notInTraining) {
                badges.push({ key: 'not-training', label: this.$t('members.scopeNotTraining'), tone: 'danger' });
            }
            if (member.clickTtApplicationSubmitted) {
                badges.push({ key: 'clicktt', label: this.$t('members.clickTtSubmitted'), tone: 'info' });
            }
            return badges;
        },
        getMemberDataQualityIssues(member) {
            if (!member) return [];

            const issues = [];
            const requirements = this.memberDataQualityRequirements;
            const hasPhone = this.getFormattedPhoneNumbers(member) !== '–';
            const hasEmail = this.getFormattedEmails(member) !== '–';
            const hasTrainingGroup = this.getMemberTrainingGroups(member).length > 0;

            if (!member.birthDate) {
                issues.push({ key: 'birthdate', label: this.$t('members.dataIssueBirthdate') });
            }
            if (requirements.requirePhone && !hasPhone) {
                issues.push({ key: 'phone', label: this.$t('members.dataIssuePhone') });
            }
            if (requirements.requireEmail && !hasEmail) {
                issues.push({ key: 'email', label: this.$t('members.dataIssueEmail') });
            }
            if (requirements.requireStreet && !this.hasTextValue(member.street)) {
                issues.push({ key: 'street', label: this.$t('members.dataIssueStreet') });
            }
            if (requirements.requirePostalCode && !this.hasTextValue(member.postalCode)) {
                issues.push({ key: 'postal-code', label: this.$t('members.dataIssuePostalCode') });
            }
            if (requirements.requireCity && !this.hasTextValue(member.city)) {
                issues.push({ key: 'city', label: this.$t('members.dataIssueCity') });
            }
            if (!member.gender || member.gender === 'unknown') {
                issues.push({ key: 'gender', label: this.$t('members.dataIssueGender') });
            }
            if (!hasTrainingGroup) {
                issues.push({ key: 'training-group', label: this.$t('members.dataIssueTrainingGroup') });
            }

            return issues;
        },
        getMemberOpenTasks(member) {
            if (!member) return [];
            const tasks = [];
            if (!member.memberFormHandedOver) {
                tasks.push({
                    key: 'verify-form',
                    label: this.$t('members.taskVerifyForm'),
                    action: 'verify-form',
                    actionLabel: this.$t('members.taskActionVerify')
                });
            }
            if (member.testMembership) {
                tasks.push({
                    key: 'review-trial',
                    label: this.$t('members.taskReviewTrialStatus'),
                    action: 'mark-regular',
                    actionLabel: this.$t('members.taskActionMarkRegular')
                });
            }
            if (member.notInTraining) {
                tasks.push({
                    key: 'training-status',
                    label: this.$t('members.taskCheckTrainingStatus'),
                    action: null,
                    actionLabel: ''
                });
            }
            if (this.getMemberDataQualityIssues(member).length) {
                tasks.push({
                    key: 'data-quality',
                    label: this.$t('members.taskCheckDataQuality'),
                    action: 'open-editor',
                    actionLabel: this.$t('members.taskActionReview')
                });
            }
            if (!this.getMemberTrainingGroups(member).length) {
                tasks.push({
                    key: 'training-group',
                    label: this.$t('members.taskAssignTrainingGroup'),
                    action: 'open-editor',
                    actionLabel: this.$t('members.taskActionReview')
                });
            }
            if (member.active && !member.ttr && !member.qttr && !member.clickTtApplicationSubmitted) {
                tasks.push({
                    key: 'clicktt',
                    label: this.$t('members.taskCheckClickTt'),
                    action: 'request-clicktt',
                    actionLabel: this.$t('members.taskActionRequest')
                });
            }
            return tasks;
        },
        async runPreviewTask(action, member) {
            if (!action || !member) return;
            if (action === 'verify-form') {
                await this.quickMarkFormHandedOver(member);
                return;
            }
            if (action === 'mark-regular') {
                await this.quickRemoveTestMembership(member);
                return;
            }
            if (action === 'request-clicktt') {
                await this.requestClickTtRegistration(member);
                return;
            }
            if (action === 'open-editor') {
                this.editMember(member);
            }
        },
        getMemberTrainingGroups(member) {
            if (!member) return [];
            return Array.isArray(member.trainingGroups) ? member.trainingGroups : [];
        },
        editorHasIssue(issueKey) {
            return this.editorDataQualityIssues.some(issue => issue.key === issueKey);
        },
        getPreviewTrainingGroups(member) {
            if (this.selectedMemberPreview && member && this.selectedMemberPreview.id === member.id && this.selectedPreviewTrainingGroups.length) {
                return this.selectedPreviewTrainingGroups;
            }
            return this.getMemberTrainingGroups(member);
        },
        openTtrHistoryDialog(member) {
            if (!member) {
                return;
            }
            this.selectedMemberForTtrHistory = member;
            this.showMemberTtrHistoryDialog = true;
        },
        closeTtrHistoryDialog() {
            this.showMemberTtrHistoryDialog = false;
            this.selectedMemberForTtrHistory = null;
        },
        openOrdersDialog(member) {
            if (!member) {
                return;
            }
            this.selectedMemberForOrders = member;
            this.showMemberOrdersDialog = true;
        },
        closeOrdersDialog() {
            this.showMemberOrdersDialog = false;
            this.selectedMemberForOrders = null;
        },
        async updateRatingsFromMyTischtennis() {
            this.isUpdatingRatings = true;
            try {
                const response = await apiClient.post(`/clubmembers/update-ratings/${this.currentClub}`);
                
                if (response.data.message) {
                    await this.showInfo(this.$t('messages.note'), response.data.message, '', 'info');
                }
                
                // Mitglieder neu laden um aktualisierte Werte anzuzeigen
                await this.loadMembers();
            } catch (error) {
                console.error(this.$t('members.errorUpdatingRatings'), error);
                const message = getSafeErrorMessage(error, this.$t('members.errorUpdatingRatings'));
                this.showInfo(this.$t('messages.error'), message, '', 'error');
            } finally {
                this.isUpdatingRatings = false;
            }
        },
        
        getAge(birthDate) {
            if (!birthDate) return null;
            
            const birth = new Date(birthDate);
            if (isNaN(birth.getTime())) return null;
            
            const today = new Date();
            const age = today.getFullYear() - birth.getFullYear();
            const monthDiff = today.getMonth() - birth.getMonth();
            
            // Korrektur für Geburtstag in diesem Jahr
            return monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate()) 
                ? age - 1 
                : age;
        },
        
        hasValidLastTrainingDate(member) {
            if (!member?.lastTraining) return false;
            const d = new Date(member.lastTraining);
            return !Number.isNaN(d.getTime());
        },
        memberRowHoverTooltip(member) {
            const akFull = this.formatMemberTtAgeClassLines(member).join(` ${this.$t('members.rowTooltipSeparator')} `);
            const dateLabel = this.$t('members.previewLastTraining');
            const dateVal = this.getOptionalFormattedDate(member.lastTraining, 'members.previewNoLastTraining');
            const parts = [`${this.$t('members.ttAgeClassCol')}: ${akFull}`, `${dateLabel}: ${dateVal}`];
            if (member.notInTraining) {
                parts.push(this.$t('members.notInTrainingTooltip', { weeks: member.missedTrainingWeeks || 0 }));
            }
            const tp = member.trainingParticipations;
            if (tp != null && !Number.isNaN(Number(tp))) {
                parts.push(`${this.$t('members.trainingParticipations')}: ${tp}`);
            }
            return parts.join(` ${this.$t('members.rowTooltipSeparator')} `);
        },
        formatMemberTtAgeClassLines(member) {
            const y0 = getSeasonStartYearFromDate();
            return [y0, y0 + 1].map((seasonYear) => {
                const { primary, secondary } = formatMemberTtAgeClassLabels(member, seasonYear);
                const prefix = formatSeasonSlash(seasonYear);
                if (primary === null) {
                    return `${prefix}: –`;
                }
                if (primary === 'adult') {
                    return `${prefix}: ${this.$t('members.ttAdult')}`;
                }
                const body = secondary ? `${primary} · ${secondary}` : primary;
                return `${prefix}: ${body}`;
            });
        },
        formatMemberTtAgeClassCsv(member) {
            return this.formatMemberTtAgeClassLines(member).join(' | ');
        },
        trainingParticipationsDisplay(member) {
            const n = member.trainingParticipations;
            if (n == null || Number.isNaN(Number(n))) {
                return '–';
            }
            return String(n);
        },
        setShowTrainingParticipationsColumn(value) {
            this.showTrainingParticipationsColumn = !!value;
            safeLocalStorage.setItem('membersShowTrainingParticipationsColumn', this.showTrainingParticipationsColumn ? '1' : '0');
        },
        
        clearFilters() {
            this.selectedAgeGroup = '';
            this.selectedSeasonStartYear = getSeasonStartYearFromDate();
            this.selectedAgeFrom = '';
            this.selectedAgeTo = '';
            this.selectedGender = '';
            this.selectedTrainingGroup = '';
            this.selectedLastTrainingFilter = '';
            this.showInactiveMembers = false;
            this.searchQuery = '';
            this.selectedMemberScope = 'active';
            this.selectedSort = 'lastName';
            this.sortDirection = 'asc';
        },
        toggleSortDirection() {
            this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
        },
        
        openTransferDialog() {
            this.showTransferDialog = true;
        },
        
        handleTransferSuccess(event) {
            const { message, details, results } = event;
            this.showInfo(
                this.$t('members.transferSuccessTitle'),
                message,
                details,
                'success'
            );
        },
        
        handleTransferError(event) {
            const { message, error } = event;
            this.showInfo(
                this.$t('members.transferErrorTitle'),
                message,
                error,
                'error'
            );
        }
    }
}
</script>

<style scoped>
.members-page {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

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

.members-subtitle {
    margin: 0.35rem 0 0;
    color: #5f6b76;
}

.members-stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 0.9rem;
}

.members-stat-card {
    padding: 0.95rem 1rem;
    border: 1px solid #d7dee6;
    border-radius: 12px;
    background: linear-gradient(180deg, #ffffff, #f8fafc);
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.members-stat-card-accent {
    border-color: #9ec5fe;
    background: linear-gradient(180deg, #f8fbff, #eef5ff);
}

.members-stat-label {
    color: #5f6b76;
    font-size: 0.9rem;
}

.members-stat-value {
    font-size: 1.5rem;
    line-height: 1;
    color: #16324f;
}

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

thead>tr>th {
    border-bottom: 1px solid #000;
}

table th,
table td {
    padding-right: 2em;
}

table td {
    cursor: pointer;
    vertical-align: top;
    padding-top: 0.8rem;
    padding-bottom: 0.8rem;
}

.toggle-new-member {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    align-items: flex-start;
    font-weight: bold;
    margin-bottom: 1rem;
}

.add {
    border: 1px solid #999;
    margin: 0 6px 4px 0;
    border-radius: 50%;
    height: 15px;
    width: 15px;
    display: inline-block;
    line-height: 15px;
    text-align: center;
    box-shadow: 2px 2px 2px #bbb;
}

.newmember {
    border: 1px solid #d7dee6;
    border-radius: 14px;
    padding: 1rem;
    background: #fff;
}

.new-member-form {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

.new-member-form > label {
    display: block;
    padding: 0.35rem 0;
    border: 1px solid transparent;
    border-radius: 10px;
}

.new-member-form>label>span {
    width: 10em;
    display: inline-block;
}

.new-member-form > label.member-field-warning {
    padding: 0.7rem 0.85rem;
    border-color: #f1d49a;
    background: #fffaf0;
}

.member-field-warning > span {
    color: #8a5a00;
    font-weight: 600;
}

.input-warning {
    border-color: #e1a43a !important;
    background: #fffaf0 !important;
    box-shadow: none;
}

.member-field-warning-box {
    position: relative;
    padding: 1rem;
    border: 1px solid #e1a43a;
    background: linear-gradient(180deg, #fff7e6 0%, #fff1d8 100%);
    box-shadow: inset 0 0 0 1px rgba(225, 164, 58, 0.12);
}

.member-field-warning-box > label {
    color: #8a5a00;
    font-weight: 600;
}

.member-field-warning-box .member-editor-field-hint {
    margin-left: 0;
}

.member-field-warning-box .member-editor-frame-hint {
    position: absolute;
    top: 0;
    left: 0.85rem;
    display: inline-block;
    margin: 0;
    padding: 0 0.45rem;
    transform: translateY(-50%);
    background: #fffdf8;
    color: #8a5a00;
    font-size: 0.82rem;
    line-height: 1.2;
}

.member-field-warning-box .contact-item,
.member-field-warning-box .member-groups-list,
.member-field-warning-box .no-groups-hint,
.member-field-warning-box .group-select,
.member-field-warning-box .checkbox-inline,
.member-field-warning-box .btn-add-contact {
    background: rgba(225, 164, 58, 0.12);
    border-radius: 8px;
}

.member-field-warning-box .contact-item,
.member-field-warning-box .no-groups-hint,
.member-field-warning-box .checkbox-inline {
    padding: 0.45rem 0.55rem;
}

.member-field-warning-box .member-groups-list {
    padding: 0.55rem;
}

.member-field-warning-box .contact-input,
.member-field-warning-box .parent-name-input,
.member-field-warning-box .group-select {
    border-color: #e1a43a;
    background: #fffaf0;
}

.member-field-warning-box .checkbox-inline {
    border: 1px solid rgba(225, 164, 58, 0.2);
}

.member-field-warning-box .btn-add-contact {
    border-color: rgba(225, 164, 58, 0.35);
    background: #f8e2b7;
    color: #704600;
}

.member-field-warning-box .btn-add-contact:hover {
    background: #f3d79f;
}

.member-editor-field-hint {
    display: block;
    margin-top: 0.35rem;
    margin-left: 10em;
    color: #8a5a00;
    font-size: 0.88rem;
}

.modal {
    display: flex;
    justify-content: center;
    align-items: center;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0, 0, 0, 0.8);
}

.modal-content {
    background-color: #fefefe;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 800px;
    max-height: 80%;
    box-shadow: 4px 3px 2px #999;
    position: relative;
}

.close {
    position: absolute;
    top: 10px;
    right: 15px;
    color: #aaa;
    font-size: 28px;
    font-weight: bold;
}

.close:hover,
.close:focus {
    color: #000;
    text-decoration: none;
    cursor: pointer;
}

.badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.85rem;
    border: 1px solid #ddd;
    background: #f5f5f5;
}
.gender-symbol, .gender-name { background: transparent; border: none; }

.gender-name.gender-male { color: #1a73e8; }
.gender-name.gender-female { color: #d81b60; }
.gender-name.gender-diverse { color: #6a1b9a; }
.gender-name.gender-unknown { color: #444; }
.gender-symbol.gender-male { color: #1a73e8; }
.gender-symbol.gender-female { color: #d81b60; }
.gender-symbol.gender-diverse { color: #6a1b9a; }
.gender-symbol.gender-unknown { color: #444; }
.gender-symbol { margin-right: .35rem; opacity: .9; font-size: 1.05em; display: inline-block; width: 1.1em; text-align: center; }
.row-inactive { opacity: .6; }
.row-selected { outline: 2px solid #dbeafe; background: #f8fbff; }
.is-inactive { text-decoration: line-through; }
.inactive-badge { margin-left: .5rem; font-size: .85em; color: #666; text-transform: lowercase; }
.row-test { background-color: #fff3cd; }
.row-test-form { background-color: #ffffcc; }

.rating-cell {
    font-family: 'Courier New', monospace;
    font-size: 0.95em;
}

.rating-button {
    border: none;
    background: transparent;
    padding: 0;
    font: inherit;
    cursor: pointer;
    color: inherit;
}

.rating-button:disabled {
    cursor: default;
}

.rating-button:not(:disabled):hover .ttr-value,
.rating-button:not(:disabled):hover .qttr-value,
.rating-button:not(:disabled):hover .no-rating {
    text-decoration: underline;
}

.ttr-value {
    font-weight: 600;
    color: #1a73e8;
}

.qttr-value {
    font-weight: 600;
    color: #d81b60;
}

.rating-separator {
    color: #999;
    margin: 0 0.25rem;
}

.no-rating {
    color: #999;
}

.action-buttons {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

.info-text {
    font-size: 0.9em;
    color: #666;
}

.btn-update-ratings {
    background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
    color: var(--text-on-primary);
    border: 1px solid transparent;
    padding: 0.5rem 1rem;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.9em;
    font-weight: 600;
    transition: transform 0.2s ease, opacity 0.2s ease;
}

.btn-update-ratings:hover:not(:disabled) {
    transform: translateY(-1px);
    opacity: 0.95;
}

.btn-update-ratings:disabled {
    background-color: #6c757d;
    cursor: not-allowed;
    opacity: 0.6;
}

/* Image Modal Styles */
.image-modal-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
}

.image-container {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 1rem;
    padding: 1rem;
}

.modal-image {
    max-width: 400px;
    max-height: 400px;
    object-fit: contain;
    flex-shrink: 0;
    transition: transform 0.3s ease;
}

.image-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.rotate-btn {
    background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
    color: var(--text-on-primary);
    border: 1px solid transparent;
    padding: 0.75rem 1rem;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.9em;
    font-weight: 600;
    transition: all 0.2s ease;
    min-width: 120px;
}

.rotate-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 18px rgba(47, 122, 95, 0.18);
}

.rotate-btn:active {
    transform: translateY(0);
}

/* Dialog-spezifische Styles */
.member-notes-content {
    min-height: 200px;
}

.btn-primary {
    padding: 0.5rem 1rem;
    border: 1px solid transparent;
    border-radius: 10px;
    background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
    color: var(--text-on-primary);
    font-weight: 600;
    cursor: pointer;
    transition: opacity 0.2s, transform 0.2s ease;
}

.btn-primary:hover {
    opacity: 0.95;
    transform: translateY(-1px);
}

/* Dropdown Styles */
.dropdown-container {
    position: relative;
    display: inline-block;
}

.btn-dropdown {
    background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
    color: var(--text-on-primary);
    border: 1px solid transparent;
    padding: 0.5rem 1rem;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.9em;
    font-weight: 600;
    transition: transform 0.2s ease, opacity 0.2s ease;
}

.btn-dropdown:hover {
    opacity: 0.95;
    transform: translateY(-1px);
}

.dropdown-content {
    position: absolute;
    top: 100%;
    left: 0;
    background: var(--surface-color);
    border: 1px solid var(--border-color);
    border-radius: 10px;
    box-shadow: 0 10px 24px rgba(15, 23, 42, 0.14);
    z-index: 1000;
    min-width: 200px;
    margin-top: 4px;
}

.member-stats {
    padding: 1rem;
}

.stat-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.5rem;
}

.stat-item:last-child {
    margin-bottom: 0;
}

.stat-label {
    font-weight: 500;
    color: #333;
}

.stat-value {
    font-weight: 600;
    color: #007bff;
    background-color: #f8f9fa;
    padding: 0.2rem 0.5rem;
    border-radius: 3px;
    min-width: 2rem;
    text-align: center;
}

/* Button Styles */
.btn-activities {
    margin-left: 0.5rem;
    background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
    color: var(--text-on-primary);
    border: 1px solid transparent;
    padding: 0.5rem 1rem;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.9em;
    font-weight: 600;
    transition: transform 0.2s ease, opacity 0.2s ease;
}

.btn-activities:hover {
    transform: translateY(-1px);
    opacity: 0.95;
}

/* Filter Styles */
.filters-section {
    padding: 1rem;
    background-color: #f8f9fa;
    border-radius: 12px;
    border: 1px solid #e0e6eb;
}

.members-filter-topline {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 1rem;
    flex-wrap: wrap;
}

.member-search-group {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: min(100%, 420px);
    flex: 1 1 320px;
}

.member-search-group label {
    font-weight: 600;
    color: #243444;
}

.member-search-input {
    padding: 0.75rem 0.9rem;
    border: 1px solid #c6d0da;
    border-radius: 10px;
    background: #fff;
    font-size: 0.95rem;
}

.members-scope-buttons {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.scope-chip {
    border: 1px solid #cfd8e3;
    background: #fff;
    color: #32475b;
    border-radius: 999px;
    padding: 0.45rem 0.8rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

.scope-chip-active {
    border-color: #0d6efd;
    color: #0d4da3;
    background: #eaf2ff;
}

.scope-chip-count {
    font-size: 0.82rem;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    background: rgba(13, 110, 253, 0.1);
}

.filter-controls {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    margin-top: 1rem;
    flex-wrap: wrap;
}

.filter-group {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.filter-group label {
    font-weight: 500;
    color: #333;
    white-space: nowrap;
}

.filter-group .checkbox-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 500;
    color: #333;
    white-space: nowrap;
    cursor: pointer;
}

.filter-group .checkbox-item input[type="checkbox"] {
    margin: 0;
}

.filter-select {
    padding: 0.5rem 0.75rem;
    border: 1px solid #ced4da;
    border-radius: 4px;
    background-color: white;
    font-size: 0.9em;
    min-width: 120px;
    cursor: pointer;
}

.filter-select:focus {
    outline: none;
    border-color: #007bff;
    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.btn-clear-filters {
    background-color: #6c757d;
    color: white;
    border: none;
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9em;
    transition: background-color 0.2s ease;
}

.btn-clear-filters:hover {
    background-color: #5a6268;
}

.btn-transfer {
    background-color: #17a2b8;
    color: white;
    border: none;
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9em;
    transition: background-color 0.2s ease;
}

.btn-transfer:hover {
    background-color: #138496;
}

.members-results-bar {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    align-items: center;
    flex-wrap: wrap;
    color: #4b5d6d;
}

.members-results-copy strong {
    color: #16324f;
}

.members-results-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.members-sort-group {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: #4b5d6d;
}

.members-export-preview {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.75rem;
}

.members-export-card {
    border: 1px solid #d7dee6;
    border-radius: 12px;
    background: #fff;
    padding: 0.9rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.members-export-card-wide {
    grid-column: span 2;
}

.members-export-label {
    color: #66788a;
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.members-export-value {
    color: #102a43;
    font-size: 1.35rem;
    line-height: 1.1;
}

.members-export-copy {
    color: #4b5d6d;
}

.members-export-actions {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.35rem;
    flex-wrap: wrap;
}

.members-state-banner {
    padding: 0.85rem 1rem;
    border-radius: 10px;
    border: 1px solid;
}

.members-state-banner-info {
    background: #eef6ff;
    border-color: #bdd6ff;
    color: #12427a;
}

.members-state-banner-error {
    background: #fff1f0;
    border-color: #f5b8b1;
    color: #8b1e1e;
}

.members-contribution-summary {
    padding: 1rem 1.1rem;
    margin-bottom: 1rem;
}

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

.members-summary-card {
    border: 1px solid #dce5ee;
    border-radius: 12px;
    padding: 0.85rem 0.95rem;
    background: linear-gradient(180deg, #ffffff, #f6f8fb);
}

.members-summary-label,
.members-summary-note,
.member-contribution-muted {
    color: #66788a;
}

.members-summary-value {
    display: block;
    margin-top: 0.25rem;
    font-size: 1.35rem;
    color: #102a43;
}

.members-summary-note {
    margin: 0.75rem 0 0;
    font-size: 0.92rem;
}

.member-preview-panel {
    border: 1px solid #d7dee6;
    border-radius: 14px;
    background: linear-gradient(180deg, #ffffff, #f8fafc);
    padding: 1rem;
}

.member-preview-header {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    align-items: flex-start;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.member-preview-label {
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #66788a;
}

.member-preview-name {
    margin: 0.2rem 0 0;
    color: #102a43;
}

.member-preview-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.member-preview-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.9rem;
}

.member-contribution-cell {
    display: grid;
    gap: 0.2rem;
}

.member-contribution-line {
    line-height: 1.25;
}

.member-contribution-presets {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin-top: 0.45rem;
}

.member-contribution-preset {
    padding: 0.45rem 0.7rem;
}

.member-preview-item {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.member-preview-item-label {
    font-size: 0.85rem;
    color: #66788a;
}

.member-preview-muted {
    color: #66788a;
    font-size: 0.88rem;
}

.member-preview-tags,
.member-preview-tasklist {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.member-preview-tag,
.member-preview-task {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.55rem;
    border-radius: 999px;
    font-size: 0.82rem;
}

.member-preview-tag {
    background: #eef6ff;
    color: #12427a;
    border: 1px solid #bdd6ff;
}

.member-preview-task {
    background: #fff7e8;
    color: #8a5a00;
    border: 1px solid #f1d49a;
    gap: 0.45rem;
}

.member-preview-task-action {
    border: 1px solid #d7dee6;
    background: #fff;
    color: #243444;
    border-radius: 999px;
    padding: 0.15rem 0.55rem;
    cursor: pointer;
    font-size: 0.78rem;
}

.member-editor-heading {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
}

.member-editor-title {
    font-size: 1rem;
    color: #102a43;
}

.member-editor-subtitle {
    margin-top: 0.15rem;
    font-size: 0.92rem;
    color: #5f6b76;
    font-weight: 400;
}

.members-table-panel {
    border: 1px solid #d7dee6;
    border-radius: 14px;
    background: #fff;
    overflow: hidden;
}

.members-table-wrap {
    overflow-x: auto;
    padding: 0.5rem 1rem 1rem;
}

.tt-age-class-cell {
    font-size: 0.88rem;
    line-height: 1.35;
    vertical-align: top;
    max-width: 11rem;
}

.tt-age-class-line + .tt-age-class-line {
    margin-top: 0.2rem;
    white-space: nowrap;
}

.tt-age-class-line--next {
    opacity: 0.88;
    font-size: 0.92em;
    white-space: nowrap;
}

.last-training-cell {
    max-width: 9rem;
    vertical-align: top;
}

.last-training-compact {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.training-participations-cell {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.action-icons-row {
    display: flex;
    gap: 0.75rem;
    flex-wrap: nowrap;
    align-items: center;
    white-space: nowrap;
}

.action-icon {
    font-size: 1.2em;
    cursor: pointer;
    transition: transform 0.2s ease, opacity 0.2s ease;
    display: inline-block;
    line-height: 1;
}

.action-icon:hover {
    transform: scale(1.2);
    opacity: 0.8;
}

.action-icon-disabled {
    opacity: 0.5;
    cursor: wait;
    pointer-events: none;
}

.action-icon-disabled:hover {
    transform: none;
    opacity: 0.5;
}

.action-icon-deactivate {
    filter: grayscale(0.3);
}

.action-icon-deactivate:hover {
    filter: grayscale(0);
}

.member-actions-cell {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    min-width: 170px;
}

.member-actions-primary,
.member-actions-secondary {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.member-action-button {
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 0.35rem 0.6rem;
    font-size: 0.82rem;
    line-height: 1;
    min-height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
    cursor: pointer;
    background: #f3f4f6;
    color: #243444;
}

.member-action-button:disabled {
    opacity: 0.6;
    cursor: wait;
}

.member-action-button-success {
    background: #edf8f0;
    color: #216e39;
    border-color: #cbe7d3;
}

.member-action-button-warning {
    background: #fff7e8;
    color: #8a5a00;
    border-color: #f1d49a;
}

.member-action-button-info {
    background: #eef6ff;
    color: #12427a;
    border-color: #bdd6ff;
    min-width: 42px;
}

.member-icon-button {
    border: 1px solid #d7dee6;
    background: #fff;
    color: #243444;
    border-radius: 8px;
    width: 34px;
    height: 34px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.member-icon-button:hover {
    background: #f8fafc;
}

.member-icon-button-edit {
    color: #0d4da3;
    border-color: #bdd6ff;
    background: #eef6ff;
}

.member-icon-button-info {
    color: #12427a;
    border-color: #bdd6ff;
    background: #eef6ff;
}

.member-icon-button-success {
    color: #216e39;
    border-color: #cbe7d3;
    background: #edf8f0;
}

.member-icon-button-danger {
    border-color: #f0b7af;
    background: #fff1f0;
}

.contact-section {
    margin: 1rem 0;
    padding: 1rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: #f9f9f9;
}

.contact-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    flex-wrap: wrap;
}

.contact-input {
    flex: 1;
    min-width: 200px;
    padding: 0.4rem;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.parent-name-input {
    flex: 0 0 150px;
    padding: 0.4rem;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.checkbox-inline {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
    font-size: 0.9em;
}

.btn-add-contact {
    margin-top: 0.5rem;
    padding: 0.4rem 0.8rem;
    background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
    color: var(--text-on-primary);
    border: 1px solid transparent;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.9em;
    font-weight: 600;
}

.btn-add-contact:hover {
    opacity: 0.95;
}

.btn-remove-contact {
    padding: 0.2rem 0.5rem;
    background: rgba(200, 74, 56, 0.12);
    color: #8b3327;
    border: 1px solid rgba(200, 74, 56, 0.24);
    border-radius: 8px;
    cursor: pointer;
    font-size: 0.9em;
}

.btn-remove-contact:hover {
    background: rgba(200, 74, 56, 0.18);
}

.member-groups-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.member-group-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem 0.5rem;
    background: #e3f2fd;
    border-radius: 4px;
    font-size: 0.9rem;
}

.remove-group-btn {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 1.2rem;
    line-height: 1;
    color: #d32f2f;
    padding: 0;
    margin-left: 0.25rem;
}

.remove-group-btn:hover {
    opacity: 0.7;
}

.group-select {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    margin-top: 0.5rem;
}

.no-groups-hint {
    color: #666;
    font-style: italic;
    margin-bottom: 0.5rem;
}

.member-sepa-section {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.member-sepa-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.75rem 1rem;
}

.member-sepa-grid > label,
.member-sepa-grid-full {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.member-sepa-grid-full {
    grid-column: 1 / -1;
}

.member-sepa-grid input,
.member-sepa-grid select,
.member-sepa-grid textarea {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    font: inherit;
}

.member-sepa-grid textarea {
    resize: vertical;
    min-height: 5rem;
}

.member-sepa-actions {
    display: flex;
    justify-content: flex-end;
}

.warning-icon {
    margin-right: 0.25rem;
    font-size: 0.8rem;
}

.warning-icon-severe {
    font-size: 0.8rem;
    animation: pulse-warning 2s infinite;
}

.member-name-cell {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.member-name-line {
    display: flex;
    align-items: center;
}

.member-name-meta {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
}

.member-name-flags {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
    margin-top: 0.18rem;
}

.member-eligibility-flag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.1rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    background: #eef6ff;
    color: #12427a;
    border: 1px solid #bfd7f3;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1.1;
}

.member-meta-text {
    color: #66788a;
    font-size: 0.88rem;
}

.member-status-stack {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    max-width: 220px;
}

.member-status-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    font-size: 0.82rem;
    border: 1px solid transparent;
    white-space: nowrap;
}

.member-status-badge-positive {
    background: #edf8f0;
    color: #216e39;
    border-color: #cbe7d3;
}

.member-status-badge-warning {
    background: #fff7e8;
    color: #8a5a00;
    border-color: #f1d49a;
}

.member-status-badge-danger {
    background: #fff1f0;
    color: #8b1e1e;
    border-color: #f0b7af;
}

.member-status-badge-muted {
    background: #f2f4f7;
    color: #5f6b76;
    border-color: #d7dee6;
}

.member-status-badge-info {
    background: #eef6ff;
    color: #12427a;
    border-color: #bdd6ff;
}

.member-contact-cell {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 220px;
}

.member-contact-line {
    color: #1f2d3d;
}

.member-contact-meta {
    color: #66788a;
    font-size: 0.88rem;
}

.member-clicktt-section {
    border: 1px solid #d7dee6;
    border-radius: 10px;
    background: #f8fbfd;
}

.member-clicktt-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.member-clicktt-button {
    align-self: flex-start;
}

.member-clicktt-hint,
.member-clicktt-status {
    color: #51606f;
    font-size: 0.9rem;
}

.member-clicktt-status {
    color: #12427a;
    font-weight: 600;
}

@keyframes pulse-warning {
    0%, 100% {
        opacity: 1;
        transform: scale(1);
    }
    50% {
        opacity: 0.7;
        transform: scale(1.1);
    }
}

        .member-image-gallery {
            display: flex;
            flex-wrap: wrap;
            gap: 0.3rem;
            margin-top: 0.4rem;
        }

        .member-image-thumb-small {
            max-width: 48px;
            max-height: 48px;
            border-radius: 4px;
            overflow: hidden;
            border: 1px solid var(--border-color, #ddd);
            background: #f5f5f5;
            display: flex;
            justify-content: center;
            align-items: center;
        }

@media (max-width: 900px) {
    .toggle-new-member,
    .members-header,
    .members-results-bar,
    .members-filter-topline {
        flex-direction: column;
        align-items: stretch;
    }

    .member-preview-header,
    .member-preview-actions,
    .member-actions-primary,
    .member-actions-secondary {
        width: 100%;
    }

    .member-preview-actions > button,
    .member-actions-primary > *,
    .member-actions-secondary > * {
        flex: 1 1 12rem;
    }

    .new-member-form > label {
        padding: 0.45rem 0;
    }

    .new-member-form > label > span {
        width: 100%;
        display: block;
        margin-bottom: 0.35rem;
    }

    .member-editor-field-hint {
        margin-left: 0;
    }

    .contact-item {
        align-items: stretch;
    }

    .contact-input,
    .parent-name-input,
    .group-select {
        width: 100%;
        min-width: 0;
        flex: 1 1 100%;
    }

    .checkbox-inline {
        width: 100%;
    }

    .members-table-wrap {
        padding: 0.35rem 0.5rem 0.75rem;
    }
}

@media (max-width: 640px) {
    .member-preview-panel,
    .newmember,
    .contact-section {
        padding-left: 0.75rem;
        padding-right: 0.75rem;
    }

    .member-preview-grid {
        grid-template-columns: 1fr;
    }

    .member-preview-actions > button,
    .member-actions-primary > *,
    .member-actions-secondary > *,
    .btn-add-contact,
    .toggle-new-member > button {
        width: 100%;
        flex: 1 1 100%;
    }

    .member-actions-cell {
        min-width: 12rem;
    }

    .action-icons-row {
        flex-wrap: wrap;
        white-space: normal;
    }

    .member-image-thumb-small,
    .member-image-thumb__placeholder {
        width: 42px;
        height: 42px;
    }
}
</style>
