mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	 28728635c9
			
		
	
	28728635c9
	
	
	
		
			
			* feat: Custom date picker * chore: Calender footer * chore: Minor fix * chore: Reset date picker * chore: Minor fix * feat: Toggle button * chore: Clean up * chore: Use font inter * chore: Cleanup and fix bugs * fix: custom date range reset the calendar * chore: fix logic bug * feat: Add manual date range * fix: styles in rtl * chore: Helper specs * chore: Clean up * chore: Review fixes * chore: remove magic strings * chore: Add comments * chore: Review fixes * chore: Clean up * chore: remove magic strings * fix: Use outline instead of border * chore: Minor style fix * chore: disable pointer events for the disabled dates * chore: Fix code climate --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
		
			
				
	
	
		
			303 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <script setup>
 | |
| import { ref, watch } from 'vue';
 | |
| import {
 | |
|   getActiveDateRange,
 | |
|   moveCalendarDate,
 | |
|   DATE_RANGE_TYPES,
 | |
|   CALENDAR_TYPES,
 | |
|   CALENDAR_PERIODS,
 | |
| } from './helpers/DatePickerHelper';
 | |
| import {
 | |
|   isValid,
 | |
|   startOfMonth,
 | |
|   subDays,
 | |
|   startOfDay,
 | |
|   endOfDay,
 | |
|   isBefore,
 | |
|   subMonths,
 | |
|   addMonths,
 | |
|   isSameMonth,
 | |
|   differenceInCalendarMonths,
 | |
|   setMonth,
 | |
|   setYear,
 | |
|   isAfter,
 | |
| } from 'date-fns';
 | |
| 
 | |
| import DatePickerButton from './components/DatePickerButton.vue';
 | |
| import CalendarDateInput from './components/CalendarDateInput.vue';
 | |
| import CalendarDateRange from './components/CalendarDateRange.vue';
 | |
| import CalendarYear from './components/CalendarYear.vue';
 | |
| import CalendarMonth from './components/CalendarMonth.vue';
 | |
| import CalendarWeek from './components/CalendarWeek.vue';
 | |
| import CalendarFooter from './components/CalendarFooter.vue';
 | |
| 
 | |
| const { LAST_7_DAYS, CUSTOM_RANGE } = DATE_RANGE_TYPES;
 | |
| const { START_CALENDAR, END_CALENDAR } = CALENDAR_TYPES;
 | |
| const { WEEK, MONTH, YEAR } = CALENDAR_PERIODS;
 | |
| 
 | |
| const showDatePicker = ref(false);
 | |
| const calendarViews = ref({ start: WEEK, end: WEEK });
 | |
| const currentDate = ref(new Date());
 | |
| const selectedStartDate = ref(startOfDay(subDays(currentDate.value, 6))); // LAST_7_DAYS
 | |
| const selectedEndDate = ref(endOfDay(currentDate.value));
 | |
| // Setting the start and end calendar
 | |
| const startCurrentDate = ref(startOfDay(selectedStartDate.value));
 | |
| const endCurrentDate = ref(
 | |
|   isSameMonth(selectedStartDate.value, selectedEndDate.value)
 | |
|     ? startOfMonth(addMonths(selectedEndDate.value, 1)) // Moves to the start of the next month if dates are in the same month (Mounted case LAST_7_DAYS)
 | |
|     : startOfMonth(selectedEndDate.value) // Always shows the month of the end date starting from the first (Mounted case LAST_7_DAYS)
 | |
| );
 | |
| const selectingEndDate = ref(false);
 | |
| const selectedRange = ref(LAST_7_DAYS);
 | |
| const hoveredEndDate = ref(null);
 | |
| 
 | |
| const manualStartDate = ref(selectedStartDate.value);
 | |
| const manualEndDate = ref(selectedEndDate.value);
 | |
| 
 | |
| const emit = defineEmits(['change']);
 | |
| 
 | |
| // Watcher will set the start and end dates based on the selected range
 | |
| watch(selectedRange, newRange => {
 | |
|   if (newRange !== CUSTOM_RANGE) {
 | |
|     // If selecting a range other than last 7 days, set the start and end dates to the selected start and end dates
 | |
|     // If selecting last 7 days, set the start date to the selected start date
 | |
|     // and the end date to one month ahead of the start date if the start date and end date are in the same month
 | |
|     // Otherwise set the end date to the selected end date
 | |
|     const isLast7days = newRange === LAST_7_DAYS;
 | |
|     startCurrentDate.value = selectedStartDate.value;
 | |
|     endCurrentDate.value =
 | |
|       isLast7days && isSameMonth(selectedStartDate.value, selectedEndDate.value)
 | |
|         ? startOfMonth(addMonths(selectedStartDate.value, 1))
 | |
|         : selectedEndDate.value;
 | |
|     selectingEndDate.value = false;
 | |
|   } else if (!selectingEndDate.value) {
 | |
|     // If selecting a custom range and not selecting an end date, set the start date to the selected start date
 | |
|     startCurrentDate.value = startOfDay(currentDate.value);
 | |
|   }
 | |
| });
 | |
| 
 | |
| // Watcher will set the input values based on the selected start and end dates
 | |
| watch(
 | |
|   [selectedStartDate, selectedEndDate],
 | |
|   ([newStart, newEnd]) => {
 | |
|     if (isValid(newStart)) {
 | |
|       manualStartDate.value = newStart;
 | |
|     } else {
 | |
|       manualStartDate.value = selectedStartDate.value;
 | |
|     }
 | |
| 
 | |
|     if (isValid(newEnd)) {
 | |
|       manualEndDate.value = newEnd;
 | |
|     } else {
 | |
|       manualEndDate.value = selectedEndDate.value;
 | |
|     }
 | |
|   },
 | |
|   { immediate: true }
 | |
| );
 | |
| 
 | |
| // Watcher to ensure dates are always in logical order
 | |
| // This watch is will ensure that the start date is always before the end date
 | |
