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:
102
test-apps/daily-notification-test/src/components/StatusList.vue
Normal file
102
test-apps/daily-notification-test/src/components/StatusList.vue
Normal 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>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user