feat: Replace traffic light with shield ring icon

This commit is contained in:
2025-11-09 08:53:46 +01:00
parent a16cbae5e6
commit ef374f79af
3 changed files with 197 additions and 15 deletions

View File

@@ -1,7 +1,56 @@
<template>
<UPageCard title="Ampelstatus" variant="naked" orientation="horizontal" class="mb-4">
{{ statusEmoji }}
</UPageCard>
<div class="flex items-center gap-2">
<div class="relative flex items-center justify-center">
<svg
class="shield-ring"
:class="ringClass"
width="48"
height="48"
viewBox="0 0 48 48"
>
<circle
class="ring-background"
cx="24"
cy="24"
r="20"
fill="none"
stroke="currentColor"
stroke-width="3"
opacity="0.1"
/>
<circle
class="ring-progress"
cx="24"
cy="24"
r="20"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
:stroke-dasharray="circumference"
:stroke-dashoffset="progressOffset"
transform="rotate(-90 24 24)"
/>
</svg>
<Transition name="shield-pulse" mode="out-in">
<UIcon
:key="status"
:name="shieldIcon"
class="absolute shield-icon"
:class="iconClass"
/>
</Transition>
</div>
<UBadge
:label="statusLabel"
:color="badgeColor"
size="md"
variant="subtle"
class="status-badge"
/>
</div>
</template>
<script setup lang="ts">
@@ -11,17 +60,154 @@ const props = defineProps<{
status: ComplianceStatus
}>()
const statusEmoji = computed(() => {
const circumference = 2 * Math.PI * 20
const shieldIcon = computed(() => {
switch (props.status) {
case ComplianceStatus.Critical:
return '🔴'
return 'i-lucide-shield-alert'
case ComplianceStatus.Warning:
return '🟡'
return 'i-lucide-shield-x'
case ComplianceStatus.NonCritical:
return '🟢'
return 'i-lucide-shield-check'
default:
return '🟢'
return 'i-lucide-shield-check'
}
})
const statusLabel = computed(() => {
switch (props.status) {
case ComplianceStatus.Critical:
return 'Kritisch'
case ComplianceStatus.Warning:
return 'Warnung'
case ComplianceStatus.NonCritical:
return 'Unkritisch'
default:
return 'Unkritisch'
}
})
const badgeColor = computed(() => {
switch (props.status) {
case ComplianceStatus.Critical:
return 'red'
case ComplianceStatus.Warning:
return 'yellow'
case ComplianceStatus.NonCritical:
return 'green'
default:
return 'green'
}
})
const ringClass = computed(() => {
switch (props.status) {
case ComplianceStatus.Critical:
return 'text-red-500'
case ComplianceStatus.Warning:
return 'text-yellow-500'
case ComplianceStatus.NonCritical:
return 'text-green-500'
default:
return 'text-green-500'
}
})
const iconClass = computed(() => {
switch (props.status) {
case ComplianceStatus.Critical:
return 'text-red-500 w-6 h-6'
case ComplianceStatus.Warning:
return 'text-yellow-500 w-6 h-6'
case ComplianceStatus.NonCritical:
return 'text-green-500 w-6 h-6'
default:
return 'text-green-500 w-6 h-6'
}
})
const progressValue = computed(() => {
switch (props.status) {
case ComplianceStatus.Critical:
return 100
case ComplianceStatus.Warning:
return 60
case ComplianceStatus.NonCritical:
return 30
default:
return 30
}
})
const progressOffset = computed(() => {
return circumference - (progressValue.value / 100) * circumference
})
watch(() => props.status, () => {
const element = document.querySelector('.shield-ring')
if (element) {
element.classList.add('status-change-pulse')
setTimeout(() => {
element.classList.remove('status-change-pulse')
}, 600)
}
})
</script>
<style scoped>
.shield-ring {
transition: color 300ms ease-in-out;
}
.ring-progress {
transition: stroke-dashoffset 500ms cubic-bezier(0.4, 0, 0.2, 1);
}
.shield-icon {
transition: color 300ms ease-in-out;
}
.status-badge {
transition: all 300ms ease-in-out;
}
@keyframes pulse-ring {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.status-change-pulse {
animation: pulse-ring 600ms ease-in-out;
}
.shield-pulse-enter-active,
.shield-pulse-leave-active {
transition: all 300ms ease-in-out;
}
.shield-pulse-enter-from {
opacity: 0;
transform: scale(0.8);
}
.shield-pulse-leave-to {
opacity: 0;
transform: scale(1.2);
}
.shield-icon {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
}
</style>

View File

@@ -15,6 +15,7 @@
<UDashboardToolbar>
<UNavigationMenu :items="links" highlight class="-mx-1 flex-1" />
<FormValidationIndicator :status="validationStatus" />
</UDashboardToolbar>
</template>
@@ -28,9 +29,7 @@
@save="onSave"
@submit="onSubmit"
@navigate="handleNavigate"
>
<FormValidationIndicator :status="validationStatus" />
</FormStepperWithNavigation>
/>
</div>
</template>
</UDashboardPanel>

View File

@@ -5,12 +5,10 @@
<template #leading>
<UDashboardSidebarCollapse />
</template>
<template #right />
</UDashboardNavbar>
<UDashboardToolbar>
<template #left />
<FormValidationIndicator :status="validationStatus" />
</UDashboardToolbar>
</template>
@@ -28,7 +26,6 @@
@save="onSave"
@submit="onSubmit"
>
<FormValidationIndicator :status="validationStatus" />
<UFormField label="Name" class="mb-4">
<UInput v-model="applicationFormTemplate.name" />
</UFormField>