Refactor BuildStats and MainNav components

Modularize BuildStats by splitting it into smaller components (StatsListSection, StatItem) for better code reusability and readability. Update MainNav to reflect this restructuring and improve the handling of dynamic styles, computed properties, and data binding.
This commit is contained in:
2025-04-14 21:41:21 +02:00
parent 9adeaa4483
commit 9762505a24
6 changed files with 167 additions and 114 deletions

View File

@@ -9,10 +9,14 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
BuildServerStats: typeof import('./src/components/BuildServerStats.vue')['default']
BuildStats: typeof import('./src/components/BuildStats.vue')['default']
BuildStats: typeof import('./src/components/MainNav/BuildStats.vue')['default']
CurrentlyBuilding: typeof import('./src/components/CurrentlyBuilding.vue')['default']
MainNav: typeof import('./src/components/MainNav.vue')['default']
PackageFilters: typeof import('./src/components/Packages/PackageFilters.vue')['default']
Packages: typeof import('./src/components/Packages.vue')['default']
QueuedPackagesList: typeof import('./src/components/QueuedPackagesList.vue')['default']
PackageTable: typeof import('./src/components/Packages/PackageTable.vue')['default']
QueuedPackagesList: typeof import('./src/components/CurrentlyBuilding/QueuedPackagesList.vue')['default']
StatItem: typeof import('./src/components/MainNav/BuildStats/StatItem.vue')['default']
StatsListSection: typeof import('./src/components/MainNav/BuildStats/StatsListSection.vue')['default']
}
}

View File

@@ -1,102 +0,0 @@
<template>
<v-sheet :style="sheetStyles" class="d-flex" color="transparent">
<!-- Dynamically render the "Stats" section -->
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
<v-list-subheader>Stats:</v-list-subheader>
<v-list-item
v-for="item in statsList"
:key="item.label"
:style="{ color: item.color }"
:title="item.label">
{{ item.value }}
</v-list-item>
</v-list>
<!-- Dynamically render the "LTO" section -->
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
<v-list-subheader>LTO:</v-list-subheader>
<v-list-item
v-for="item in ltoList"
:key="item.label"
:style="{ color: item.color }"
:title="item.label">
{{ item.value }}
</v-list-item>
</v-list>
</v-sheet>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { type Stats } from '@/types/Stats'
// Define reusable styles and colors
const sheetStyles = { gap: '50px' }
const listStyles = { borderRadius: '5px' }
const colors = {
latest: '#069b35',
queued: '#b97808',
skipped: '#878787',
failed: '#b30303',
enabled: '#069b35',
disabled: '#b30303',
unknown: '#878787'
}
// Reactive stats object
const stats = ref<Stats>({
latest: 0,
queued: 0,
skipped: 0,
failed: 0,
lto: {
enabled: 0,
disabled: 0,
unknown: 0
}
})
// Map stats and LTO data for simpler rendering in the template
const statsList = ref([
{ label: 'latest', value: stats.value.latest, color: colors.latest },
{ label: 'queued', value: stats.value.queued, color: colors.queued },
{ label: 'skipped', value: stats.value.skipped, color: colors.skipped },
{ label: 'failed', value: stats.value.failed, color: colors.failed }
])
const ltoList = ref([
{ label: 'enabled', value: stats.value.lto.enabled, color: colors.enabled },
{ label: 'disabled', value: stats.value.lto.disabled, color: colors.disabled },
{ label: 'unknown', value: stats.value.lto.unknown, color: colors.unknown }
])
// Function to fetch stats data (refactored with async/await)
const getStats = async (): Promise<void> => {
try {
const response = await fetch('https://api.alhp.dev/stats')
if (!response.ok) throw new Error(response.statusText)
const statistics = await response.json()
stats.value = statistics
// Update lists with fresh data
statsList.value = [
{ label: 'latest', value: stats.value.latest, color: colors.latest },
{ label: 'queued', value: stats.value.queued, color: colors.queued },
{ label: 'skipped', value: stats.value.skipped, color: colors.skipped },
{ label: 'failed', value: stats.value.failed, color: colors.failed }
]
ltoList.value = [
{ label: 'enabled', value: stats.value.lto.enabled, color: colors.enabled },
{ label: 'disabled', value: stats.value.lto.disabled, color: colors.disabled },
{ label: 'unknown', value: stats.value.lto.unknown, color: colors.unknown }
]
} catch (error) {
console.error('Failed to fetch stats:', error)
}
}
// Fetch stats on mount
onMounted(() => {
getStats()
})
</script>

View File

