Rework day progress bar to use block-duration time instead of wall clock
Both the TV and parent dashboard progress bars now calculate % complete based on total scheduled block time vs. remaining block + break time, so the bar only advances while blocks are actively being worked. TV bar labels changed to "🟢 Start" and "Finish 🏁". Parent dashboard shows first block's scheduled start time on the left and a live estimated finish time (now + remaining block/break time) on the right. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,16 +47,16 @@
|
||||
<div class="card session-card">
|
||||
<div class="card-title">Today's Session</div>
|
||||
<div v-if="scheduleStore.session">
|
||||
<div v-if="scheduleStore.dayStartTime && scheduleStore.dayEndTime" class="day-progress-section">
|
||||
<div v-if="scheduleStore.blocks.length > 0" class="day-progress-section">
|
||||
<div class="day-progress-header">
|
||||
<span class="badge-active">Active</span>
|
||||
<span class="day-progress-pct">{{ dayProgressPercent }}%</span>
|
||||
</div>
|
||||
<ProgressBar :percent="dayProgressPercent" />
|
||||
<div class="day-progress-times">
|
||||
<span>{{ formatDayTime(scheduleStore.dayStartTime) }}</span>
|
||||
<span>{{ firstBlockStartTime }}</span>
|
||||
<span>{{ currentTimeDisplay }}</span>
|
||||
<span>{{ formatDayTime(scheduleStore.dayEndTime) }}</span>
|
||||
<span>{{ estimatedFinishTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="session-info">
|
||||
@@ -73,6 +73,32 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="session-actions">
|
||||
<div class="session-actions-left">
|
||||
<button
|
||||
class="btn-sm"
|
||||
v-if="scheduleStore.session.current_block_id && !scheduleStore.isPaused"
|
||||
@click="sendAction('pause')"
|
||||
>Pause</button>
|
||||
<button
|
||||
class="btn-sm btn-start"
|
||||
v-if="scheduleStore.isPaused && scheduleStore.blockElapsedOffset === 0 && scheduleStore.session.current_block_id"
|
||||
@click="scheduleStore.startCurrentBlock(scheduleStore.session.id)"
|
||||
>Start</button>
|
||||
<button
|
||||
class="btn-sm"
|
||||
v-if="scheduleStore.isPaused && scheduleStore.blockElapsedOffset > 0"
|
||||
@click="sendAction('resume')"
|
||||
>Resume</button>
|
||||
<button
|
||||
class="btn-sm"
|
||||
v-if="scheduleStore.session.current_block_id"
|
||||
@click="scheduleStore.resetCurrentBlock(scheduleStore.session.id)"
|
||||
>Reset</button>
|
||||
</div>
|
||||
<button class="btn-sm btn-danger" @click="sendAction('complete')">End Day</button>
|
||||
</div>
|
||||
|
||||
<!-- Break Time section -->
|
||||
<div
|
||||
v-if="scheduleStore.currentBlock?.break_time_enabled"
|
||||
@@ -117,32 +143,6 @@
|
||||
>Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="session-actions">
|
||||
<div class="session-actions-left">
|
||||
<button
|
||||
class="btn-sm"
|
||||
v-if="scheduleStore.session.current_block_id && !scheduleStore.isPaused"
|
||||
@click="sendAction('pause')"
|
||||
>Pause</button>
|
||||
<button
|
||||
class="btn-sm btn-start"
|
||||
v-if="scheduleStore.isPaused && scheduleStore.blockElapsedOffset === 0 && scheduleStore.session.current_block_id"
|
||||
@click="scheduleStore.startCurrentBlock(scheduleStore.session.id)"
|
||||
>Start</button>
|
||||
<button
|
||||
class="btn-sm"
|
||||
v-if="scheduleStore.isPaused && scheduleStore.blockElapsedOffset > 0"
|
||||
@click="sendAction('resume')"
|
||||
>Resume</button>
|
||||
<button
|
||||
class="btn-sm"
|
||||
v-if="scheduleStore.session.current_block_id"
|
||||
@click="scheduleStore.resetCurrentBlock(scheduleStore.session.id)"
|
||||
>Reset</button>
|
||||
</div>
|
||||
<button class="btn-sm btn-danger" @click="sendAction('complete')">End Day</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-session">
|
||||
<p>No active session.</p>
|
||||
@@ -221,12 +221,6 @@ const templates = ref([])
|
||||
const now = ref(new Date())
|
||||
setInterval(() => { now.value = new Date() }, 1000)
|
||||
|
||||
function timeStrToMinutes(str) {
|
||||
if (!str) return null
|
||||
const [h, m] = str.split(':').map(Number)
|
||||
return h * 60 + m
|
||||
}
|
||||
|
||||
function formatDayTime(str) {
|
||||
if (!str) return ''
|
||||
const [h, m] = str.split(':').map(Number)
|
||||
@@ -240,11 +234,17 @@ const currentTimeDisplay = computed(() =>
|
||||
)
|
||||
|
||||
const dayProgressPercent = computed(() => {
|
||||
const start = timeStrToMinutes(scheduleStore.dayStartTime)
|
||||
const end = timeStrToMinutes(scheduleStore.dayEndTime)
|
||||
if (start === null || end === null || end <= start) return 0
|
||||
const nowMin = now.value.getHours() * 60 + now.value.getMinutes()
|
||||
return Math.max(0, Math.min(100, Math.round((nowMin - start) / (end - start) * 100)))
|
||||
const allBlocks = scheduleStore.blocks
|
||||
if (!allBlocks.length) return 0
|
||||
const totalSeconds = allBlocks.reduce((sum, b) => sum + (b.duration_minutes || 0) * 60, 0)
|
||||
if (totalSeconds === 0) return 0
|
||||
const remainingSeconds = allBlocks.reduce((sum, b) => {
|
||||
if (scheduleStore.completedBlockIds.includes(b.id)) return sum
|
||||
const blockTotal = (b.duration_minutes || 0) * 60
|
||||
const elapsed = blockElapsed(b)
|
||||
return sum + Math.max(0, blockTotal - elapsed)
|
||||
}, 0)
|
||||
return Math.max(0, Math.min(100, Math.round((totalSeconds - remainingSeconds) / totalSeconds * 100)))
|
||||
})
|
||||
|
||||
let wsDisconnect = null
|
||||
@@ -291,6 +291,36 @@ function blockElapsed(block) {
|
||||
return scheduleStore.blockElapsedCache[block.id] || 0
|
||||
}
|
||||
|
||||
function breakElapsed(block) {
|
||||
if (scheduleStore.isBreakMode && block.id === scheduleStore.session?.current_block_id && scheduleStore.breakStartedAt) {
|
||||
return scheduleStore.breakElapsedOffset + Math.floor((now.value - scheduleStore.breakStartedAt) / 1000)
|
||||
}
|
||||
return scheduleStore.breakElapsedCache[block.id] || 0
|
||||
}
|
||||
|
||||
const firstBlockStartTime = computed(() => {
|
||||
const first = scheduleStore.blocks[0]
|
||||
return first?.time_start ? formatDayTime(first.time_start) : ''
|
||||
})
|
||||
|
||||
const estimatedFinishTime = computed(() => {
|
||||
const allBlocks = scheduleStore.blocks
|
||||
if (!allBlocks.length) return ''
|
||||
let remainingSeconds = 0
|
||||
for (const b of allBlocks) {
|
||||
if (scheduleStore.completedBlockIds.includes(b.id)) continue
|
||||
remainingSeconds += Math.max(0, (b.duration_minutes || 0) * 60 - blockElapsed(b))
|
||||
if (b.break_time_enabled && b.break_time_minutes) {
|
||||
remainingSeconds += Math.max(0, b.break_time_minutes * 60 - breakElapsed(b))
|
||||
}
|
||||
}
|
||||
const finish = new Date(now.value.getTime() + remainingSeconds * 1000)
|
||||
const h = finish.getHours()
|
||||
const period = h >= 12 ? 'PM' : 'AM'
|
||||
const hour = h % 12 || 12
|
||||
return `${hour}:${String(finish.getMinutes()).padStart(2, '0')} ${period}`
|
||||
})
|
||||
|
||||
function selectBlock(block) {
|
||||
if (!scheduleStore.session) return
|
||||
// Clicking the current block does nothing — use Start/Pause/Resume buttons
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
</header>
|
||||
|
||||
<!-- Day progress bar — full width, always visible when day hours are set -->
|
||||
<div class="tv-day-progress" v-if="scheduleStore.dayStartTime && scheduleStore.dayEndTime">
|
||||
<div class="tv-day-progress" v-if="scheduleStore.blocks.length > 0">
|
||||
<div class="tv-day-progress-meta">
|
||||
<span class="tv-day-start">{{ formatDayTime(scheduleStore.dayStartTime) }}</span>
|
||||
<span class="tv-day-start">🟢 Start</span>
|
||||
<span class="tv-day-pct">{{ dayProgressPercent }}% through the day</span>
|
||||
<span class="tv-day-end">{{ formatDayTime(scheduleStore.dayEndTime) }}</span>
|
||||
<span class="tv-day-end">Finish 🏁</span>
|
||||
</div>
|
||||
<ProgressBar :percent="dayProgressPercent" />
|
||||
</div>
|
||||
@@ -167,12 +167,6 @@ const dateDisplay = computed(() =>
|
||||
)
|
||||
|
||||
// Day progress helpers
|
||||
function timeStrToMinutes(str) {
|
||||
if (!str) return null
|
||||
const [h, m] = str.split(':').map(Number)
|
||||
return h * 60 + m
|
||||
}
|
||||
|
||||
function formatDayTime(str) {
|
||||
if (!str) return ''
|
||||
const [h, m] = str.split(':').map(Number)
|
||||
@@ -182,11 +176,17 @@ function formatDayTime(str) {
|
||||
}
|
||||
|
||||
const dayProgressPercent = computed(() => {
|
||||
const start = timeStrToMinutes(scheduleStore.dayStartTime)
|
||||
const end = timeStrToMinutes(scheduleStore.dayEndTime)
|
||||
if (start === null || end === null || end <= start) return 0
|
||||
const nowMin = now.value.getHours() * 60 + now.value.getMinutes()
|
||||
return Math.max(0, Math.min(100, Math.round((nowMin - start) / (end - start) * 100)))
|
||||
const allBlocks = scheduleStore.blocks
|
||||
if (!allBlocks.length) return 0
|
||||
const totalSeconds = allBlocks.reduce((sum, b) => sum + (b.duration_minutes || 0) * 60, 0)
|
||||
if (totalSeconds === 0) return 0
|
||||
const remainingSeconds = allBlocks.reduce((sum, b) => {
|
||||
if (scheduleStore.completedBlockIds.includes(b.id)) return sum
|
||||
const blockTotal = (b.duration_minutes || 0) * 60
|
||||
const elapsed = blockElapsed(b)
|
||||
return sum + Math.max(0, blockTotal - elapsed)
|
||||
}, 0)
|
||||
return Math.max(0, Math.min(100, Math.round((totalSeconds - remainingSeconds) / totalSeconds * 100)))
|
||||
})
|
||||
|
||||
// Countdown to first block
|
||||
|
||||
Reference in New Issue
Block a user