Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <template>
- <div class="datetime-picker">
- <input
- type="text"
- v-model="displayValue"
- @focus="showPicker = true"
- @blur="onBlur"
- @keydown="handleKeyDown"
- @input="handleInput"
- placeholder="DD/MM/YYYY HH:mm"
- class="input-field"
- ref="inputField"
- />
- <div v-if="showPicker" class="picker-dropdown">
- <!-- Date Picker Section -->
- <div class="date-section">
- <div class="header">
- <button @click="prevMonth" class="nav-button"><</button>
- <span class="month-display">{{ monthNames[currentMonth] }} {{ currentYear }}</span>
- <button @click="nextMonth" class="nav-button">></button>
- </div>
- <div class="day-names">
- <span v-for="day in ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']" :key="day">{{ day }}</span>
- </div>
- <div class="days-grid">
- <span
- v-for="day in calendarDays"
- :key="day.date"
- :class="{
- 'other-month': !day.isCurrentMonth,
- 'selected': day.isSelected,
- 'today': day.isToday
- }"
- @click="selectDate(day.date)"
- >
- {{ day.day }}
- </span>
- </div>
- </div>
- <!-- Time Picker Section -->
- <div class="time-section">
- <div class="time-inputs">
- <input
- type="number"
- v-model="selectedHours"
- min="0"
- max="23"
- @change="validateHours"
- class="time-input"
- >
- <span>:</span>
- <input
- type="number"
- v-model="selectedMinutes"
- min="0"
- max="59"
- @change="validateMinutes"
- class="time-input"
- >
- </div>
- <div class="time-buttons">
- <button @click="setCurrentTime" class="time-button">Now</button>
- <button @click="applySelection" class="time-button apply-button">Apply</button>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import { ref, computed, watch, onMounted } from 'vue';
- export default {
- name: 'DateTimePicker',
- props: {
- modelValue: {
- type: [Date, String],
- default: null
- }
- },
- emits: ['update:modelValue'],
- setup(props, { emit }) {
- // Refs
- const displayValue = ref('');
- const internalDate = ref(null);
- const showPicker = ref(false);
- const inputField = ref(null);
- // Date picker state
- const currentMonth = ref(new Date().getMonth());
- const currentYear = ref(new Date().getFullYear());
- const selectedHours = ref(0);
- const selectedMinutes = ref(0);
- // Constants
- const monthNames = ["January", "February", "March", "April", "May", "June",
- "July", "August", "September", "October", "November", "December"];
- // Computed
- const calendarDays = computed(() => {
- const days = [];
- const firstDayOfMonth = new Date(currentYear.value, currentMonth.value, 1);
- const lastDayOfMonth = new Date(currentYear.value, currentMonth.value + 1, 0);
- // Previous month days
- const prevMonthDays = firstDayOfMonth.getDay();
- for (let i = prevMonthDays - 1; i >= 0; i--) {
- const date = new Date(currentYear.value, currentMonth.value - 1, 0 - i);
- days.push({
- day: date.getDate(),
- date: date,
- isCurrentMonth: false,
- isSelected: isSameDay(date, internalDate.value),
- isToday: isSameDay(date, new Date())
- });
- }
- // Current month days
- for (let i = 1; i <= lastDayOfMonth.getDate(); i++) {
- const date = new Date(currentYear.value, currentMonth.value, i);
- days.push({
- day: i,
- date: date,
- isCurrentMonth: true,
- isSelected: isSameDay(date, internalDate.value),
- isToday: isSameDay(date, new Date())
- });
- }
- // Next month days
- const nextMonthDays = 6 - lastDayOfMonth.getDay();
- for (let i = 1; i <= nextMonthDays; i++) {
- const date = new Date(currentYear.value, currentMonth.value + 1, i);
- days.push({
- day: i,
- date: date,
- isCurrentMonth: false,
- isSelected: isSameDay(date, internalDate.value),
- isToday: isSameDay(date, new Date())
- });
- }
- return days;
- });
- // Functions
- function isSameDay(date1, date2) {
- if (!date1 || !date2) return false;
- return date1.getDate() === date2.getDate() &&
- date1.getMonth() === date2.getMonth() &&
- date1.getFullYear() === date2.getFullYear();
- }
- function updateFromModelValue(value) {
- if (!value) {
- internalDate.value = null;
- displayValue.value = '';
- return;
- }
- const date = value instanceof Date ? new Date(value) : parseDateString(value);
- if (date && !isNaN(date.getTime())) {
- internalDate.value = date;
- displayValue.value = formatDate(date);
- selectedHours.value = date.getHours();
- selectedMinutes.value = date.getMinutes();
- currentMonth.value = date.getMonth();
- currentYear.value = date.getFullYear();
- }
- }
- function formatDate(date) {
- if (!date) return '';
- const pad = num => num.toString().padStart(2, '0');
- return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
- }
- function parseDateString(dateString) {
- if (!dateString) return null;
- const cleanString = dateString
- .replace(/[^0-9/:]/g, '')
- .replace(/(\/|:)+/g, '$1');
- const [datePart, timePart] = cleanString.split(' ');
- const [day, month, year] = (datePart || '').split('/').map(Number);
- const [hours, minutes] = (timePart || '').split(':').map(Number);
- if (!day || !month || !year) return null;
- if (day < 1 || day > 31) return null;
- if (month < 1 || month > 12) return null;
- if (year < 1000 || year > 9999) return null;
- const date = new Date(
- year,
- month - 1,
- Math.min(day, daysInMonth(year, month - 1)),
- hours || 0,
- minutes || 0
- );
- return isNaN(date.getTime()) ? null : date;
- }
- function daysInMonth(year, month) {
- return new Date(year, month + 1, 0).getDate();
- }
- function handleKeyDown(e) {
- // Always allow backspace (keyCode 8)
- if (e.keyCode === 8) return;
- // Allow: delete, tab, escape, enter
- if ([46, 9, 27, 13].includes(e.keyCode)) return;
- // Allow: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X
- if ((e.ctrlKey || e.metaKey) && [65, 67, 86, 88].includes(e.keyCode)) return;
- // Allow: home, end, left, right
- if ([35, 36, 37, 39].includes(e.keyCode)) return;
- // Allow: numbers and numpad numbers
- if ((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105)) {
- return;
- }
- // Allow: / and : for date/time separation
- if ([191, 186].includes(e.keyCode)) return;
- e.preventDefault();
- }
- function handleInput(e) {
- const value = e.target.value;
- if (value.length === 2 || value.length === 5) {
- displayValue.value = value + '/';
- } else if (value.length === 10) {
- displayValue.value = value + ' ';
- } else if (value.length === 13) {
- displayValue.value = value + ':';
- }
- }
- function onBlur() {
- setTimeout(() => {
- showPicker.value = false;
- }, 200);
- validateAndUpdate();
- }
- function validateAndUpdate() {
- const parsedDate = parseDateString(displayValue.value);
- if (parsedDate && !isNaN(parsedDate.getTime())) {
- internalDate.value = parsedDate;
- emit('update:modelValue', parsedDate);
- currentMonth.value = parsedDate.getMonth();
- currentYear.value = parsedDate.getFullYear();
- selectedHours.value = parsedDate.getHours();
- selectedMinutes.value = parsedDate.getMinutes();
- } else {
- displayValue.value = internalDate.value ? formatDate(internalDate.value) : '';
- }
- }
- function prevMonth() {
- if (currentMonth.value === 0) {
- currentMonth.value = 11;
- currentYear.value--;
- } else {
- currentMonth.value--;
- }
- }
- function nextMonth() {
- if (currentMonth.value === 11) {
- currentMonth.value = 0;
- currentYear.value++;
- } else {
- currentMonth.value++;
- }
- }
- function selectDate(date) {
- internalDate.value = new Date(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- selectedHours.value,
- selectedMinutes.value
- );
- displayValue.value = formatDate(internalDate.value);
- emit('update:modelValue', internalDate.value);
- }
- function validateHours() {
- selectedHours.value = Math.min(23, Math.max(0, parseInt(selectedHours.value) || 0);
- updateTime();
- }
- function validateMinutes() {
- selectedMinutes.value = Math.min(59, Math.max(0, parseInt(selectedMinutes.value) || 0);
- updateTime();
- }
- function updateTime() {
- if (internalDate.value) {
- internalDate.value = new Date(
- internalDate.value.getFullYear(),
- internalDate.value.getMonth(),
- internalDate.value.getDate(),
- selectedHours.value,
- selectedMinutes.value
- );
- displayValue.value = formatDate(internalDate.value);
- emit('update:modelValue', internalDate.value);
- }
- }
- function setCurrentTime() {
- const now = new Date();
- selectedHours.value = now.getHours();
- selectedMinutes.value = now.getMinutes();
- updateTime();
- }
- function applySelection() {
- showPicker.value = false;
- inputField.value.focus();
- }
- // Lifecycle
- onMounted(() => {
- if (props.modelValue) {
- updateFromModelValue(props.modelValue);
- }
- });
- watch(() => props.modelValue, (newVal) => {
- updateFromModelValue(newVal);
- });
- return {
- displayValue,
- showPicker,
- inputField,
- currentMonth,
- currentYear,
- selectedHours,
- selectedMinutes,
- monthNames,
- calendarDays,
- handleKeyDown,
- handleInput,
- onBlur,
- prevMonth,
- nextMonth,
- selectDate,
- validateHours,
- validateMinutes,
- setCurrentTime,
- applySelection
- };
- }
- };
- </script>
- <style scoped>
- .datetime-picker {
- position: relative;
- display: inline-block;
- font-family: Arial, sans-serif;
- }
- .input-field {
- padding: 8px 12px;
- border: 1px solid #ccc;
- border-radius: 4px;
- font-size: 14px;
- width: 180px;
- }
- .input-field:focus {
- outline: none;
- border-color: #4a90e2;
- box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
- }
- .picker-dropdown {
- position: absolute;
- top: 100%;
- left: 0;
- background: white;
- border: 1px solid #ddd;
- border-radius: 4px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
- margin-top: 4px;
- z-index: 1000;
- display: flex;
- padding: 10px;
- gap: 20px;
- }
- .date-section {
- width: 220px;
- }
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 10px;
- }
- .nav-button {
- background: none;
- border: none;
- cursor: pointer;
- font-size: 16px;
- padding: 0 8px;
- }
- .month-display {
- font-weight: bold;
- }
- .day-names {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- text-align: center;
- font-size: 12px;
- margin-bottom: 5px;
- color: #666;
- }
- .days-grid {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: 4px;
- }
- .days-grid span {
- padding: 5px;
- text-align: center;
- cursor: pointer;
- border-radius: 3px;
- }
- .days-grid span:hover {
- background-color: #f0f0f0;
- }
- .days-grid .other-month {
- color: #aaa;
- }
- .days-grid .selected {
- background-color: #4a90e2;
- color: white;
- }
- .days-grid .today {
- font-weight: bold;
- position: relative;
- }
- .days-grid .today:after {
- content: '';
- position: absolute;
- bottom: 1px;
- left: 50%;
- transform: translateX(-50%);
- width: 16px;
- height: 2px;
- background-color: #4a90e2;
- }
- .time-section {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- }
- .time-inputs {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- .time-input {
- width: 40px;
- padding: 5px;
- text-align: center;
- border: 1px solid #ddd;
- border-radius: 3px;
- }
- .time-input:focus {
- outline: none;
- border-color: #4a90e2;
- }
- .time-buttons {
- display: flex;
- gap: 5px;
- margin-top: 10px;
- }
- .time-button {
- padding: 5px 10px;
- background-color: #f0f0f0;
- border: 1px solid #ddd;
- border-radius: 3px;
- cursor: pointer;
- }
- .time-button:hover {
- background-color: #e0e0e0;
- }
- .apply-button {
- background-color: #4a90e2;
- color: white;
- border-color: #3a80d2;
- }
- .apply-button:hover {
- background-color: #3a80d2;
- }
- </style>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement