chore: initial commit

This commit is contained in:
Matthew Raymer
2025-10-15 10:46:50 +00:00
parent 54478b1c97
commit 1e6c4bf7fc
174 changed files with 6867 additions and 21404 deletions

View File

@@ -0,0 +1,15 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<div class="history-view">
<div class="view-header">
<h1 class="page-title">📋 History</h1>
<p class="page-subtitle">Notification history and activity</p>
</div>
<div class="placeholder-content">
<p>History view coming soon...</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
@Component
export default class HistoryView extends Vue {}
</script>
<style scoped>
.history-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.placeholder-content {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
</style>

View File

@@ -0,0 +1,332 @@
<!--
/**
* Home View - Main Dashboard
*
* Platform-neutral home view with quick actions
*
* @author Matthew Raymer
* @version 1.0.0
*/
-->
<template>
<div class="home-view">
<!-- Welcome Section -->
<div class="welcome-section">
<h1 class="welcome-title">
🔔 Daily Notification Test
</h1>
<p class="welcome-subtitle">
Vue 3 + vue-facing-decorator + Capacitor
</p>
<div class="platform-info">
<span class="platform-badge" :class="platformClass">
{{ platformName }}
</span>
<span class="status-badge" :class="statusClass">
{{ statusText }}
</span>
</div>
</div>
<!-- Quick Actions -->
<div class="quick-actions">
<h2 class="section-title">Quick Actions</h2>
<div class="action-grid">
<ActionCard
icon="📅"
title="Schedule Notification"
description="Schedule a new daily notification"
@click="navigateToSchedule"
:loading="isScheduling"
/>
<ActionCard
icon="📊"
title="Check Status"
description="View notification system status"
@click="checkSystemStatus"
:loading="isCheckingStatus"
/>
<ActionCard
icon="🔔"
title="View Notifications"
description="Manage scheduled notifications"
@click="navigateToNotifications"
/>
<ActionCard
icon="📋"
title="View History"
description="Check notification history"
@click="navigateToHistory"
/>
<ActionCard
icon="📜"
title="View Logs"
description="Check system logs and debug info"
@click="navigateToLogs"
/>
<ActionCard
icon="⚙️"
title="Settings"
description="Configure app preferences"
@click="navigateToSettings"
/>
</div>
</div>
<!-- System Status -->
<div class="system-status">
<h2 class="section-title">System Status</h2>
<StatusCard :status="systemStatus" @refresh="refreshSystemStatus" />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores/app'
import ActionCard from '@/components/cards/ActionCard.vue'
import StatusCard from '@/components/cards/StatusCard.vue'
@Component({
components: {
ActionCard,
StatusCard
}
})
export default class HomeView extends Vue {
private router = useRouter()
private appStore = useAppStore()
isScheduling = false
isCheckingStatus = false
get platformName(): string {
const platform = this.appStore.platform
return platform.charAt(0).toUpperCase() + platform.slice(1)
}
get platformClass(): string {
return `platform-${this.appStore.platform}`
}
get statusClass(): string {
const status = this.appStore.notificationStatus
if (!status) return 'unknown'
if (status.canScheduleNow) return 'ready'
return 'not-ready'
}
get statusText(): string {
const status = this.appStore.notificationStatus
if (!status) return 'Unknown'
if (status.canScheduleNow) return 'Ready'
return 'Not Ready'
}
get systemStatus() {
const status = this.appStore.notificationStatus
if (!status) {
return [
{ label: 'Platform', value: this.platformName, status: 'info' },
{ label: 'Plugin', value: 'Not Available', status: 'error' }
]
}
return [
{ label: 'Platform', value: this.platformName, status: 'success' },
{ label: 'Plugin', value: 'Available', status: 'success' },
{ label: 'Notifications', value: status.postNotificationsGranted ? 'Granted' : 'Denied', status: status.postNotificationsGranted ? 'success' : 'error' },
{ label: 'Exact Alarms', value: status.exactAlarmsGranted ? 'Granted' : 'Denied', status: status.exactAlarmsGranted ? 'success' : 'error' },
{ label: 'Channel', value: status.channelEnabled ? 'Enabled' : 'Disabled', status: status.channelEnabled ? 'success' : 'warning' }
]
}
navigateToSchedule(): void {
this.router.push('/schedule')
}
navigateToNotifications(): void {
this.router.push('/notifications')
}
navigateToHistory(): void {
this.router.push('/history')
}
navigateToLogs(): void {
this.router.push('/logs')
}
navigateToSettings(): void {
this.router.push('/settings')
}
async checkSystemStatus(): Promise<void> {
this.isCheckingStatus = true
try {
// Refresh plugin status
await this.refreshSystemStatus()
} catch (error) {
console.error('❌ Status check failed:', error)
this.appStore.setError('Failed to check system status: ' + (error as Error).message)
} finally {
this.isCheckingStatus = false
}
}
async refreshSystemStatus(): Promise<void> {
try {
if (window.DailyNotification) {
const status = await window.DailyNotification.checkStatus()
this.appStore.setNotificationStatus(status)
console.log('✅ System status refreshed:', status)
}
} catch (error) {
console.error('❌ Failed to refresh system status:', error)
throw error
}
}
}
</script>
<style scoped>
.home-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.welcome-section {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
backdrop-filter: blur(10px);
}
.welcome-title {
margin: 0 0 12px 0;
font-size: 32px;
font-weight: 700;
color: white;
}
.welcome-subtitle {
margin: 0 0 20px 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.platform-info {
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
.platform-badge {
padding: 6px 12px;
border-radius: 16px;
font-size: 14px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.platform-android {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.platform-ios {
background: rgba(0, 122, 255, 0.2);
color: #007aff;
border: 1px solid rgba(0, 122, 255, 0.3);
}
.platform-electron {
background: rgba(138, 43, 226, 0.2);
color: #8a2be2;
border: 1px solid rgba(138, 43, 226, 0.3);
}
.platform-web {
background: rgba(255, 152, 0, 0.2);
color: #ff9800;
border: 1px solid rgba(255, 152, 0, 0.3);
}
.status-badge {
padding: 6px 12px;
border-radius: 16px;
font-size: 14px;
font-weight: 500;
}
.status-badge.ready {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.status-badge.not-ready {
background: rgba(244, 67, 54, 0.2);
color: #f44336;
border: 1px solid rgba(244, 67, 54, 0.3);
}
.status-badge.unknown {
background: rgba(158, 158, 158, 0.2);
color: #9e9e9e;
border: 1px solid rgba(158, 158, 158, 0.3);
}
.quick-actions {
margin-bottom: 40px;
}
.section-title {
margin: 0 0 20px 0;
font-size: 24px;
font-weight: 600;
color: white;
}
.action-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
}
.system-status {
margin-bottom: 40px;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.home-view {
padding: 16px;
}
.welcome-section {
padding: 24px 16px;
}
.welcome-title {
font-size: 24px;
}
.action-grid {
grid-template-columns: 1fr;
}
.platform-info {
flex-direction: column;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,475 @@
<!--
/**
* Logs View - Platform Neutral Log Display
*
* View and copy system logs with clipboard functionality
*
* @author Matthew Raymer
* @version 1.0.0
*/
-->
<template>
<div class="logs-view">
<div class="view-header">
<h1 class="page-title">📋 System Logs</h1>
<p class="page-subtitle">View and copy DailyNotification plugin logs</p>
</div>
<div class="logs-controls">
<button
class="control-button refresh-button"
@click="refreshLogs"
:disabled="isRefreshing"
>
<span v-if="isRefreshing">🔄</span>
<span v-else>🔄</span>
{{ isRefreshing ? 'Refreshing...' : 'Refresh Logs' }}
</button>
<button
class="control-button copy-button"
@click="copyLogsToClipboard"
:disabled="!hasLogs || isCopying"
>
<span v-if="isCopying">📋</span>
<span v-else>📋</span>
{{ isCopying ? 'Copying...' : 'Copy to Clipboard' }}
</button>
<button
class="control-button clear-button"
@click="clearLogs"
:disabled="!hasLogs"
>
🗑 Clear Logs
</button>
</div>
<div class="logs-container">
<div v-if="hasLogs" class="logs-content">
<div class="logs-header">
<span class="logs-count">{{ logs.length }} log entries</span>
<span class="last-updated">Last updated: {{ formatTimestamp(lastUpdated) }}</span>
</div>
<div class="log-entries">
<div
v-for="(log, index) in logs"
:key="index"
:class="['log-entry', `log-level-${log.level.toLowerCase()}`]"
>
<span class="log-timestamp">{{ formatTimestamp(log.timestamp) }}</span>
<span class="log-level">[{{ log.level }}]</span>
<span class="log-tag">{{ log.tag }}:</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
</div>
<div v-else class="no-logs">
<span class="empty-icon">📜</span>
<p class="empty-message">No logs available. Click "Refresh Logs" to fetch them.</p>
</div>
</div>
<div v-if="feedbackMessage" :class="['feedback-message', feedbackType]">
{{ feedbackMessage }}
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
import { Capacitor } from '@capacitor/core'
import { format } from 'date-fns'
import { useAppStore } from '@/stores/app'
interface LogEntry {
timestamp: number
level: string
tag: string
message: string
}
@Component
export default class LogsView extends Vue {
private appStore = useAppStore()
logs: LogEntry[] = []
isRefreshing = false
isCopying = false
lastUpdated: number = Date.now()
feedbackMessage: string | null = null
feedbackType: 'success' | 'error' = 'success'
get hasLogs(): boolean {
return this.logs.length > 0
}
mounted(): void {
this.refreshLogs()
}
async refreshLogs(): Promise<void> {
if (!Capacitor.isNativePlatform()) {
this.appStore.setError('Log fetching is only available on native platforms.')
return
}
this.isRefreshing = true
this.clearFeedback()
try {
// For now, create mock logs - replace with actual plugin call
// Example: const rawLogs = await DailyNotificationPlugin.getLogs();
const mockLogs: LogEntry[] = [
{ timestamp: Date.now() - 5000, level: 'DEBUG', tag: 'DailyNotificationPlugin', message: 'Plugin initialized' },
{ timestamp: Date.now() - 4000, level: 'INFO', tag: 'DailyNotificationScheduler', message: 'Notification scheduled for 09:00' },
{ timestamp: Date.now() - 3000, level: 'WARN', tag: 'DailyNotificationWorker', message: 'JIT freshness check skipped: content too fresh' },
{ timestamp: Date.now() - 2000, level: 'ERROR', tag: 'DailyNotificationStorage', message: 'Failed to save notification: disk full' },
{ timestamp: Date.now() - 1000, level: 'INFO', tag: 'DailyNotificationReceiver', message: 'Received NOTIFICATION intent' }
]
this.logs = mockLogs.sort((a, b) => a.timestamp - b.timestamp)
this.lastUpdated = Date.now()
this.showFeedback(`✅ Fetched ${this.logs.length} log entries.`, 'success')
} catch (error) {
console.error('❌ Failed to refresh logs:', error)
this.appStore.setError('Failed to fetch logs: ' + (error as Error).message)
this.showFeedback('❌ Failed to fetch logs.', 'error')
} finally {
this.isRefreshing = false
}
}
async copyLogsToClipboard(): Promise<void> {
if (!this.hasLogs) {
this.showFeedback('No logs to copy.', 'error')
return
}
this.isCopying = true
this.clearFeedback()
const logsText = this.formatLogsForCopy()
try {
if (Capacitor.isNativePlatform()) {
// Use Capacitor Clipboard plugin if available
// Assuming Clipboard plugin is installed and configured
await window.Capacitor.Plugins.Clipboard.write({
string: logsText
})
} else {
// Web clipboard API
await navigator.clipboard.writeText(logsText)
}
this.showFeedback(`✅ Copied ${this.logs.length} log entries to clipboard!`, 'success')
} catch (error) {
console.error('❌ Failed to copy logs:', error)
this.appStore.setError('Failed to copy logs: ' + (error as Error).message)
this.showFeedback('❌ Failed to copy logs.', 'error')
} finally {
this.isCopying = false
}
}
clearLogs(): void {
this.logs = []
this.clearFeedback()
this.showFeedback('🗑️ Logs cleared.', 'success')
}
formatTimestamp(timestamp: number): string {
return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss')
}
formatLogsForCopy(): string {
let formattedText = `DailyNotification Plugin Logs\n`
formattedText += `Generated: ${format(new Date(), 'MM/dd/yyyy, h:mm:ss a')}\n`
formattedText += `Total Entries: ${this.logs.length}\n\n`
this.logs.forEach(log => {
formattedText += `${this.formatTimestamp(log.timestamp)} ${log.level}/${log.tag}: ${log.message}\n`
})
return formattedText
}
showFeedback(message: string, type: 'success' | 'error'): void {
this.feedbackMessage = message
this.feedbackType = type
setTimeout(() => {
this.feedbackMessage = null
}, 3000) // Hide after 3 seconds
}
clearFeedback(): void {
this.feedbackMessage = null
this.feedbackType = 'success'
}
}
</script>
<style scoped>
.logs-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.logs-controls {
display: flex;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
justify-content: center;
}
.control-button {
padding: 12px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
}
.refresh-button {
background: #2196f3;
color: white;
}
.refresh-button:hover:not(:disabled) {
background: #1976d2;
}
.copy-button {
background: #ff9800;
color: white;
}
.copy-button:hover:not(:disabled) {
background: #f57c00;
}
.clear-button {
background: #f44336;
color: white;
}
.clear-button:hover:not(:disabled) {
background: #d32f2f;
}
.control-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.logs-container {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
backdrop-filter: blur(10px);
overflow: hidden;
}
.logs-content {
padding: 20px;
}
.logs-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.logs-count {
font-size: 14px;
font-weight: 500;
color: rgba(255, 255, 255, 0.8);
}
.last-updated {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.log-entries {
max-height: 400px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
}
.log-entry {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 8px 12px;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
}
.log-level-debug {
background: rgba(33, 150, 243, 0.1);
border-left: 3px solid #2196f3;
}
.log-level-info {
background: rgba(76, 175, 80, 0.1);
border-left: 3px solid #4caf50;
}
.log-level-warn {
background: rgba(255, 152, 0, 0.1);
border-left: 3px solid #ff9800;
}
.log-level-error {
background: rgba(244, 67, 54, 0.1);
border-left: 3px solid #f44336;
}
.log-timestamp {
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
flex-shrink: 0;
}
.log-level {
font-weight: 600;
flex-shrink: 0;
min-width: 60px;
}
.log-level-debug .log-level {
color: #2196f3;
}
.log-level-info .log-level {
color: #4caf50;
}
.log-level-warn .log-level {
color: #ff9800;
}
.log-level-error .log-level {
color: #f44336;
}
.log-tag {
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
flex-shrink: 0;
}
.log-message {
color: white;
flex: 1;
}
.no-logs {
padding: 40px 20px;
text-align: center;
color: rgba(255, 255, 255, 0.6);
}
.empty-icon {
font-size: 48px;
display: block;
margin-bottom: 16px;
}
.empty-message {
margin: 0;
font-size: 16px;
}
.feedback-message {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
z-index: 1000;
backdrop-filter: blur(10px);
}
.feedback-message.success {
background: rgba(76, 175, 80, 0.9);
color: white;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.feedback-message.error {
background: rgba(244, 67, 54, 0.9);
color: white;
border: 1px solid rgba(244, 67, 54, 0.3);
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.logs-view {
padding: 16px;
}
.logs-controls {
flex-direction: column;
align-items: stretch;
}
.control-button {
justify-content: center;
}
.logs-header {
flex-direction: column;
gap: 8px;
align-items: flex-start;
}
.log-entry {
flex-direction: column;
gap: 4px;
}
.log-timestamp {
font-size: 11px;
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="not-found-view">
<div class="view-header">
<h1 class="page-title">404</h1>
<p class="page-subtitle">Page not found</p>
</div>
<div class="placeholder-content">
<p>The page you're looking for doesn't exist.</p>
<router-link to="/" class="home-link"> Back to Home</router-link>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
@Component
export default class NotFoundView extends Vue {}
</script>
<style scoped>
.not-found-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.placeholder-content {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
.home-link {
display: inline-block;
margin-top: 20px;
padding: 12px 24px;
background: #1976d2;
color: white;
text-decoration: none;
border-radius: 8px;
transition: background 0.2s ease;
}
.home-link:hover {
background: #1565c0;
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<div class="notifications-view">
<div class="view-header">
<h1 class="page-title">🔔 Notifications</h1>
<p class="page-subtitle">Manage scheduled notifications</p>
</div>
<div class="placeholder-content">
<p>Notifications management coming soon...</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
@Component
export default class NotificationsView extends Vue {}
</script>
<style scoped>
.notifications-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.placeholder-content {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
</style>

View File

@@ -0,0 +1,180 @@
<!--
/**
* Schedule View - Platform Neutral Scheduling Interface
*
* @author Matthew Raymer
* @version 1.0.0
*/
-->
<template>
<div class="schedule-view">
<div class="view-header">
<h1 class="page-title">📅 Schedule Notification</h1>
<p class="page-subtitle">Schedule a new daily notification</p>
</div>
<div class="schedule-form">
<div class="form-group">
<label class="form-label">Notification Time</label>
<input type="time" class="form-input" v-model="scheduleTime" />
</div>
<div class="form-group">
<label class="form-label">Title</label>
<input type="text" class="form-input" v-model="notificationTitle" placeholder="Daily Update" />
</div>
<div class="form-group">
<label class="form-label">Message</label>
<textarea class="form-textarea" v-model="notificationMessage" placeholder="Your daily notification message"></textarea>
</div>
<div class="form-actions">
<button class="action-button primary" @click="scheduleNotification" :disabled="isScheduling">
{{ isScheduling ? 'Scheduling...' : 'Schedule Notification' }}
</button>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
@Component
export default class ScheduleView extends Vue {
scheduleTime = '09:00'
notificationTitle = 'Daily Update'
notificationMessage = 'Your daily notification is ready!'
isScheduling = false
async scheduleNotification(): Promise<void> {
this.isScheduling = true
try {
// TODO: Implement actual scheduling
console.log('Scheduling notification:', {
time: this.scheduleTime,
title: this.notificationTitle,
message: this.notificationMessage
})
// Mock success
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('✅ Notification scheduled successfully')
} catch (error) {
console.error('❌ Failed to schedule notification:', error)
} finally {
this.isScheduling = false
}
}
}
</script>
<style scoped>
.schedule-view {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.schedule-form {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 24px;
backdrop-filter: blur(10px);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: white;
}
.form-input, .form-textarea {
width: 100%;
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 14px;
transition: border-color 0.2s ease;
}
.form-input:focus, .form-textarea:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.4);
}
.form-textarea {
min-height: 80px;
resize: vertical;
}
.form-actions {
margin-top: 24px;
}
.action-button {
width: 100%;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.action-button.primary {
background: #1976d2;
color: white;
}
.action-button.primary:hover:not(:disabled) {
background: #1565c0;
}
.action-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.schedule-view {
padding: 16px;
}
.schedule-form {
padding: 20px;
}
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<div class="settings-view">
<div class="view-header">
<h1 class="page-title"> Settings</h1>
<p class="page-subtitle">Configure app preferences</p>
</div>
<div class="placeholder-content">
<p>Settings view coming soon...</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
@Component
export default class SettingsView extends Vue {}
</script>
<style scoped>
.settings-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.placeholder-content {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<div class="status-view">
<div class="view-header">
<h1 class="page-title">📊 Status</h1>
<p class="page-subtitle">System status and diagnostics</p>
</div>
<div class="placeholder-content">
<p>Status view coming soon...</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
@Component
export default class StatusView extends Vue {}
</script>
<style scoped>
.status-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.view-header {
text-align: center;
margin-bottom: 30px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
color: white;
}
.page-subtitle {
margin: 0;
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.placeholder-content {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
</style>