import { computed, onMounted, ref } from 'vue' import { CheckCircle2, Database, FileText, Link2, ShieldCheck, Tags, UserRound } from '@lucide/vue' import { getRiskMetricValue } from '../../lib/adminMetrics' import { useAwardsStore } from '../../stores/awards' import type { AdminSettingsCheckItem, AdminSettingsGateItem } from './adminSettingsTypes' export function useAdminSettingsOverview() { const store = useAwardsStore() const healthLoading = ref(false) const healthError = ref('') const healthLoadedAt = ref(null) const seasonDetail = computed(() => store.adminSeasonDetail) const databaseHealth = computed(() => store.databaseHealth) const hasVotingPhase = computed(() => seasonDetail.value.currentPhase.toLowerCase().includes('voting')) const categoriesWithoutCandidates = computed(() => seasonDetail.value.categories.filter((category) => !seasonDetail.value.candidates.some((candidate) => candidate.categoryId === category.id), ), ) const pendingClips = computed(() => seasonDetail.value.clipSubmissions.filter((clip) => clip.status === 'pending').length) const pendingMigrationCount = computed(() => databaseHealth.value.pendingMigrations.length) const openRiskCount = computed(() => getRiskMetricValue(store.admin.metrics)) const healthLoadedLabel = computed(() => healthLoadedAt.value ? healthLoadedAt.value.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) : 'Noch nicht aktualisiert', ) const siteSettings = computed(() => store.adminSiteSettings) const configuredFooterLinks = computed(() => [ siteSettings.value.imprintUrl, siteSettings.value.contactUrl, siteSettings.value.sponsorsUrl, siteSettings.value.newsletterUrl, ].filter((url) => url.trim()).length) const configuredFooterPages = computed(() => [ siteSettings.value.imprintContent, siteSettings.value.contactContent, siteSettings.value.sponsorsContent, ].filter((content) => content.trim()).length) const contentChecks = computed(() => [ { label: 'Host', value: Boolean(siteSettings.value.hostDisplayName.trim() && siteSettings.value.hostTagline.trim()), note: siteSettings.value.hostDisplayName.trim() ? siteSettings.value.hostTagline.trim() ? siteSettings.value.hostDisplayName.trim() : 'Host-Tagline fehlt' : 'Kein Host-Name hinterlegt', icon: UserRound, to: '/admin/content', }, { label: 'Social Links', value: siteSettings.value.socialLinks.length > 0, note: `${siteSettings.value.socialLinks.length} Links gespeichert`, icon: Link2, to: '/admin/content', }, { label: 'FAQ', value: siteSettings.value.faq.length > 0, note: `${siteSettings.value.faq.length} Fragen gespeichert`, icon: FileText, to: '/admin/content', }, { label: 'Footer Links', value: configuredFooterLinks.value === 4 && configuredFooterPages.value === 3, note: `${configuredFooterLinks.value} von 4 Link-Zielen, ${configuredFooterPages.value} von 3 Footer-Seiten gepflegt`, icon: Link2, to: '/admin/content', }, { label: 'Datenschutz', value: Boolean(siteSettings.value.privacyPolicyContent.trim() && siteSettings.value.privacyEmail.trim()), note: siteSettings.value.privacyPolicyUpdatedAt ? `Zuletzt aktualisiert: ${new Date(siteSettings.value.privacyPolicyUpdatedAt).toLocaleString('de-DE')}` : 'Datenschutztext oder Datenschutz-Mail fehlt', icon: ShieldCheck, to: '/admin/content', }, ]) const contentCompletion = computed(() => contentChecks.value.filter((check) => check.value).length) const checks = computed(() => [ { label: 'Backend verbunden', value: store.apiMode === 'api', note: store.apiMode === 'api' ? 'Admin-Daten kommen aus der API.' : 'Fallback-Daten aktiv oder API nicht erreichbar.', icon: Database, to: null, }, { label: 'Postgres verbunden', value: databaseHealth.value.canConnect, note: databaseHealth.value.canConnect ? `${databaseHealth.value.provider} erreichbar · Quelle: ${databaseHealth.value.configuredConnection.source}.` : databaseHealth.value.error || 'Datenbank ist nicht erreichbar.', icon: Database, to: null, }, { label: 'Migrationen aktuell', value: databaseHealth.value.canConnect && pendingMigrationCount.value === 0, note: pendingMigrationCount.value === 0 ? 'Keine ausstehenden Migrationen.' : `${pendingMigrationCount.value} Migrationen warten auf Anwendung.`, icon: CheckCircle2, to: null, }, { label: 'Public-Jahr gesetzt', value: seasonDetail.value.isCurrent, note: seasonDetail.value.isCurrent ? `${seasonDetail.value.year} ist öffentlich markiert.` : 'Das gewählte Jahr ist aktuell intern.', icon: CheckCircle2, to: '/admin/years', }, { label: 'Voting-Basis vollständig', value: categoriesWithoutCandidates.value.length === 0 && seasonDetail.value.categories.length > 0, note: categoriesWithoutCandidates.value.length === 0 ? 'Alle Kategorien haben Kandidaten.' : `${categoriesWithoutCandidates.value.length} Kategorien brauchen Kandidaten.`, icon: Tags, to: '/admin/categories', }, { label: 'Risiko-Queue leer', value: openRiskCount.value === 0, note: `${openRiskCount.value} offene Risikohinweise im Admin-Kontext.`, icon: ShieldCheck, to: '/admin/risk', }, ]) const gates = computed(() => [ { label: 'Nominierungsfenster', state: seasonDetail.value.currentPhase.toLowerCase().includes('nomin'), note: seasonDetail.value.currentPhase.toLowerCase().includes('nomin') ? 'Öffentliche Nominierung ist aktiv.' : 'Die aktuelle Phase erlaubt keine neuen Nominierungen.', to: '/admin/years', }, { label: 'Voting freigeschaltet', state: hasVotingPhase.value && categoriesWithoutCandidates.value.length === 0, note: hasVotingPhase.value ? 'Phase ist Voting; Kategorie-Readiness entscheidet.' : 'Phase ist nicht Voting.', to: '/admin/categories', }, { label: 'Clip-Reviews offen', state: pendingClips.value > 0, note: pendingClips.value > 0 ? `${pendingClips.value} Clip-Einreichungen offen.` : 'Keine offenen Clip-Einreichungen.', to: '/admin/clips', }, { label: 'Freitext-Reviews geklärt', state: seasonDetail.value.pendingNominations.length === 0, note: `${seasonDetail.value.pendingNominations.length} offene Freitext-Reviews.`, to: '/admin/nominations?review=1', }, ]) async function refreshDatabaseHealth() { healthLoading.value = true healthError.value = '' try { await store.loadDatabaseHealth() healthLoadedAt.value = new Date() } catch (error) { healthError.value = error instanceof Error ? error.message : 'Datenbank-Healthcheck konnte nicht geladen werden.' } finally { healthLoading.value = false } } onMounted(refreshDatabaseHealth) return { healthLoading, healthError, databaseHealth, pendingMigrationCount, healthLoadedLabel, contentChecks, contentCompletion, checks, gates, refreshDatabaseHealth, } }