| watch(
 | |
|   [startCurrentDate, endCurrentDate],
 | |
|   ([newStart, newEnd], [oldStart, oldEnd]) => {
 | |
|     const monthDifference = differenceInCalendarMonths(newEnd, newStart);
 | |
| 
 | |
|     if (newStart !== oldStart) {
 | |
|       if (isAfter(newStart, newEnd) || monthDifference === 0) {
 | |
|         // Adjust the end date forward if the start date is adjusted and is after the end date or in the same month
 | |
|         endCurrentDate.value = addMonths(newStart, 1);
 | |
|       }
 | |
|     }
 | |
|     if (newEnd !== oldEnd) {
 | |
|       if (isBefore(newEnd, newStart) || monthDifference === 0) {
 | |
|         // Adjust the start date backward if the end date is adjusted and is before the start date or in the same month
 | |
|         startCurrentDate.value = subMonths(newEnd, 1);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   { immediate: true, deep: true }
 | |
| );
 | |
| 
 | |
| const setDateRange = range => {
 | |
|   selectedRange.value = range.value;
 | |
|   const { start, end } = getActiveDateRange(range.value, currentDate.value);
 | |
|   selectedStartDate.value = start;
 | |
|   selectedEndDate.value = end;
 | |
| };
 | |
| 
 | |
| const moveCalendar = (calendar, direction, period = MONTH) => {
 | |
|   const { start, end } = moveCalendarDate(
 | |
|     calendar,
 | |
|     startCurrentDate.value,
 | |
|     endCurrentDate.value,
 | |
|     direction,
 | |
|     period
 | |
|   );
 | |
|   startCurrentDate.value = start;
 | |
|   endCurrentDate.value = end;
 | |
| };
 | |
| 
 | |
| const selectDate = day => {
 | |
|   selectedRange.value = CUSTOM_RANGE;
 | |
|   if (!selectingEndDate.value || day < selectedStartDate.value) {
 | |
|     selectedStartDate.value = day;
 | |
|     selectedEndDate.value = null;
 | |
|     selectingEndDate.value = true;
 | |
|   } else {
 | |
|     selectedEndDate.value = day;
 | |
|     selectingEndDate.value = false;
 | |
|   }
 | |
| };
 | |
| 
 | |
| const setViewMode = (calendar, mode) => {
 | |
|   selectedRange.value = CUSTOM_RANGE;
 | |
|   calendarViews.value[calendar] = mode;
 | |
| };
 | |
| 
 | |
| const openCalendar = (index, calendarType, period = MONTH) => {
 | |
|   const current =
 | |
|     calendarType === START_CALENDAR
 | |
|       ? startCurrentDate.value
 | |
|       : endCurrentDate.value;
 | |
|   const newDate =
 | |
|     period === MONTH
 | |
|       ? setMonth(startOfMonth(current), index)
 | |
|       : setYear(current, index);
 | |
|   if (calendarType === START_CALENDAR) {
 | |
|     startCurrentDate.value = newDate;
 | |
|   } else {
 | |
|     endCurrentDate.value = newDate;
 | |
|   }
 | |
|   setViewMode(calendarType, period === MONTH ? WEEK : MONTH);
 | |
| };
 | |
| 
 | |
| const updateManualInput = (newDate, calendarType) => {
 | |
|   if (calendarType === START_CALENDAR) {
 | |
|     selectedStartDate.value = newDate;
 | |
|     startCurrentDate.value = newDate;
 | |
|   } else {
 | |
|     selectedEndDate.value = newDate;
 | |
|     endCurrentDate.value = newDate;
 | |
|   }
 | |
|   selectingEndDate.value = false;
 | |
| };
 | |
| 
 | |
| const handleManualInputError = message => {
 | |
|   bus.$emit('newToastMessage', message);
 | |
| };
 | |
| 
 | |
| const resetDatePicker = () => {
 | |
|   startCurrentDate.value = startOfDay(currentDate.value); // Resets to today at start of the day
 | |
|   endCurrentDate.value = addMonths(startOfDay(currentDate.value), 1); // Resets to one month ahead
 | |
|   selectedStartDate.value = startOfDay(subDays(currentDate.value, 6));
 | |
|   selectedEndDate.value = endOfDay(currentDate.value);
 | |
|   selectingEndDate.value = false;
 | |
|   selectedRange.value = LAST_7_DAYS;
 | |
|   // Reset view modes if they are being used to toggle between different calendar views
 | |
|   calendarViews.value = { start: WEEK, end: WEEK };
 | |
| };
 | |
| 
 | |
| const emitDateRange = () => {
 | |
|   if (!isValid(selectedStartDate.value) || !isValid(selectedEndDate.value)) {
 | |
|     bus.$emit('newToastMessage', 'Please select a valid time range');
 | |
|   } else {
 | |
|     showDatePicker.value = false;
 | |
|     emit('dateRangeChanged', [selectedStartDate.value, selectedEndDate.value]);
 | |
|   }
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <template>
 | |
|   <div class="relative font-inter">
 | |
|     <DatePickerButton
 | |
|       :selected-start-date="selectedStartDate"
 | |
|       :selected-end-date="selectedEndDate"
 | |
|       :selected-range="selectedRange"
 | |
|       @open="showDatePicker = !showDatePicker"
 | |
|     />
 | |
|     <div
 | |
|       v-if="showDatePicker"
 | |
|       class="flex absolute top-9 ltr:left-0 rtl:right-0 z-30 shadow-md select-none w-[880px] h-[490px] rounded-2xl border border-slate-50 dark:border-slate-800 bg-white dark:bg-slate-800"
 | |
|     >
 | |
|       <CalendarDateRange
 | |
|         :selected-range="selectedRange"
 | |
|         @set-range="setDateRange"
 | |
|       />
 | |
|       <div
 | |
|         class="flex flex-col w-[680px] ltr:border-l rtl:border-r border-slate-50 dark:border-slate-700/50"
 | |
|       >
 | |
|         <div class="flex justify-around h-fit">
 | |
|           <!-- Calendars for Start and End Dates -->
 | |
|           <div
 | |
|             v-for="calendar in [START_CALENDAR, END_CALENDAR]"
 | |
|             :key="`${calendar}-calendar`"
 | |
|             class="flex flex-col items-center"
 | |
|           >
 | |
|             <CalendarDateInput
 | |
|               :calendar-type="calendar"
 | |
|               :date-value="
 | |
|                 calendar === START_CALENDAR ? manualStartDate : manualEndDate
 | |
|               "
 | |
|               :compare-date="
 | |
|                 calendar === START_CALENDAR ? manualEndDate : manualStartDate
 | |
|               "
 | |
|               :is-disabled="selectedRange !== CUSTOM_RANGE"
 | |
|               @update="
 | |
|                 calendar === START_CALENDAR
 | |
|                   ? (manualStartDate = $event)
 | |
|                   : (manualEndDate = $event)
 | |
|               "
 | |
|               @validate="updateManualInput($event, calendar)"
 | |
|               @error="handleManualInputError($event)"
 | |
|             />
 | |
|             <div class="py-5 border-b border-slate-50 dark:border-slate-700/50">
 | |
|               <div
 | |
|                 class="flex flex-col items-center gap-2 px-5 min-w-[340px] max-h-[352px]"
 | |
|                 :class="
 | |
|                   calendar === START_CALENDAR &&
 | |
|                   'ltr:border-r rtl:border-l border-slate-50 dark:border-slate-700/50'
 | |
|                 "
 | |
|               >
 | |
|                 <CalendarYear
 | |
|                   v-if="calendarViews[calendar] === YEAR"
 | |
|                   :calendar-type="calendar"
 | |
|                   :start-current-date="startCurrentDate"
 | |
|                   :end-current-date="endCurrentDate"
 | |
|                   @select-year="openCalendar($event, calendar, YEAR)"
 | |
|                 />
 | |
|                 <CalendarMonth
 | |
|                   v-else-if="calendarViews[calendar] === MONTH"
 | |
|                   :calendar-type="calendar"
 | |
|                   :start-current-date="startCurrentDate"
 | |
|                   :end-current-date="endCurrentDate"
 | |
|                   @select-month="openCalendar($event, calendar)"
 | |
|                   @set-view="setViewMode"
 | |
|                   @prev="moveCalendar(calendar, 'prev', YEAR)"
 | |
|                   @next="moveCalendar(calendar, 'next', YEAR)"
 | |
|                 />
 | |
|                 <CalendarWeek
 | |
|                   v-else-if="calendarViews[calendar] === WEEK"
 | |
|                   :calendar-type="calendar"
 | |
|                   :current-date="currentDate"
 | |
|                   :start-current-date="startCurrentDate"
 | |
|                   :end-current-date="endCurrentDate"
 | |
|                   :selected-start-date="selectedStartDate"
 | |
|                   :selected-end-date="selectedEndDate"
 | |
|                   :selecting-end-date="selectingEndDate"
 | |
|                   :hovered-end-date="hoveredEndDate"
 | |
|                   @update-hovered-end-date="hoveredEndDate = $event"
 | |
|                   @select-date="selectDate"
 | |
|                   @set-view="setViewMode"
 | |
|                   @prev="moveCalendar(calendar, 'prev')"
 | |
|                   @next="moveCalendar(calendar, 'next')"
 | |
|                 />
 | |
|               </div>
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|         <CalendarFooter @change="emitDateRange" @clear="resetDatePicker" />
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 |