feat: Build final 9 admin views + swap final hero graphic
Views: Plugins, Wipes, WipeProfiles, WipeCalendar, WipeHistory, Maps, Analytics, StoreManage, ModuleStore. All 20/20 admin views now implemented. Updated hero graphic to final version. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,138 @@
|
||||
<script setup lang="ts">
|
||||
// TODO: Implement calendar view of scheduled and past wipes
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useWipeStore } from '@/stores/wipe'
|
||||
import { Calendar, ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||
|
||||
const wipeStore = useWipeStore()
|
||||
|
||||
const currentMonth = ref(new Date())
|
||||
|
||||
const monthLabel = computed(() => {
|
||||
return currentMonth.value.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
||||
})
|
||||
|
||||
const calendarDays = computed(() => {
|
||||
const year = currentMonth.value.getFullYear()
|
||||
const month = currentMonth.value.getMonth()
|
||||
const firstDay = new Date(year, month, 1).getDay()
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
||||
|
||||
const days: { date: number; inMonth: boolean; hasWipe: boolean; wipeType?: string }[] = []
|
||||
|
||||
// Padding before
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
days.push({ date: 0, inMonth: false, hasWipe: false })
|
||||
}
|
||||
|
||||
// Days of month
|
||||
for (let d = 1; d <= daysInMonth; d++) {
|
||||
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
||||
const wipe = wipeStore.history.find(w =>
|
||||
w.started_at?.startsWith(dateStr) || w.completed_at?.startsWith(dateStr)
|
||||
)
|
||||
days.push({
|
||||
date: d,
|
||||
inMonth: true,
|
||||
hasWipe: !!wipe,
|
||||
wipeType: wipe?.wipe_type,
|
||||
})
|
||||
}
|
||||
|
||||
return days
|
||||
})
|
||||
|
||||
function prevMonth() {
|
||||
const d = new Date(currentMonth.value)
|
||||
d.setMonth(d.getMonth() - 1)
|
||||
currentMonth.value = d
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
const d = new Date(currentMonth.value)
|
||||
d.setMonth(d.getMonth() + 1)
|
||||
currentMonth.value = d
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
wipeStore.fetchHistory()
|
||||
wipeStore.fetchSchedules()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Wipe Calendar</h1>
|
||||
<p class="text-neutral-400">Calendar view of upcoming and completed server wipes.</p>
|
||||
<div class="p-6 space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3">
|
||||
<Calendar class="w-5 h-5 text-oxide-500" />
|
||||
<h1 class="text-2xl font-bold text-neutral-100">Wipe Calendar</h1>
|
||||
</div>
|
||||
|
||||
<!-- Month navigation -->
|
||||
<div class="flex items-center justify-between">
|
||||
<button @click="prevMonth" class="p-2 text-neutral-400 hover:text-neutral-200 transition-colors">
|
||||
<ChevronLeft class="w-5 h-5" />
|
||||
</button>
|
||||
<h2 class="text-lg font-semibold text-neutral-200">{{ monthLabel }}</h2>
|
||||
<button @click="nextMonth" class="p-2 text-neutral-400 hover:text-neutral-200 transition-colors">
|
||||
<ChevronRight class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Calendar grid -->
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-lg overflow-hidden">
|
||||
<!-- Day headers -->
|
||||
<div class="grid grid-cols-7 border-b border-neutral-800">
|
||||
<div
|
||||
v-for="day in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']"
|
||||
:key="day"
|
||||
class="py-2 text-center text-xs font-medium text-neutral-500 uppercase"
|
||||
>
|
||||
{{ day }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Day cells -->
|
||||
<div class="grid grid-cols-7">
|
||||
<div
|
||||
v-for="(day, i) in calendarDays"
|
||||
:key="i"
|
||||
class="min-h-20 p-2 border-b border-r border-neutral-800 last:border-r-0"
|
||||
:class="{ 'bg-neutral-800/30': !day.inMonth }"
|
||||
>
|
||||
<template v-if="day.inMonth">
|
||||
<p class="text-sm" :class="day.hasWipe ? 'text-oxide-400 font-bold' : 'text-neutral-400'">
|
||||
{{ day.date }}
|
||||
</p>
|
||||
<div v-if="day.hasWipe" class="mt-1">
|
||||
<span class="text-xs bg-oxide-500/15 text-oxide-400 px-1.5 py-0.5 rounded">
|
||||
{{ day.wipeType }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming schedules -->
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-5">
|
||||
<h2 class="text-sm font-medium text-neutral-400 uppercase tracking-wider mb-3">Active Schedules</h2>
|
||||
<div v-if="wipeStore.schedules.length === 0" class="text-sm text-neutral-500 text-center py-4">
|
||||
No active schedules.
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<div
|
||||
v-for="schedule in wipeStore.schedules.filter(s => s.is_active)"
|
||||
:key="schedule.id"
|
||||
class="flex items-center justify-between p-3 bg-neutral-800/50 rounded-lg"
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm text-neutral-200">{{ schedule.schedule_name }}</p>
|
||||
<p class="text-xs text-neutral-500 font-mono">{{ schedule.cron_expression }}</p>
|
||||
</div>
|
||||
<p class="text-xs text-neutral-400">
|
||||
Next: {{ schedule.next_scheduled_run ? new Date(schedule.next_scheduled_run).toLocaleDateString() : 'TBD' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user