Add Meeting system subject and notification system
- Auto-create a locked "Meeting" subject for every user on registration and seed it for all existing users on startup - Meeting subject cannot be deleted or renamed (is_system flag) - 5-minute corner toast warning on Dashboard and TV with live countdown, dismiss button, and 1-minute re-notify if dismissed - At start time: full-screen TV overlay with 30-second auto-dismiss, automatic pause of running block, switch to Meeting block, and auto-start of Meeting timer - Web Audio API chimes: rising on warnings, falling at meeting start - Update README with Meeting subject and notification system docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -189,6 +189,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Meeting alert corner toasts -->
|
||||
<teleport to="body">
|
||||
<div class="meeting-toasts">
|
||||
<transition-group name="toast">
|
||||
<div v-for="alert in meetingAlerts.dashboardAlerts.value" :key="alert.id" class="meeting-toast">
|
||||
<div class="toast-icon">📅</div>
|
||||
<div class="toast-body">
|
||||
<div class="toast-title">Upcoming Meeting</div>
|
||||
<div class="toast-label">{{ alert.label }}</div>
|
||||
<div class="toast-countdown">Starts in {{ meetingAlerts.alertCountdown(alert) }}</div>
|
||||
</div>
|
||||
<button class="toast-dismiss" @click="meetingAlerts.dismissDashboardAlert(alert.id)">✕</button>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -197,6 +214,7 @@ import { ref, onMounted, watch, computed } from 'vue'
|
||||
import { useChildrenStore } from '@/stores/children'
|
||||
import { useScheduleStore } from '@/stores/schedule'
|
||||
import { useWebSocket } from '@/composables/useWebSocket'
|
||||
import { useMeetingAlerts } from '@/composables/useMeetingAlerts'
|
||||
import api from '@/composables/useApi'
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
import ChildSelector from '@/components/ChildSelector.vue'
|
||||
@@ -207,6 +225,10 @@ import TimerDisplay from '@/components/TimerDisplay.vue'
|
||||
const childrenStore = useChildrenStore()
|
||||
const scheduleStore = useScheduleStore()
|
||||
const activeChild = computed(() => childrenStore.activeChild)
|
||||
const meetingAlerts = useMeetingAlerts((blockId) => {
|
||||
if (!scheduleStore.session) return
|
||||
scheduleStore.switchBlock(scheduleStore.session.id, blockId)
|
||||
})
|
||||
|
||||
// Virtual block for break timer (same block but with break duration)
|
||||
const breakBlock = computed(() => {
|
||||
@@ -539,4 +561,39 @@ h1 { font-size: 1.75rem; font-weight: 700; }
|
||||
color: #f1f5f9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Meeting alert toasts */
|
||||
.meeting-toasts {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.meeting-toast {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
background: #1c1a07;
|
||||
border: 1px solid #f59e0b;
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.85rem 1rem;
|
||||
width: 280px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.6);
|
||||
}
|
||||
.toast-icon { font-size: 1.5rem; flex-shrink: 0; }
|
||||
.toast-body { flex: 1; min-width: 0; }
|
||||
.toast-title { font-size: 0.7rem; font-weight: 700; color: #fbbf24; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.15rem; }
|
||||
.toast-label { font-size: 0.95rem; font-weight: 600; color: #f1f5f9; margin-bottom: 0.25rem; }
|
||||
.toast-countdown { font-size: 1.1rem; font-weight: 700; color: #fbbf24; font-variant-numeric: tabular-nums; }
|
||||
.toast-dismiss { background: none; border: none; color: #64748b; cursor: pointer; font-size: 1rem; padding: 0; flex-shrink: 0; line-height: 1; }
|
||||
.toast-dismiss:hover { color: #f1f5f9; }
|
||||
|
||||
.toast-enter-active { transition: all 0.3s ease; }
|
||||
.toast-leave-active { transition: all 0.3s ease; }
|
||||
.toast-enter-from { opacity: 0; transform: translateX(100%); }
|
||||
.toast-leave-to { opacity: 0; transform: translateX(100%); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user