Advertisement
Vit_abo

Generated

May 23rd, 2025
163
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <template>
  2.   <div class="datetime-picker">
  3.     <input
  4.       type="text"
  5.       v-model="displayValue"
  6.       @focus="showPicker = true"
  7.       @blur="onBlur"
  8.       @keydown="handleKeyDown"
  9.       @input="handleInput"
  10.       placeholder="DD/MM/YYYY HH:mm"
  11.       class="input-field"
  12.       ref="inputField"
  13.     />
  14.  
  15.     <div v-if="showPicker" class="picker-dropdown">
  16.       <!-- Date Picker Section -->
  17.       <div class="date-section">
  18.         <div class="header">
  19.           <button @click="prevMonth" class="nav-button">&lt;</button>
  20.           <span class="month-display">{{ monthNames[currentMonth] }} {{ currentYear }}</span>
  21.           <button @click="nextMonth" class="nav-button">&gt;</button>
  22.         </div>
  23.         <div class="day-names">
  24.           <span v-for="day in ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']" :key="day">{{ day }}</span>
  25.         </div>
  26.         <div class="days-grid">
  27.           <span
  28.             v-for="day in calendarDays"
  29.             :key="day.date"
  30.             :class="{
  31.              'other-month': !day.isCurrentMonth,
  32.              'selected': day.isSelected,
  33.              'today': day.isToday
  34.            }"
  35.             @click="selectDate(day.date)"
  36.           >
  37.             {{ day.day }}
  38.           </span>
  39.         </div>
  40.       </div>
  41.  
  42.       <!-- Time Picker Section -->
  43.       <div class="time-section">
  44.         <div class="time-inputs">
  45.           <input
  46.             type="number"
  47.             v-model="selectedHours"
  48.             min="0"
  49.             max="23"
  50.             @change="validateHours"
  51.             class="time-input"
  52.           >
  53.           <span>:</span>
  54.           <input
  55.             type="number"
  56.             v-model="selectedMinutes"
  57.             min="0"
  58.             max="59"
  59.             @change="validateMinutes"
  60.             class="time-input"
  61.           >
  62.         </div>
  63.         <div class="time-buttons">
  64.           <button @click="setCurrentTime" class="time-button">Now</button>
  65.           <button @click="applySelection" class="time-button apply-button">Apply</button>
  66.         </div>
  67.       </div>
  68.     </div>
  69.   </div>
  70. </template>
  71.  
  72. <script>
  73. import { ref, computed, watch, onMounted } from 'vue';
  74.  
  75. export default {
  76.   name: 'DateTimePicker',
  77.   props: {
  78.     modelValue: {
  79.       type: [Date, String],
  80.       default: null
  81.     }
  82.   },
  83.   emits: ['update:modelValue'],
  84.   setup(props, { emit }) {
  85.     // Refs
  86.     const displayValue = ref('');
  87.     const internalDate = ref(null);
  88.     const showPicker = ref(false);
  89.     const inputField = ref(null);
  90.    
  91.     // Date picker state
  92.     const currentMonth = ref(new Date().getMonth());
  93.     const currentYear = ref(new Date().getFullYear());
  94.     const selectedHours = ref(0);
  95.     const selectedMinutes = ref(0);
  96.    
  97.     // Constants
  98.     const monthNames = ["January", "February", "March", "April", "May", "June",
  99.       "July", "August", "September", "October", "November", "December"];
  100.  
  101.     // Computed
  102.     const calendarDays = computed(() => {
  103.       const days = [];
  104.       const firstDayOfMonth = new Date(currentYear.value, currentMonth.value, 1);
  105.       const lastDayOfMonth = new Date(currentYear.value, currentMonth.value + 1, 0);
  106.      
  107.       // Previous month days
  108.       const prevMonthDays = firstDayOfMonth.getDay();
  109.       for (let i = prevMonthDays - 1; i >= 0; i--) {
  110.         const date = new Date(currentYear.value, currentMonth.value - 1, 0 - i);
  111.         days.push({
  112.           day: date.getDate(),
  113.           date: date,
  114.           isCurrentMonth: false,
  115.           isSelected: isSameDay(date, internalDate.value),
  116.           isToday: isSameDay(date, new Date())
  117.         });
  118.       }
  119.      
  120.       // Current month days
  121.       for (let i = 1; i <= lastDayOfMonth.getDate(); i++) {
  122.         const date = new Date(currentYear.value, currentMonth.value, i);
  123.         days.push({
  124.           day: i,
  125.           date: date,
  126.           isCurrentMonth: true,
  127.           isSelected: isSameDay(date, internalDate.value),
  128.           isToday: isSameDay(date, new Date())
  129.         });
  130.       }
  131.      
  132.       // Next month days
  133.       const nextMonthDays = 6 - lastDayOfMonth.getDay();
  134.       for (let i = 1; i <= nextMonthDays; i++) {
  135.         const date = new Date(currentYear.value, currentMonth.value + 1, i);
  136.         days.push({
  137.           day: i,
  138.           date: date,
  139.           isCurrentMonth: false,
  140.           isSelected: isSameDay(date, internalDate.value),
  141.           isToday: isSameDay(date, new Date())
  142.         });
  143.       }
  144.      
  145.       return days;
  146.     });
  147.  
  148.     // Functions
  149.     function isSameDay(date1, date2) {
  150.       if (!date1 || !date2) return false;
  151.       return date1.getDate() === date2.getDate() &&
  152.              date1.getMonth() === date2.getMonth() &&
  153.              date1.getFullYear() === date2.getFullYear();
  154.     }
  155.  
  156.     function updateFromModelValue(value) {
  157.       if (!value) {
  158.         internalDate.value = null;
  159.         displayValue.value = '';
  160.         return;
  161.       }
  162.  
  163.       const date = value instanceof Date ? new Date(value) : parseDateString(value);
  164.       if (date && !isNaN(date.getTime())) {
  165.         internalDate.value = date;
  166.         displayValue.value = formatDate(date);
  167.         selectedHours.value = date.getHours();
  168.         selectedMinutes.value = date.getMinutes();
  169.         currentMonth.value = date.getMonth();
  170.         currentYear.value = date.getFullYear();
  171.       }
  172.     }
  173.  
  174.     function formatDate(date) {
  175.       if (!date) return '';
  176.       const pad = num => num.toString().padStart(2, '0');
  177.       return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
  178.     }
  179.  
  180.     function parseDateString(dateString) {
  181.       if (!dateString) return null;
  182.      
  183.       const cleanString = dateString
  184.         .replace(/[^0-9/:]/g, '')
  185.         .replace(/(\/|:)+/g, '$1');
  186.      
  187.       const [datePart, timePart] = cleanString.split(' ');
  188.       const [day, month, year] = (datePart || '').split('/').map(Number);
  189.       const [hours, minutes] = (timePart || '').split(':').map(Number);
  190.      
  191.       if (!day || !month || !year) return null;
  192.       if (day < 1 || day > 31) return null;
  193.       if (month < 1 || month > 12) return null;
  194.       if (year < 1000 || year > 9999) return null;
  195.      
  196.       const date = new Date(
  197.         year,
  198.         month - 1,
  199.         Math.min(day, daysInMonth(year, month - 1)),
  200.         hours || 0,
  201.         minutes || 0
  202.       );
  203.      
  204.       return isNaN(date.getTime()) ? null : date;
  205.     }
  206.  
  207.     function daysInMonth(year, month) {
  208.       return new Date(year, month + 1, 0).getDate();
  209.     }
  210.  
  211.     function handleKeyDown(e) {
  212.       // Always allow backspace (keyCode 8)
  213.       if (e.keyCode === 8) return;
  214.      
  215.       // Allow: delete, tab, escape, enter
  216.       if ([46, 9, 27, 13].includes(e.keyCode)) return;
  217.      
  218.       // Allow: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X
  219.       if ((e.ctrlKey || e.metaKey) && [65, 67, 86, 88].includes(e.keyCode)) return;
  220.      
  221.       // Allow: home, end, left, right
  222.       if ([35, 36, 37, 39].includes(e.keyCode)) return;
  223.      
  224.       // Allow: numbers and numpad numbers
  225.       if ((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105)) {
  226.         return;
  227.       }
  228.      
  229.       // Allow: / and : for date/time separation
  230.       if ([191, 186].includes(e.keyCode)) return;
  231.      
  232.       e.preventDefault();
  233.     }
  234.  
  235.     function handleInput(e) {
  236.       const value = e.target.value;
  237.       if (value.length === 2 || value.length === 5) {
  238.         displayValue.value = value + '/';
  239.       } else if (value.length === 10) {
  240.         displayValue.value = value + ' ';
  241.       } else if (value.length === 13) {
  242.         displayValue.value = value + ':';
  243.       }
  244.     }
  245.  
  246.     function onBlur() {
  247.       setTimeout(() => {
  248.         showPicker.value = false;
  249.       }, 200);
  250.       validateAndUpdate();
  251.     }
  252.  
  253.     function validateAndUpdate() {
  254.       const parsedDate = parseDateString(displayValue.value);
  255.       if (parsedDate && !isNaN(parsedDate.getTime())) {
  256.         internalDate.value = parsedDate;
  257.         emit('update:modelValue', parsedDate);
  258.         currentMonth.value = parsedDate.getMonth();
  259.         currentYear.value = parsedDate.getFullYear();
  260.         selectedHours.value = parsedDate.getHours();
  261.         selectedMinutes.value = parsedDate.getMinutes();
  262.       } else {
  263.         displayValue.value = internalDate.value ? formatDate(internalDate.value) : '';
  264.       }
  265.     }
  266.  
  267.     function prevMonth() {
  268.       if (currentMonth.value === 0) {
  269.         currentMonth.value = 11;
  270.         currentYear.value--;
  271.       } else {
  272.         currentMonth.value--;
  273.       }
  274.     }
  275.  
  276.     function nextMonth() {
  277.       if (currentMonth.value === 11) {
  278.         currentMonth.value = 0;
  279.         currentYear.value++;
  280.       } else {
  281.         currentMonth.value++;
  282.       }
  283.     }
  284.  
  285.     function selectDate(date) {
  286.       internalDate.value = new Date(
  287.         date.getFullYear(),
  288.         date.getMonth(),
  289.         date.getDate(),
  290.         selectedHours.value,
  291.         selectedMinutes.value
  292.       );
  293.       displayValue.value = formatDate(internalDate.value);
  294.       emit('update:modelValue', internalDate.value);
  295.     }
  296.  
  297.     function validateHours() {
  298.       selectedHours.value = Math.min(23, Math.max(0, parseInt(selectedHours.value) || 0);
  299.       updateTime();
  300.     }
  301.  
  302.     function validateMinutes() {
  303.       selectedMinutes.value = Math.min(59, Math.max(0, parseInt(selectedMinutes.value) || 0);
  304.       updateTime();
  305.     }
  306.  
  307.     function updateTime() {
  308.       if (internalDate.value) {
  309.         internalDate.value = new Date(
  310.           internalDate.value.getFullYear(),
  311.           internalDate.value.getMonth(),
  312.           internalDate.value.getDate(),
  313.           selectedHours.value,
  314.           selectedMinutes.value
  315.         );
  316.         displayValue.value = formatDate(internalDate.value);
  317.         emit('update:modelValue', internalDate.value);
  318.       }
  319.     }
  320.  
  321.     function setCurrentTime() {
  322.       const now = new Date();
  323.       selectedHours.value = now.getHours();
  324.       selectedMinutes.value = now.getMinutes();
  325.       updateTime();
  326.     }
  327.  
  328.     function applySelection() {
  329.       showPicker.value = false;
  330.       inputField.value.focus();
  331.     }
  332.  
  333.     // Lifecycle
  334.     onMounted(() => {
  335.       if (props.modelValue) {
  336.         updateFromModelValue(props.modelValue);
  337.       }
  338.     });
  339.  
  340.     watch(() => props.modelValue, (newVal) => {
  341.       updateFromModelValue(newVal);
  342.     });
  343.  
  344.     return {
  345.       displayValue,
  346.       showPicker,
  347.       inputField,
  348.       currentMonth,
  349.       currentYear,
  350.       selectedHours,
  351.       selectedMinutes,
  352.       monthNames,
  353.       calendarDays,
  354.       handleKeyDown,
  355.       handleInput,
  356.       onBlur,
  357.       prevMonth,
  358.       nextMonth,
  359.       selectDate,
  360.       validateHours,
  361.       validateMinutes,
  362.       setCurrentTime,
  363.       applySelection
  364.     };
  365.   }
  366. };
  367. </script>
  368.  
  369. <style scoped>
  370. .datetime-picker {
  371.   position: relative;
  372.   display: inline-block;
  373.   font-family: Arial, sans-serif;
  374. }
  375.  
  376. .input-field {
  377.   padding: 8px 12px;
  378.   border: 1px solid #ccc;
  379.   border-radius: 4px;
  380.   font-size: 14px;
  381.   width: 180px;
  382. }
  383.  
  384. .input-field:focus {
  385.   outline: none;
  386.   border-color: #4a90e2;
  387.   box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
  388. }
  389.  
  390. .picker-dropdown {
  391.   position: absolute;
  392.   top: 100%;
  393.   left: 0;
  394.   background: white;
  395.   border: 1px solid #ddd;
  396.   border-radius: 4px;
  397.   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  398.   margin-top: 4px;
  399.   z-index: 1000;
  400.   display: flex;
  401.   padding: 10px;
  402.   gap: 20px;
  403. }
  404.  
  405. .date-section {
  406.   width: 220px;
  407. }
  408.  
  409. .header {
  410.   display: flex;
  411.   justify-content: space-between;
  412.   align-items: center;
  413.   margin-bottom: 10px;
  414. }
  415.  
  416. .nav-button {
  417.   background: none;
  418.   border: none;
  419.   cursor: pointer;
  420.   font-size: 16px;
  421.   padding: 0 8px;
  422. }
  423.  
  424. .month-display {
  425.   font-weight: bold;
  426. }
  427.  
  428. .day-names {
  429.   display: grid;
  430.   grid-template-columns: repeat(7, 1fr);
  431.   text-align: center;
  432.   font-size: 12px;
  433.   margin-bottom: 5px;
  434.   color: #666;
  435. }
  436.  
  437. .days-grid {
  438.   display: grid;
  439.   grid-template-columns: repeat(7, 1fr);
  440.   gap: 4px;
  441. }
  442.  
  443. .days-grid span {
  444.   padding: 5px;
  445.   text-align: center;
  446.   cursor: pointer;
  447.   border-radius: 3px;
  448. }
  449.  
  450. .days-grid span:hover {
  451.   background-color: #f0f0f0;
  452. }
  453.  
  454. .days-grid .other-month {
  455.   color: #aaa;
  456. }
  457.  
  458. .days-grid .selected {
  459.   background-color: #4a90e2;
  460.   color: white;
  461. }
  462.  
  463. .days-grid .today {
  464.   font-weight: bold;
  465.   position: relative;
  466. }
  467.  
  468. .days-grid .today:after {
  469.   content: '';
  470.   position: absolute;
  471.   bottom: 1px;
  472.   left: 50%;
  473.   transform: translateX(-50%);
  474.   width: 16px;
  475.   height: 2px;
  476.   background-color: #4a90e2;
  477. }
  478.  
  479. .time-section {
  480.   display: flex;
  481.   flex-direction: column;
  482.   justify-content: space-between;
  483. }
  484.  
  485. .time-inputs {
  486.   display: flex;
  487.   align-items: center;
  488.   gap: 5px;
  489. }
  490.  
  491. .time-input {
  492.   width: 40px;
  493.   padding: 5px;
  494.   text-align: center;
  495.   border: 1px solid #ddd;
  496.   border-radius: 3px;
  497. }
  498.  
  499. .time-input:focus {
  500.   outline: none;
  501.   border-color: #4a90e2;
  502. }
  503.  
  504. .time-buttons {
  505.   display: flex;
  506.   gap: 5px;
  507.   margin-top: 10px;
  508. }
  509.  
  510. .time-button {
  511.   padding: 5px 10px;
  512.   background-color: #f0f0f0;
  513.   border: 1px solid #ddd;
  514.   border-radius: 3px;
  515.   cursor: pointer;
  516. }
  517.  
  518. .time-button:hover {
  519.   background-color: #e0e0e0;
  520. }
  521.  
  522. .apply-button {
  523.   background-color: #4a90e2;
  524.   color: white;
  525.   border-color: #3a80d2;
  526. }
  527.  
  528. .apply-button:hover {
  529.   background-color: #3a80d2;
  530. }
  531. </style>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement