Files
thetool/public/mobile/modules/lager/shippingnote/DatePicker.js
2026-01-17 12:48:08 +00:00

283 lines
12 KiB
JavaScript

/**
* DatePicker Component
*
* Beautiful mobile date picker with bottom sheet modal.
* Features quick buttons (Heute, Gestern) and calendar grid.
*/
export default {
name: 'DatePicker',
emits: ['update:modelValue', 'close'],
props: {
modelValue: {
type: String,
default: null
},
show: {
type: Boolean,
default: false
}
},
setup(props, { emit }) {
const { ref, computed, watch } = Vue;
// Current calendar view month/year
const viewDate = ref(new Date());
// Initialize view date when opened
watch(() => props.show, (newVal) => {
if (newVal && props.modelValue) {
viewDate.value = new Date(props.modelValue);
} else if (newVal) {
viewDate.value = new Date();
}
});
// German weekday names (short)
const weekDays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
// German month names
const monthNames = [
'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
];
const shortMonthNames = [
'Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'
];
const weekDayNames = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
// Format date for display
const formatDisplayDate = (dateStr) => {
if (!dateStr) return 'Datum wählen';
const date = new Date(dateStr);
const dayName = weekDayNames[date.getDay()];
const day = date.getDate();
const month = shortMonthNames[date.getMonth()];
const year = date.getFullYear();
return `${dayName}, ${day}. ${month} ${year}`;
};
// Current month/year display
const currentMonthYear = computed(() => {
const month = monthNames[viewDate.value.getMonth()];
const year = viewDate.value.getFullYear();
return `${month} ${year}`;
});
// Get calendar days for current view
const calendarDays = computed(() => {
const year = viewDate.value.getFullYear();
const month = viewDate.value.getMonth();
// First day of month
const firstDay = new Date(year, month, 1);
// Last day of month
const lastDay = new Date(year, month + 1, 0);
// Day of week for first day (0=Sun, convert to 0=Mon)
let startDay = firstDay.getDay() - 1;
if (startDay < 0) startDay = 6;
const days = [];
// Add empty slots for days before first of month
for (let i = 0; i < startDay; i++) {
days.push({ day: null, date: null });
}
// Add days of month
for (let d = 1; d <= lastDay.getDate(); d++) {
const date = new Date(year, month, d);
const dateStr = formatDateISO(date);
days.push({
day: d,
date: dateStr,
isToday: isToday(date),
isSelected: dateStr === props.modelValue
});
}
return days;
});
// Check if date is today
const isToday = (date) => {
const today = new Date();
return date.toDateString() === today.toDateString();
};
// Format date as ISO string (YYYY-MM-DD)
const formatDateISO = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// Quick date helpers
const getToday = () => formatDateISO(new Date());
const getYesterday = () => {
const d = new Date();
d.setDate(d.getDate() - 1);
return formatDateISO(d);
};
// Navigation
const prevMonth = () => {
viewDate.value = new Date(viewDate.value.getFullYear(), viewDate.value.getMonth() - 1, 1);
};
const nextMonth = () => {
viewDate.value = new Date(viewDate.value.getFullYear(), viewDate.value.getMonth() + 1, 1);
};
// Selection
const selectDate = (dateStr) => {
if (!dateStr) return;
emit('update:modelValue', dateStr);
emit('close');
};
const selectToday = () => selectDate(getToday());
const selectYesterday = () => selectDate(getYesterday());
const close = () => {
emit('close');
};
return {
viewDate,
weekDays,
currentMonthYear,
calendarDays,
formatDisplayDate,
prevMonth,
nextMonth,
selectDate,
selectToday,
selectYesterday,
close,
getToday,
getYesterday
};
},
template: `
<!-- Bottom Sheet Modal -->
<teleport to="body">
<transition name="fade">
<div v-if="show" class="fixed inset-0 z-50">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/50" @click="close"></div>
<!-- Sheet -->
<transition name="slide-up-sheet">
<div v-if="show" class="absolute bottom-0 left-0 right-0 bg-white dark:bg-slate-800 rounded-t-2xl shadow-xl max-h-[80vh] overflow-hidden">
<!-- Handle -->
<div class="flex justify-center pt-2 pb-1">
<div class="w-10 h-1 bg-slate-300 dark:bg-slate-600 rounded-full"></div>
</div>
<!-- Header -->
<div class="px-4 pb-3 border-b border-slate-100 dark:border-slate-700">
<h3 class="text-lg font-semibold text-slate-800 dark:text-white text-center">
Datum wählen
</h3>
</div>
<!-- Quick Buttons -->
<div class="px-4 py-3 flex gap-2">
<button
@click="selectToday"
:class="[
'flex-1 py-2.5 rounded-xl font-medium transition',
modelValue === getToday()
? 'bg-primary text-white'
: 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300'
]"
>
Heute
</button>
<button
@click="selectYesterday"
:class="[
'flex-1 py-2.5 rounded-xl font-medium transition',
modelValue === getYesterday()
? 'bg-primary text-white'
: 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300'
]"
>
Gestern
</button>
</div>
<!-- Month Navigation -->
<div class="px-4 py-2 flex items-center justify-between">
<button
@click="prevMonth"
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-slate-100 dark:hover:bg-slate-700 text-slate-600 dark:text-slate-400"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
<span class="text-base font-semibold text-slate-800 dark:text-white">
{{ currentMonthYear }}
</span>
<button
@click="nextMonth"
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-slate-100 dark:hover:bg-slate-700 text-slate-600 dark:text-slate-400"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
<!-- Calendar Grid -->
<div class="px-4 pb-6">
<!-- Weekday Headers -->
<div class="grid grid-cols-7 gap-1 mb-2">
<div
v-for="day in weekDays"
:key="day"
class="h-8 flex items-center justify-center text-xs font-medium text-slate-500 dark:text-slate-400"
>
{{ day }}
</div>
</div>
<!-- Days Grid -->
<div class="grid grid-cols-7 gap-1">
<button
v-for="(item, idx) in calendarDays"
:key="idx"
@click="selectDate(item.date)"
:disabled="!item.day"
:class="[
'h-10 flex items-center justify-center rounded-full text-sm font-medium transition',
!item.day ? 'invisible' : '',
item.isSelected ? 'bg-primary text-white' : '',
item.isToday && !item.isSelected ? 'ring-2 ring-primary text-primary' : '',
!item.isSelected && !item.isToday && item.day ? 'text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700' : ''
]"
>
{{ item.day }}
</button>
</div>
</div>
<!-- Safe area padding for iOS -->
<div class="h-6"></div>
</div>
</transition>
</div>
</transition>
</teleport>
`
};