@@ -1,11 +1,11 @@
<template>
<v-app-bar color="#191c2a" style="background: #0d1538">
<v-container :class="width < 1440 ? 'ms-3' : 'mx-auto'" fluid style="max-width: 1440px">
<v-app-bar :color="appBarColors.background">
<v-container :class="containerClasses" :style="{ maxWidth: maxContainerWidth }" fluid>
<v-row>
<v-app-bar-title style="align-self: center">
<span style="font-size: 20px">
ALHP Status
<a class="ms-2 gitea-link" href="https://somegit.dev/ALHP/ALHP.GO" target="_blank">
<v-app-bar-title class="app-title">
<span>
{{ appTitle }}
<a :href="repoUrl" class="ms-2 gitea-link" target="_blank">
<i class="fa fa-gitea"></i>
</a>
</span>
@@ -18,21 +18,40 @@
</template>
<script lang="ts" setup>
import BuildStats from '@/components/BuildStats.vue'
import BuildStats from '@/components/MainNav/BuildStats.vue'
import { useDisplay } from 'vuetify'
import { computed } from 'vue'
const { mobile, width } = useDisplay()
const appTitle = 'ALHP Status'
const repoUrl = 'https://somegit.dev/ALHP/ALHP.GO'
const maxContainerWidth = '1440px'
const appBarColors = {
background: '#0d1538',
accent: '#609926'
}
const containerClasses = computed(() => ({
'ms-3': width.value < 1440,
'mx-auto': width.value >= 1440
}))
</script>
<style scoped>
.app-title {
align-self: center;
font-size: 20px;
}
.gitea-link {
color: white;
font-size: 25px;
text-decoration: none; /* Optional: To prevent underlining */
transition: color 0.2s; /* Smooth color transition */
text-decoration: none;
transition: color 0.2s;
}
.gitea-link:hover {
color: #609926;
color: v-bind('appBarColors.accent');
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<v-sheet :style="sheetStyles" class="d-flex" color="transparent">
<StatsListSection title="Stats">
<StatItem
v-for="(value, key) in generalStats"
:key="key"
:color="value.color"
:count="value.count"
:title="key" />
</StatsListSection>
<StatsListSection title="LTO">
<StatItem
v-for="(value, key) in customStats.lto"
:key="key"
:color="value.color"
:count="value.count"
:title="key" />
</StatsListSection>
</v-sheet>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useDataStore } from '@/stores/dataStore'
import { type dataStore } from '@/types/dataStore'
import StatsListSection from '@/components/MainNav/BuildStats/StatsListSection.vue'
import StatItem from '@/components/MainNav/BuildStats/StatItem.vue'
interface CustomStatItem {
count: number
color: string
}
interface CustomStats {
latest: CustomStatItem
queued: CustomStatItem
building: CustomStatItem
skipped: CustomStatItem
failed: CustomStatItem
lto: {
enabled: CustomStatItem
disabled: CustomStatItem
unknown: CustomStatItem
}
}
const COLORS = {
SUCCESS: '#069b35',
WARNING: '#b97808',
ERROR: '#b30303',
NEUTRAL: '#878787'
}
const sheetStyles = { gap: '50px' }
const dataStore = useDataStore()
const stats = ref<dataStore['stats']>()
const customStats = ref<CustomStats>({
latest: { count: 0, color: COLORS.SUCCESS },
queued: { count: 0, color: COLORS.WARNING },
building: { count: 0, color: COLORS.WARNING },
skipped: { count: 0, color: COLORS.NEUTRAL },
failed: { count: 0, color: COLORS.ERROR },
lto: {
enabled: { count: 0, color: COLORS.SUCCESS },
disabled: { count: 0, color: COLORS.ERROR },
unknown: { count: 0, color: COLORS.NEUTRAL }
}
})
const generalStats = computed(() => {
const { lto, ...rest } = customStats.value
return rest
})
const updateStats = (): void => {
stats.value = dataStore.getPackageStats()
if (!stats.value) return
Object.keys(generalStats.value).forEach((key) => {
const typedKey = key as keyof typeof generalStats.value
customStats.value[typedKey].count = stats.value?.[typedKey] as number
})
Object.keys(customStats.value.lto).forEach((ltoKey) => {
const typedLtoKey = ltoKey as keyof typeof customStats.value.lto
customStats.value.lto[typedLtoKey].count = stats.value?.lto[typedLtoKey] as number
})
}
watch(
() => dataStore.loading,
(isLoading) => {
if (!isLoading) {
updateStats()
}
}
)
onMounted(updateStats)
</script>

View File

@@ -0,0 +1,13 @@
<template>
<v-list-item :style="{ color }" :title="title">
{{ count }}
</v-list-item>
</template>
<script lang="ts" setup>
defineProps<{
title: string
count: number
color: string
}>()
</script>

View File

@@ -0,0 +1,15 @@
<template>
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
<v-list-subheader>{{ title }}:</v-list-subheader>
<slot></slot>
</v-list>
</template>
<script lang="ts" setup>
defineProps<{
title: string
}>()
// Reusable styles
const listStyles = { borderRadius: '5px' }
</script>