Extract season overview/workflow cards and add sponsor seed data
Splits AdminSeasonsView into AdminSeasonOverviewCard and AdminSeasonWorkflowCard for cleaner separation of concerns. Adds SeedSponsorsBootstrapper with demo sponsor SVG assets and extends SeedCatalog with sponsor seed entries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,33 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
AlertTriangle,
|
||||
CalendarCog,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
ExternalLink,
|
||||
Globe2,
|
||||
History,
|
||||
LockKeyhole,
|
||||
Pencil,
|
||||
PlusCircle,
|
||||
Trash2,
|
||||
WandSparkles,
|
||||
X,
|
||||
} from '@lucide/vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
import AdminSeasonCreateModal from '../../components/admin/AdminSeasonCreateModal.vue'
|
||||
import AdminSeasonDeleteModal from '../../components/admin/AdminSeasonDeleteModal.vue'
|
||||
import AdminSeasonOverviewCard from '../../components/admin/AdminSeasonOverviewCard.vue'
|
||||
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
||||
import {
|
||||
createSeasonTimelineRows,
|
||||
normalizePhaseKey,
|
||||
resolveAutoPhase,
|
||||
SEASON_PHASES,
|
||||
type PhaseKey,
|
||||
type PhaseRowConfig,
|
||||
} from '../../components/admin/adminSeasonTimeline'
|
||||
import AdminSeasonWorkflowCard from '../../components/admin/AdminSeasonWorkflowCard.vue'
|
||||
import { useAdminSeasonManager } from '../../components/admin/useAdminSeasonManager'
|
||||
import { watchAdminToast } from '../../composables/useAdminToast'
|
||||
import Button from '../../components/ui/Button.vue'
|
||||
@@ -61,14 +48,12 @@ const {
|
||||
latestSeasonAuditMeta,
|
||||
canCreate,
|
||||
phaseGateway,
|
||||
activatePhase,
|
||||
activatePublicSeason,
|
||||
requestPhaseGateway,
|
||||
requestCompletionGateway,
|
||||
confirmPhaseGateway,
|
||||
openCreateModal,
|
||||
saveSeason,
|
||||
completeSeason,
|
||||
createSeason,
|
||||
openDeleteSeasonModal,
|
||||
confirmDeleteSeason,
|
||||
@@ -88,181 +73,11 @@ async function selectSeason(seasonId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Phase / Timeline ----------
|
||||
const currentPhaseKey = computed(() => normalizePhaseKey(form.currentPhase))
|
||||
const autoPhase = computed(() => resolveAutoPhase(form))
|
||||
const autoPhaseIsCompleted = computed(() => normalizePhaseKey(autoPhase.value) === 'completed')
|
||||
const autoMismatch = computed(() => Boolean(autoPhase.value && normalizePhaseKey(autoPhase.value) !== currentPhaseKey.value))
|
||||
const todayLabel = computed(() =>
|
||||
new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }),
|
||||
)
|
||||
|
||||
const editingPhase = ref<PhaseKey | null>(null)
|
||||
const timelineError = ref('')
|
||||
const draft = reactive({ start: '', end: '', showStartsAt: '20:00' })
|
||||
const phaseWarning = ref<{
|
||||
action: 'activate' | 'complete'
|
||||
phase: string
|
||||
reason: string
|
||||
detail: string
|
||||
range: string
|
||||
} | null>(null)
|
||||
|
||||
const timelineRows = computed(() =>
|
||||
createSeasonTimelineRows(form, currentPhaseKey.value, autoPhase.value ?? '', editingPhase.value).map((row) => ({
|
||||
...row,
|
||||
finalLocked: row.key === 'completed' && !row.active,
|
||||
})),
|
||||
)
|
||||
|
||||
function startEdit(row: PhaseRowConfig) {
|
||||
if (!row.editable) return
|
||||
timelineError.value = ''
|
||||
editingPhase.value = row.key
|
||||
draft.start = row.start ? form[row.start] : ''
|
||||
draft.end = row.end ? form[row.end] : ''
|
||||
draft.showStartsAt = form.showStartsAt || '20:00'
|
||||
}
|
||||
function cancelEdit() {
|
||||
editingPhase.value = null
|
||||
timelineError.value = ''
|
||||
}
|
||||
async function saveEdit(row: PhaseRowConfig) {
|
||||
timelineError.value = ''
|
||||
if (!row.start || !row.end) return
|
||||
if (!draft.start || !draft.end) { timelineError.value = 'Start und Ende ausfüllen.'; return }
|
||||
if (row.key !== 'show' && draft.start > draft.end) { timelineError.value = 'Startdatum darf nicht nach Enddatum liegen.'; return }
|
||||
form[row.start] = draft.start
|
||||
form[row.end] = row.key === 'show' ? draft.start : draft.end
|
||||
if (row.key === 'show') form.showStartsAt = draft.showStartsAt || '20:00'
|
||||
const saved = await saveSeason()
|
||||
if (saved !== false) editingPhase.value = null
|
||||
}
|
||||
|
||||
// ---------- Countdown helpers ----------
|
||||
function daysUntil(dateStr: string | undefined | null): number | null {
|
||||
if (!dateStr) return null
|
||||
const target = new Date(`${dateStr}T00:00:00`)
|
||||
if (Number.isNaN(target.getTime())) return null
|
||||
const diff = Math.ceil((target.getTime() - Date.now()) / 86_400_000)
|
||||
return diff
|
||||
}
|
||||
function phaseCountdown(row: (typeof timelineRows.value)[number]): string | null {
|
||||
if (row.active) {
|
||||
const endField = SEASON_PHASES.find((p) => p.key === row.key)?.end
|
||||
if (!endField) return null
|
||||
const days = daysUntil(form[endField])
|
||||
if (days === null) return null
|
||||
if (days < 0) return 'Überfällig'
|
||||
if (days === 0) return 'Heute endet'
|
||||
if (days === 1) return 'Noch 1 Tag'
|
||||
return `Noch ${days} Tage`
|
||||
}
|
||||
const startField = SEASON_PHASES.find((p) => p.key === row.key)?.start
|
||||
if (!startField) return null
|
||||
const days = daysUntil(form[startField])
|
||||
if (days === null || days <= 0) return null
|
||||
return `Startet in ${days} Tagen`
|
||||
}
|
||||
|
||||
function parseLocalDate(value: string | undefined | null) {
|
||||
if (!value) return null
|
||||
const [year, month, day] = value.split('-').map(Number)
|
||||
if (!year || !month || !day) return null
|
||||
const date = new Date(year, month - 1, day)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return Number.isNaN(date.getTime()) ? null : date
|
||||
}
|
||||
|
||||
function formatLocalDate(value: string | undefined | null) {
|
||||
const date = parseLocalDate(value)
|
||||
return date
|
||||
? date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
: 'offen'
|
||||
}
|
||||
|
||||
function todayAtStartOfDay() {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
return today
|
||||
}
|
||||
|
||||
function phaseWindowWarning(row: (typeof timelineRows.value)[number]) {
|
||||
if (row.key === 'completed' || !row.start || !row.end) return null
|
||||
|
||||
const start = parseLocalDate(form[row.start])
|
||||
const end = parseLocalDate(form[row.end])
|
||||
if (!start || !end) {
|
||||
return {
|
||||
reason: `Für „${row.title}“ ist kein vollständiges Zeitfenster hinterlegt.`,
|
||||
detail: 'Bitte prüfe zuerst Start und Ende der Phase oder bestätige bewusst die manuelle Aktivierung.',
|
||||
}
|
||||
}
|
||||
|
||||
const today = todayAtStartOfDay()
|
||||
if (today < start) {
|
||||
return {
|
||||
reason: `„${row.title}“ startet laut Zeitplan erst am ${formatLocalDate(form[row.start])}.`,
|
||||
detail: 'Wenn du jetzt aktivierst, öffnen Public-API, Countdown und Teilnahme-Gates vor dem geplanten Start.',
|
||||
}
|
||||
}
|
||||
|
||||
if (today > end) {
|
||||
return {
|
||||
reason: `„${row.title}“ ist laut Zeitplan seit dem ${formatLocalDate(form[row.end])} beendet.`,
|
||||
detail: 'Wenn du jetzt aktivierst, springt die öffentliche Award-Phase zurück in ein bereits abgelaufenes Fenster.',
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function requestPhaseActivation(row: (typeof timelineRows.value)[number]) {
|
||||
if (saving.value || !selectedSeasonId.value || row.active || row.finalLocked) return
|
||||
|
||||
const warning = phaseWindowWarning(row)
|
||||
if (warning) {
|
||||
phaseWarning.value = {
|
||||
action: 'activate',
|
||||
phase: row.title,
|
||||
range: row.dateRange,
|
||||
reason: warning.reason,
|
||||
detail: warning.detail,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
void requestPhaseGateway(row.title)
|
||||
}
|
||||
|
||||
function requestPhaseActivationByTitle(phase: string) {
|
||||
const row = timelineRows.value.find((item) => item.title === phase)
|
||||
if (!row) {
|
||||
void requestPhaseGateway(phase)
|
||||
return
|
||||
}
|
||||
|
||||
requestPhaseActivation(row)
|
||||
}
|
||||
|
||||
function requestCompleteSeason() {
|
||||
if (completing.value || !canCompleteSelectedSeason.value) return
|
||||
void requestCompletionGateway()
|
||||
}
|
||||
|
||||
async function confirmPhaseWarning() {
|
||||
const pending = phaseWarning.value
|
||||
if (!pending) return
|
||||
|
||||
phaseWarning.value = null
|
||||
if (pending.action === 'complete') {
|
||||
await completeSeason()
|
||||
return
|
||||
}
|
||||
|
||||
await activatePhase(pending.phase)
|
||||
}
|
||||
|
||||
// ---------- Quick stats ----------
|
||||
const quickStats = computed(() => {
|
||||
const d = seasonDetail.value
|
||||
@@ -342,76 +157,20 @@ const readinessScore = computed(() => {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- ═══ Aktives Jahr: Hero + Quick Stats + Actions ═══ -->
|
||||
<Card v-if="selectedSeasonId" class="overflow-hidden">
|
||||
<div class="border-b border-violet-100 bg-gradient-to-br from-white via-violet-50/40 to-[#f7eef8] px-6 py-5">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="flex items-end gap-4">
|
||||
<strong class="text-5xl font-black leading-none text-slate-950">{{ form.year }}</strong>
|
||||
<div class="mb-0.5">
|
||||
<p class="text-lg font-bold leading-snug text-slate-800">{{ form.name || '–' }}</p>
|
||||
<p class="mt-1 text-sm text-slate-500">{{ form.showStreamUrl || 'Kein Stream-Link' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="rounded-2xl border px-4 py-2 text-sm font-bold"
|
||||
:class="form.isCurrent
|
||||
? 'border-emerald-200 bg-emerald-50 text-emerald-700'
|
||||
: 'border-slate-200 bg-slate-100 text-slate-600'"
|
||||
>
|
||||
{{ form.isCurrent ? '🟢 Public aktiv' : '⚫ Intern' }}
|
||||
</span>
|
||||
<span class="rounded-2xl border border-violet-200 bg-violet-100 px-4 py-2 text-sm font-bold text-violet-800">
|
||||
{{ form.currentPhase || 'Keine Phase' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick stats -->
|
||||
<div class="grid grid-cols-2 divide-x divide-y divide-violet-50 border-b border-violet-100 sm:grid-cols-5">
|
||||
<div
|
||||
v-for="stat in quickStats"
|
||||
:key="stat.label"
|
||||
class="px-5 py-4"
|
||||
>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em]" :class="stat.warn ? 'text-amber-600' : 'text-slate-400'">{{ stat.label }}</p>
|
||||
<strong class="mt-1 block text-2xl" :class="stat.warn ? 'text-amber-700' : 'text-slate-900'">{{ stat.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions footer -->
|
||||
<div class="flex flex-wrap items-center gap-3 px-5 py-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="gap-1.5 border border-rose-100 bg-rose-50 text-rose-600 hover:bg-rose-100"
|
||||
:disabled="!canDeleteSelectedSeason"
|
||||
@click="openDeleteSeasonModal"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" /> Löschen
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="gap-1.5 border border-emerald-100 bg-emerald-50 text-emerald-700 hover:bg-emerald-100"
|
||||
:disabled="saving || form.isCurrent || !canActivatePublic"
|
||||
@click="activatePublicSeason"
|
||||
>
|
||||
<Globe2 class="h-4 w-4" /> {{ saving ? 'Aktiviert …' : 'Public aktivieren' }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="gap-1.5 border border-amber-100 bg-amber-50 text-amber-700 hover:bg-amber-100"
|
||||
:disabled="completing || !canCompleteSelectedSeason"
|
||||
@click="requestCompleteSeason"
|
||||
>
|
||||
<CheckCircle2 class="h-4 w-4" /> {{ completing ? 'Schließt …' : 'Beenden' }}
|
||||
</Button>
|
||||
<Button class="gap-1.5 ml-auto" :disabled="saving" @click="saveSeason">
|
||||
{{ saving ? 'Speichert …' : 'Speichern' }}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<AdminSeasonOverviewCard
|
||||
v-if="selectedSeasonId"
|
||||
:form="form"
|
||||
:saving="saving"
|
||||
:completing="completing"
|
||||
:quick-stats="quickStats"
|
||||
:can-delete-selected-season="canDeleteSelectedSeason"
|
||||
:can-activate-public="canActivatePublic"
|
||||
:can-complete-selected-season="canCompleteSelectedSeason"
|
||||
:open-delete-season-modal="openDeleteSeasonModal"
|
||||
:activate-public-season="activatePublicSeason"
|
||||
:request-complete-season="requestCompleteSeason"
|
||||
:save-season="saveSeason"
|
||||
/>
|
||||
|
||||
<!-- ═══ Grunddaten + Readiness ═══ -->
|
||||
<section v-if="selectedSeasonId" class="grid gap-6 xl:grid-cols-[minmax(0,1fr)_380px]">
|
||||
@@ -559,150 +318,14 @@ const readinessScore = computed(() => {
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<!-- ═══ Phase & Timeline (kombiniert) ═══ -->
|
||||
<Card v-if="selectedSeasonId" class="overflow-hidden">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4 border-b border-violet-100 bg-gradient-to-br from-white via-violet-50/40 to-[#f7eef8] px-6 py-5">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Phase & Timeline</p>
|
||||
<h2 class="mt-1 text-xl font-bold text-slate-900">Phasenwechsel und Zeitplan</h2>
|
||||
<p class="mt-1.5 max-w-2xl text-sm leading-5 text-slate-500">
|
||||
Aktive Phase wechseln und Zeitfenster direkt inline bearbeiten. Die Daten steuern Public-API, Gates und Countdown.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Auto-mismatch compact banner -->
|
||||
<div v-if="autoMismatch" class="flex items-center gap-3 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3">
|
||||
<AlertTriangle class="h-4 w-4 shrink-0 text-amber-700" />
|
||||
<div>
|
||||
<p class="text-sm font-bold text-amber-900">Zeitplan schlägt vor: {{ autoPhase }}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="ml-2 border border-amber-200 bg-white text-amber-700 hover:bg-amber-100"
|
||||
:disabled="saving || !selectedSeasonId || !autoPhase || autoPhaseIsCompleted"
|
||||
@click="autoPhase && !autoPhaseIsCompleted && requestPhaseActivationByTitle(autoPhase)"
|
||||
>
|
||||
{{ autoPhaseIsCompleted ? 'Über Beenden' : 'Aktivieren' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="timelineError" class="border-b border-rose-100 bg-rose-50 px-6 py-3 text-sm font-semibold text-rose-700">{{ timelineError }}</p>
|
||||
|
||||
<!-- Phase cards grid -->
|
||||
<div class="p-5">
|
||||
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-5">
|
||||
<article
|
||||
v-for="row in timelineRows"
|
||||
:key="row.key"
|
||||
class="flex flex-col rounded-[22px] border transition"
|
||||
:class="row.active
|
||||
? 'border-violet-300 bg-[linear-gradient(135deg,#ede9fe,#fff)] shadow-[0_16px_40px_rgba(139,108,219,0.16)]'
|
||||
: row.isEditing
|
||||
? 'border-violet-200 bg-violet-50/70'
|
||||
: row.statusLabel === 'Auto'
|
||||
? 'border-amber-200 bg-amber-50/50'
|
||||
: 'border-violet-100 bg-white/90 hover:border-violet-200'"
|
||||
>
|
||||
<!-- Card header -->
|
||||
<div class="flex items-center justify-between gap-2 px-4 pt-4">
|
||||
<span
|
||||
class="rounded-full border px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.08em]"
|
||||
:class="row.statusClass"
|
||||
>
|
||||
{{ row.statusLabel }}
|
||||
</span>
|
||||
<span class="h-2 w-2 rounded-full" :class="row.dotClass" />
|
||||
</div>
|
||||
|
||||
<!-- Card body -->
|
||||
<div class="flex-1 px-4 pb-2 pt-3">
|
||||
<h3 class="text-base font-bold text-slate-900">{{ row.title }}</h3>
|
||||
<p class="mt-1 text-xs leading-5 text-slate-500">{{ row.description }}</p>
|
||||
|
||||
<!-- Date display or edit -->
|
||||
<div class="mt-3">
|
||||
<template v-if="row.isEditing">
|
||||
<div class="space-y-2">
|
||||
<template v-if="row.key === 'show'">
|
||||
<input v-model="draft.start" type="date" class="h-9 w-full rounded-xl border border-violet-200 bg-white px-3 text-xs outline-none focus:border-violet-400 focus:ring-2 focus:ring-violet-100" />
|
||||
<input v-model="draft.showStartsAt" type="time" class="h-9 w-full rounded-xl border border-violet-200 bg-white px-3 text-xs outline-none focus:border-violet-400 focus:ring-2 focus:ring-violet-100" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<input v-model="draft.start" type="date" placeholder="Start" class="h-9 w-full rounded-xl border border-violet-200 bg-white px-3 text-xs outline-none focus:border-violet-400 focus:ring-2 focus:ring-violet-100" />
|
||||
<input v-model="draft.end" type="date" placeholder="Ende" class="h-9 w-full rounded-xl border border-violet-200 bg-white px-3 text-xs outline-none focus:border-violet-400 focus:ring-2 focus:ring-violet-100" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="text-sm font-semibold text-slate-700">{{ row.dateRange }}</p>
|
||||
<!-- Countdown badge for active/upcoming phases -->
|
||||
<span
|
||||
v-if="phaseCountdown(row)"
|
||||
class="mt-2 inline-flex items-center gap-1.5 rounded-full border border-violet-100 bg-violet-50 px-2.5 py-1 text-[11px] font-semibold"
|
||||
:class="row.active && phaseCountdown(row) === 'Überfällig' ? 'border-rose-200 bg-rose-50 text-rose-700' : 'text-violet-700'"
|
||||
>
|
||||
<Clock class="h-3 w-3" />
|
||||
{{ phaseCountdown(row) }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card actions -->
|
||||
<div class="flex flex-wrap gap-2 px-4 pb-4 pt-2">
|
||||
<template v-if="row.isEditing">
|
||||
<Button size="sm" class="flex-1" :disabled="saving" @click="saveEdit(row)">OK</Button>
|
||||
<button
|
||||
type="button"
|
||||
class="grid h-9 w-9 shrink-0 place-items-center rounded-xl border border-violet-100 bg-white text-slate-500 hover:bg-violet-50"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- Activate phase button -->
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-1 items-center justify-center gap-1.5 rounded-2xl px-3 py-2 text-xs font-bold transition disabled:cursor-not-allowed disabled:opacity-60"
|
||||
:class="row.active
|
||||
? 'bg-violet-600 text-white'
|
||||
: row.finalLocked
|
||||
? 'border border-slate-200 bg-slate-50 text-slate-500'
|
||||
: 'border border-violet-200 bg-white text-violet-700 hover:bg-violet-50'"
|
||||
:disabled="saving || !selectedSeasonId || row.active || row.finalLocked"
|
||||
@click="requestPhaseActivation(row)"
|
||||
>
|
||||
<CheckCircle2 v-if="row.active" class="h-3.5 w-3.5" />
|
||||
<LockKeyhole v-else-if="row.finalLocked" class="h-3.5 w-3.5" />
|
||||
<WandSparkles v-else class="h-3.5 w-3.5" />
|
||||
{{ row.active ? 'Aktiv' : row.finalLocked ? 'Beenden nutzen' : 'Aktivieren' }}
|
||||
</button>
|
||||
<!-- Edit dates button -->
|
||||
<button
|
||||
v-if="row.editable"
|
||||
type="button"
|
||||
class="grid h-9 w-9 shrink-0 place-items-center rounded-xl border border-violet-100 bg-white text-violet-600 transition hover:bg-violet-50 disabled:opacity-50"
|
||||
:disabled="saving || !selectedSeasonId"
|
||||
title="Datum bearbeiten"
|
||||
@click="startEdit(row)"
|
||||
>
|
||||
<Pencil class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<span v-else class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-slate-100 bg-slate-50">
|
||||
<LockKeyhole class="h-3.5 w-3.5 text-slate-400" />
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-violet-100 bg-violet-50/30 px-6 py-3 text-xs leading-5 text-slate-500">
|
||||
Timeline-Felder steuern Public-API, Vorschau und Teilnahme-Gates. Änderungen werden erst mit „Speichern" gespeichert.
|
||||
</div>
|
||||
</Card>
|
||||
<AdminSeasonWorkflowCard
|
||||
v-if="selectedSeasonId"
|
||||
:form="form"
|
||||
:saving="saving"
|
||||
:selected-season-id="selectedSeasonId"
|
||||
:save-season="saveSeason"
|
||||
:request-phase-gateway="requestPhaseGateway"
|
||||
/>
|
||||
|
||||
<!-- Empty state -->
|
||||
<Card v-else class="px-8 py-16 text-center">
|
||||
@@ -735,50 +358,6 @@ const readinessScore = computed(() => {
|
||||
:on-confirm-delete="confirmDeleteSeason"
|
||||
/>
|
||||
|
||||
<Modal
|
||||
:open="!!phaseWarning"
|
||||
title="Phase außerhalb des Zeitfensters"
|
||||
subtitle="Der Zeitplan passt nicht zur gewählten manuellen Umstellung."
|
||||
@close="phaseWarning = null"
|
||||
>
|
||||
<div v-if="phaseWarning" class="space-y-4">
|
||||
<div class="flex gap-4 rounded-[24px] border border-amber-200 bg-amber-50 p-4 text-amber-900">
|
||||
<AlertTriangle class="mt-0.5 h-5 w-5 shrink-0" />
|
||||
<div>
|
||||
<p class="font-bold">{{ phaseWarning.reason }}</p>
|
||||
<p class="mt-2 text-sm font-semibold leading-6 text-amber-800">{{ phaseWarning.detail }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 rounded-[22px] border border-violet-100 bg-white/80 p-4 text-sm">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="font-semibold text-slate-500">Gewählte Phase</span>
|
||||
<strong class="text-right text-slate-900">{{ phaseWarning.phase }}</strong>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="font-semibold text-slate-500">Geplanter Zeitraum</span>
|
||||
<strong class="text-right text-slate-900">{{ phaseWarning.range }}</strong>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="font-semibold text-slate-500">Heute</span>
|
||||
<strong class="text-right text-slate-900">{{ todayLabel }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="rounded-2xl border border-violet-100 bg-violet-50/70 px-4 py-3 text-sm font-semibold leading-6 text-slate-600">
|
||||
Bestätige nur, wenn der Zeitplan bewusst übersteuert werden soll. Nach der Bestätigung wird die Phase sofort gespeichert.
|
||||
</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button type="button" variant="ghost" :disabled="saving || completing" @click="phaseWarning = null">
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" :disabled="saving || completing" @click="confirmPhaseWarning">
|
||||
{{ phaseWarning?.action === 'complete' ? 'Trotzdem beenden' : 'Trotzdem aktivieren' }}
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
:open="!!phaseGateway"
|
||||
:title="phaseGateway?.title || 'Check-Gateway'"
|
||||
|
||||
Reference in New Issue
Block a user