132 lines
3.1 KiB
Vue
132 lines
3.1 KiB
Vue
<template>
|
||
<div
|
||
class="block-card"
|
||
:class="{
|
||
'is-current': isCurrent,
|
||
'is-completed': isCompleted,
|
||
compact,
|
||
}"
|
||
>
|
||
<div class="block-indicator" :style="{ background: subjectColor }"></div>
|
||
<div class="block-body">
|
||
<div class="block-title">
|
||
<span>{{ subjectName || block.label || 'Block' }}</span>
|
||
<span v-if="subjectName && block.label" class="block-label-suffix"> - {{ block.label }}</span>
|
||
</div>
|
||
<div class="block-time">
|
||
{{ formatTime(block.time_start) }} – {{ formatTime(block.time_end) }}
|
||
<span class="block-duration" :class="{ 'block-duration-custom': block.duration_minutes != null }">
|
||
· {{ durationLabel }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="block-status" v-if="isCompleted">✓</div>
|
||
<div class="block-status active" v-else-if="isCurrent">▶</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
|
||
const props = defineProps({
|
||
block: { type: Object, required: true },
|
||
isCurrent: { type: Boolean, default: false },
|
||
isCompleted: { type: Boolean, default: false },
|
||
compact: { type: Boolean, default: false },
|
||
})
|
||
|
||
function formatTime(str) {
|
||
if (!str) return ''
|
||
const [h, m] = str.split(':').map(Number)
|
||
const period = h >= 12 ? 'PM' : 'AM'
|
||
const hour = h % 12 || 12
|
||
return `${hour}:${String(m).padStart(2, '0')} ${period}`
|
||
}
|
||
|
||
const subjectColor = computed(() => props.block.subject?.color || '#475569')
|
||
const subjectName = computed(() => props.block.subject?.name || null)
|
||
|
||
const durationLabel = computed(() => {
|
||
if (props.block.duration_minutes != null) return `${props.block.duration_minutes} min`
|
||
const start = props.block.time_start
|
||
const end = props.block.time_end
|
||
if (start && end) {
|
||
const [sh, sm] = start.split(':').map(Number)
|
||
const [eh, em] = end.split(':').map(Number)
|
||
const mins = (eh * 60 + em) - (sh * 60 + sm)
|
||
if (mins > 0) return `${mins} min`
|
||
}
|
||
return ''
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.block-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
background: #1e293b;
|
||
border-radius: 0.75rem;
|
||
padding: 0.75rem 1rem;
|
||
border: 1px solid transparent;
|
||
transition: all 0.2s;
|
||
cursor: default;
|
||
}
|
||
|
||
.block-card.is-current {
|
||
border-color: #4f46e5;
|
||
background: #1e1b4b;
|
||
}
|
||
|
||
.block-card.is-completed {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.block-card.compact {
|
||
padding: 0.5rem 0.75rem;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
.block-indicator {
|
||
width: 4px;
|
||
height: 36px;
|
||
border-radius: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.block-card.compact .block-indicator {
|
||
height: 24px;
|
||
}
|
||
|
||
.block-body { flex: 1; min-width: 0; }
|
||
|
||
.block-title {
|
||
font-size: 0.95rem;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.block-card.compact .block-title { font-size: 0.85rem; }
|
||
|
||
.block-time {
|
||
font-size: 0.75rem;
|
||
color: #64748b;
|
||
margin-top: 0.15rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.block-label-suffix { font-weight: 400; color: #94a3b8; }
|
||
.block-duration { color: #475569; }
|
||
.block-duration-custom { color: #818cf8; }
|
||
|
||
.block-status {
|
||
font-size: 0.85rem;
|
||
color: #64748b;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.block-status.active { color: #818cf8; }
|
||
</style>
|