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:
Vantz Stockwell
2026-02-14 23:20:01 -05:00
parent c45567670e
commit a160ba2df4
10 changed files with 1126 additions and 36 deletions

View File

@@ -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>