current init
This commit is contained in:
185
frontend/src/components/YearPicker.vue
Normal file
185
frontend/src/components/YearPicker.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const monthNames = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
|
||||
|
||||
function buildCells(startIndex, count) {
|
||||
const now = new Date()
|
||||
const currentMonth = now.getMonth()
|
||||
const currentYear = now.getFullYear()
|
||||
const result = []
|
||||
|
||||
let row = 2
|
||||
let col = 2
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const idx = startIndex + i
|
||||
const m = (currentMonth + idx) % 12
|
||||
const y = currentYear + Math.floor((currentMonth + idx) / 12)
|
||||
const daysInMonth = new Date(y, m + 1, 0).getDate()
|
||||
const firstDayOffset = (new Date(y, m, 1).getDay() + 6) % 7
|
||||
|
||||
result.push({
|
||||
type: 'label',
|
||||
name: monthNames[m],
|
||||
row,
|
||||
col: 1,
|
||||
key: `label-${y}-${m}`,
|
||||
})
|
||||
|
||||
if (i === 0) {
|
||||
row++
|
||||
col = 2 + firstDayOffset
|
||||
} else {
|
||||
const newCol = 2 + firstDayOffset
|
||||
if (newCol < col) {
|
||||
row++
|
||||
}
|
||||
col = newCol
|
||||
}
|
||||
|
||||
for (let d = 1; d <= daysInMonth; d++) {
|
||||
result.push({
|
||||
type: 'day',
|
||||
month: m + 1,
|
||||
day: d,
|
||||
row,
|
||||
col,
|
||||
key: `d-${y}-${m}-${d}`,
|
||||
})
|
||||
col++
|
||||
if (col > 8) {
|
||||
col = 2
|
||||
row++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const totalMonths = 12 - new Date().getMonth()
|
||||
const leftCount = Math.ceil(totalMonths / 2)
|
||||
const rightCount = totalMonths - leftCount
|
||||
|
||||
const leftCells = computed(() => buildCells(0, leftCount))
|
||||
const rightCells = computed(() => buildCells(leftCount, rightCount))
|
||||
|
||||
function isSelected(month, day) {
|
||||
return props.modelValue.some(yd => yd.month === month && yd.day === day)
|
||||
}
|
||||
|
||||
function toggle(month, day) {
|
||||
const current = [...props.modelValue]
|
||||
const index = current.findIndex(yd => yd.month === month && yd.day === day)
|
||||
if (index === -1) {
|
||||
current.push({ month, day })
|
||||
} else {
|
||||
current.splice(index, 1)
|
||||
}
|
||||
emit('update:modelValue', current)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="year-picker-columns">
|
||||
<div v-for="(cells, side) in [leftCells, rightCells]" :key="side" class="year-picker">
|
||||
<div class="header" style="grid-row:1;grid-column:1"></div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:2">Mo</div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:3">Di</div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:4">Mi</div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:5">Do</div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:6">Fr</div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:7">Sa</div>
|
||||
<div class="weekday-header" style="grid-row:1;grid-column:8">So</div>
|
||||
|
||||
<template v-for="cell in cells" :key="cell.key">
|
||||
<div
|
||||
v-if="cell.type === 'label'"
|
||||
class="month-label"
|
||||
:style="{ gridRow: cell.row, gridColumn: cell.col }"
|
||||
>
|
||||
{{ cell.name }}
|
||||
</div>
|
||||
<label
|
||||
v-else
|
||||
class="day-option"
|
||||
:class="{ active: isSelected(cell.month, cell.day) }"
|
||||
:style="{ gridRow: cell.row, gridColumn: cell.col }"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="isSelected(cell.month, cell.day)"
|
||||
@change="toggle(cell.month, cell.day)"
|
||||
class="sr-only"
|
||||
/>
|
||||
{{ cell.day }}
|
||||
</label>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.year-picker-columns {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.year-picker {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 2.5rem repeat(7, 1fr);
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.weekday-header {
|
||||
font-size: 0.6rem;
|
||||
text-align: center;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.month-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-h);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.day-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.65rem;
|
||||
color: var(--text);
|
||||
transition: all 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.day-option.active {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user