refactor(test-app): extract reusable StatusList component

Created standardized StatusList component to eliminate duplicate status
display code across views. Standardized styling:
- Flexbox layout with space-between justification
- Border-bottom dividers (removed via :last-child)
- Optional status-based color coding via show-status-colors prop
- Consistent padding (12px 0) and spacing

Migrated HomeView and StatusView to use the new component:
- HomeView: Replaced inline status list, removed ~50 lines of duplicate CSS
- StatusView: Replaced diagnostics info items, removed ~25 lines of duplicate CSS
- Removed unused helper functions (getStatusType, getStatusDescription)
- Fixed TypeScript type assertions for status values
- Added diagnosticsItems computed property in StatusView

Reduces code duplication by ~75 lines and provides single source of truth
for status list styling across the application.
This commit is contained in:
Jose Olarte III
2026-01-07 21:20:49 +08:00
parent 66c7eca33d
commit 65379aedd6
3 changed files with 129 additions and 155 deletions

View File

@@ -0,0 +1,102 @@
<!--
/**
* Status List Component - Standardized Label/Value List Display
*
* Reusable component for displaying status items in a consistent format
* with optional status-based color coding
*
* @author Matthew Raymer
* @version 1.0.0
*/
-->
<template>
<div class="status-list">
<div
v-for="item in items"
:key="item.label"
class="status-item"
:class="showStatusColors && item.status ? `status-${item.status}` : ''"
>
<div class="status-row">
<span class="status-label">{{ item.label }}</span>
<span
class="status-value"
:class="showStatusColors && item.status ? `value-${item.status}` : ''"
>
{{ item.value }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
interface StatusListItem {
label: string
value: string
status?: 'success' | 'warning' | 'error' | 'info'
}
interface Props {
items: StatusListItem[]
showStatusColors?: boolean
}
defineProps<Props>()
</script>
<style scoped>
.status-list {
display: flex;
flex-direction: column;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.status-item:last-child {
border-bottom: none;
}
.status-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.status-label {
font-size: 14px;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.status-value {
font-size: 14px;
font-weight: 600;
color: white;
}
.value-success {
color: #4caf50;
}
.value-warning {
color: #ff9800;
}
.value-error {
color: #f44336;
}
.value-info {
color: #2196f3;
}
</style>

View File

@@ -82,22 +82,7 @@
<!-- System Status -->
<div class="system-status">
<h2 class="section-title">System Status</h2>
<div class="status-list">
<div
v-for="(item, index) in systemStatus"
:key="item.label"
class="status-item"
:class="`status-${getStatusType(item.status)}`"
>
<div class="status-row">
<span class="status-label">{{ item.label }}</span>
<span class="status-value" :class="`value-${getStatusType(item.status)}`">
{{ item.value }}
</span>
</div>
<hr v-if="index < systemStatus.length - 1" class="status-divider" />
</div>
</div>
<StatusList :items="systemStatus" :show-status-colors="true" />
<!-- Diagnostic Actions -->
<div class="section">
@@ -126,6 +111,7 @@ import { computed, ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores/app'
import ActionCard from '@/components/cards/ActionCard.vue'
import StatusList from '@/components/StatusList.vue'
import { Capacitor } from '@capacitor/core'
import { DailyNotification } from '@timesafari/daily-notification-plugin'
import { TEST_USER_ZERO_CONFIG, generateEndorserJWT } from '@/config/test-user-zero'
@@ -185,17 +171,20 @@ const systemStatus = computed(() => {
const status = appStore.notificationStatus
if (!status) {
return [
{ label: 'Platform', value: platformName.value, status: 'info' },
{ label: 'Plugin', value: 'Not Available', status: 'error' }
{ label: 'Platform', value: platformName.value, status: 'info' as const },
{ label: 'Plugin', value: 'Not Available', status: 'error' as const }
]
}
const permissionsStatus: 'success' | 'warning' = status.postNotificationsGranted ? 'success' : 'warning'
const canScheduleStatus: 'success' | 'warning' = status.canScheduleNow ? 'success' : 'warning'
return [
{ label: 'Platform', value: platformName.value, status: 'info' },
{ label: 'Plugin', value: 'Available', status: 'success' },
{ label: 'Permissions', value: status.postNotificationsGranted ? 'Granted' : 'Not Granted', status: status.postNotificationsGranted ? 'success' : 'warning' },
{ label: 'Can Schedule', value: status.canScheduleNow ? 'Yes' : 'No', status: status.canScheduleNow ? 'success' : 'warning' },
{ label: 'Next Scheduled', value: status.nextScheduledAt ? new Date(status.nextScheduledAt).toLocaleTimeString() : 'None', status: 'info' }
{ label: 'Platform', value: platformName.value, status: 'info' as const },
{ label: 'Plugin', value: 'Available', status: 'success' as const },
{ label: 'Permissions', value: status.postNotificationsGranted ? 'Granted' : 'Not Granted', status: permissionsStatus },
{ label: 'Can Schedule', value: status.canScheduleNow ? 'Yes' : 'No', status: canScheduleStatus },
{ label: 'Next Scheduled', value: status.nextScheduledAt ? new Date(status.nextScheduledAt).toLocaleTimeString() : 'None', status: 'info' as const }
]
})
@@ -372,35 +361,6 @@ const checkSystemStatus = async (): Promise<void> => {
}
}
const getStatusType = (status: string): 'success' | 'warning' | 'error' | 'info' => {
switch (status) {
case 'success':
case 'warning':
case 'error':
case 'info':
return status
default:
return 'info'
}
}
const getStatusDescription = (label: string): string => {
switch (label) {
case 'Platform':
return 'Current platform information'
case 'Plugin':
return 'DailyNotification plugin availability'
case 'Permissions':
return 'Notification permission status'
case 'Can Schedule':
return 'Ready to schedule notifications'
case 'Next Scheduled':
return 'Next scheduled notification time'
default:
return 'System status information'
}
}
const refreshSystemStatus = async (): Promise<void> => {
console.log('🔄 CLICK: Refresh System Status')
await checkSystemStatus()
@@ -733,61 +693,9 @@ onMounted(async () => {
margin-bottom: 32px;
}
.status-list {
display: flex;
flex-direction: column;
gap: 0;
margin-bottom: 32px;
}
.status-item {
padding: 12px 0;
}
.status-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 16px;
align-items: center;
justify-content: space-between;
}
.status-label {
font-size: 14px;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.status-value {
font-size: 14px;
font-weight: 600;
}
.value-success {
color: #4caf50;
}
.value-warning {
color: #ff9800;
}
.value-error {
color: #f44336;
}
.value-info {
color: #2196f3;
}
.status-divider {
margin: 0;
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.15);
margin-top: 12px;
}
.section {
margin-bottom: 24px;
margin-top: 32px;
}
/* Mobile responsiveness */

View File

@@ -49,28 +49,7 @@
<div class="diagnostics-section">
<h2>System Diagnostics</h2>
<div class="diagnostics-content">
<div class="diagnostics-info">
<div class="info-item">
<span class="label">App Version:</span>
<span class="value">{{ diagnostics.appVersion }}</span>
</div>
<div class="info-item">
<span class="label">Platform:</span>
<span class="value">{{ diagnostics.platform }}</span>
</div>
<div class="info-item">
<span class="label">API Level:</span>
<span class="value">{{ diagnostics.apiLevel }}</span>
</div>
<div class="info-item">
<span class="label">Timezone:</span>
<span class="value">{{ diagnostics.timezone }}</span>
</div>
<div class="info-item">
<span class="label">Last Updated:</span>
<span class="value">{{ diagnostics.lastUpdated }}</span>
</div>
</div>
<StatusList :items="diagnosticsItems" />
<div class="diagnostics-json">
<h3>Raw Diagnostics (JSON)</h3>
@@ -95,6 +74,7 @@ import { Vue, Component, toNative } from 'vue-facing-decorator'
import { Capacitor } from '@capacitor/core'
import router from '../router'
import StatusCard from '../components/cards/StatusCard.vue'
import StatusList from '../components/StatusList.vue'
import { createTypedPlugin } from '../lib/typed-plugin'
import { collectDiagnostics, copyDiagnosticsToClipboard, type ComprehensiveDiagnostics } from '../lib/diagnostics-export'
@@ -115,7 +95,8 @@ type Diagnostics = ComprehensiveDiagnostics
@Component({
components: {
StatusCard
StatusCard,
StatusList
}
})
class StatusView extends Vue {
@@ -239,6 +220,16 @@ class StatusView extends Vue {
return JSON.stringify(this.diagnostics, null, 2)
}
get diagnosticsItems() {
return [
{ label: 'App Version:', value: this.diagnostics.appVersion },
{ label: 'Platform:', value: this.diagnostics.platform },
{ label: 'API Level:', value: this.diagnostics.apiLevel },
{ label: 'Timezone:', value: this.diagnostics.timezone },
{ label: 'Last Updated:', value: this.diagnostics.lastUpdated }
]
}
async mounted() {
await this.refreshStatus()
}
@@ -496,33 +487,6 @@ export default toNative(StatusView)
gap: 24px;
}
.diagnostics-info {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.info-item:last-child {
border-bottom: none;
}
.info-item .label {
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
}
.info-item .value {
color: white;
font-weight: 600;
}
.diagnostics-json {
min-width: 0;