mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	fix: Update business hour calculation (#4496)
This commit is contained in:
		@@ -2,7 +2,6 @@ import fromUnixTime from 'date-fns/fromUnixTime';
 | 
			
		||||
import format from 'date-fns/format';
 | 
			
		||||
import isToday from 'date-fns/isToday';
 | 
			
		||||
import isYesterday from 'date-fns/isYesterday';
 | 
			
		||||
import parseISO from 'date-fns/parseISO';
 | 
			
		||||
 | 
			
		||||
export const formatUnixDate = (date, dateFormat = 'MMM dd, yyyy') => {
 | 
			
		||||
  const unixDate = fromUnixTime(date);
 | 
			
		||||
@@ -20,10 +19,14 @@ export const formatDigitToString = val => {
 | 
			
		||||
  return val > 9 ? `${val}` : `0${val}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const buildDateFromTime = (hr, min, utcOffset, date = new Date()) => {
 | 
			
		||||
  const today = format(date, 'yyyy-MM-dd');
 | 
			
		||||
  const hour = formatDigitToString(hr);
 | 
			
		||||
  const minute = formatDigitToString(min);
 | 
			
		||||
  const timeString = `${today}T${hour}:${minute}:00${utcOffset}`;
 | 
			
		||||
  return parseISO(timeString);
 | 
			
		||||
export const isTimeAfter = (h1, m1, h2, m2) => {
 | 
			
		||||
  if (h1 < h2) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (h1 === h2) {
 | 
			
		||||
    return m1 >= m2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import {
 | 
			
		||||
  formatDate,
 | 
			
		||||
  formatUnixDate,
 | 
			
		||||
  buildDateFromTime,
 | 
			
		||||
  formatDigitToString,
 | 
			
		||||
  isTimeAfter,
 | 
			
		||||
} from '../DateHelper';
 | 
			
		||||
 | 
			
		||||
describe('#DateHelper', () => {
 | 
			
		||||
@@ -44,27 +44,21 @@ describe('#DateHelper', () => {
 | 
			
		||||
      })
 | 
			
		||||
    ).toEqual('Yesterday');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('#buildDate', () => {
 | 
			
		||||
    it('returns correctly parsed date', () => {
 | 
			
		||||
      const date = new Date();
 | 
			
		||||
      date.setFullYear(2021);
 | 
			
		||||
      date.setMonth(2);
 | 
			
		||||
      date.setDate(5);
 | 
			
		||||
 | 
			
		||||
      const result = buildDateFromTime(12, 15, '.465Z', date);
 | 
			
		||||
      expect(result + '').toEqual(
 | 
			
		||||
        'Fri Mar 05 2021 12:15:00 GMT+0000 (Coordinated Universal Time)'
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('#formatDigitToString', () => {
 | 
			
		||||
});
 | 
			
		||||
describe('#formatDigitToString', () => {
 | 
			
		||||
  it('returns date compatabile string from number is less than 9', () => {
 | 
			
		||||
    expect(formatDigitToString(8)).toEqual('08');
 | 
			
		||||
  });
 | 
			
		||||
  it('returns date compatabile string from number is greater than 9', () => {
 | 
			
		||||
    expect(formatDigitToString(11)).toEqual('11');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('#isTimeAfter', () => {
 | 
			
		||||
  it('return correct values', () => {
 | 
			
		||||
    expect(isTimeAfter(5, 30, 9, 30)).toEqual(false);
 | 
			
		||||
    expect(isTimeAfter(9, 30, 9, 30)).toEqual(true);
 | 
			
		||||
    expect(isTimeAfter(9, 29, 9, 30)).toEqual(false);
 | 
			
		||||
    expect(isTimeAfter(11, 59, 12, 0)).toEqual(false);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ export default {
 | 
			
		||||
      default: false,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapGetters({
 | 
			
		||||
      widgetColor: 'appConfig/getWidgetColor',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import compareAsc from 'date-fns/compareAsc';
 | 
			
		||||
import { buildDateFromTime } from 'shared/helpers/DateHelper';
 | 
			
		||||
import { utcToZonedTime } from 'date-fns-tz';
 | 
			
		||||
import { isTimeAfter } from 'shared/helpers/DateHelper';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  computed: {
 | 
			
		||||
@@ -33,23 +33,32 @@ export default {
 | 
			
		||||
        closedAllDay,
 | 
			
		||||
        openAllDay,
 | 
			
		||||
      } = this.currentDayAvailability;
 | 
			
		||||
 | 
			
		||||
      if (openAllDay || closedAllDay) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const { utcOffset } = this.channelConfig;
 | 
			
		||||
 | 
			
		||||
      if (openAllDay) return true;
 | 
			
		||||
 | 
			
		||||
      if (closedAllDay) return false;
 | 
			
		||||
 | 
			
		||||
      const startTime = buildDateFromTime(openHour, openMinute, utcOffset);
 | 
			
		||||
      const endTime = buildDateFromTime(closeHour, closeMinute, utcOffset);
 | 
			
		||||
      const isBetween =
 | 
			
		||||
        compareAsc(new Date(), startTime) === 1 &&
 | 
			
		||||
        compareAsc(endTime, new Date()) === 1;
 | 
			
		||||
 | 
			
		||||
      if (isBetween) return true;
 | 
			
		||||
      return false;
 | 
			
		||||
      const today = this.getDateWithOffset(utcOffset);
 | 
			
		||||
      const currentHours = today.getHours();
 | 
			
		||||
      const currentMinutes = today.getMinutes();
 | 
			
		||||
      const isAfterStartTime = isTimeAfter(
 | 
			
		||||
        currentHours,
 | 
			
		||||
        currentMinutes,
 | 
			
		||||
        openHour,
 | 
			
		||||
        openMinute
 | 
			
		||||
      );
 | 
			
		||||
      const isBeforeEndTime = isTimeAfter(
 | 
			
		||||
        closeHour,
 | 
			
		||||
        closeMinute,
 | 
			
		||||
        currentHours,
 | 
			
		||||
        currentMinutes
 | 
			
		||||
      );
 | 
			
		||||
      return isAfterStartTime && isBeforeEndTime;
 | 
			
		||||
    },
 | 
			
		||||
    currentDayAvailability() {
 | 
			
		||||
      const dayOfTheWeek = new Date().getDay();
 | 
			
		||||
      const { utcOffset } = this.channelConfig;
 | 
			
		||||
      const dayOfTheWeek = this.getDateWithOffset(utcOffset).getDay();
 | 
			
		||||
      const [workingHourConfig = {}] = this.channelConfig.workingHours.filter(
 | 
			
		||||
        workingHour => workingHour.day_of_week === dayOfTheWeek
 | 
			
		||||
      );
 | 
			
		||||
@@ -63,8 +72,14 @@ export default {
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    isInBusinessHours() {
 | 
			
		||||
      const { workingHoursEnabled } = window.chatwootWebChannel;
 | 
			
		||||
      const { workingHoursEnabled } = this.channelConfig;
 | 
			
		||||
      return workingHoursEnabled ? this.isInBetweenTheWorkingHours : true;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    getDateWithOffset(utcOffset) {
 | 
			
		||||
      return utcToZonedTime(new Date().toISOString(), utcOffset);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								app/javascript/widget/mixins/specs/availabilityMixin.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								app/javascript/widget/mixins/specs/availabilityMixin.spec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import { createWrapper } from '@vue/test-utils';
 | 
			
		||||
import availabilityMixin from '../availability';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
global.chatwootWebChannel = {
 | 
			
		||||
  workingHoursEnabled: true,
 | 
			
		||||
  workingHours: [
 | 
			
		||||
    {
 | 
			
		||||
      day_of_week: 3,
 | 
			
		||||
      closed_all_day: false,
 | 
			
		||||
      open_hour: 8,
 | 
			
		||||
      open_minutes: 30,
 | 
			
		||||
      close_hour: 17,
 | 
			
		||||
      close_minutes: 35,
 | 
			
		||||
      open_all_day: false,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      day_of_week: 4,
 | 
			
		||||
      closed_all_day: false,
 | 
			
		||||
      open_hour: 8,
 | 
			
		||||
      open_minutes: 30,
 | 
			
		||||
      close_hour: 17,
 | 
			
		||||
      close_minutes: 30,
 | 
			
		||||
      open_all_day: false,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  utcOffset: '-07:00',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('availabilityMixin', () => {
 | 
			
		||||
  it('returns valid isInBetweenWorkingHours if in different timezone', () => {
 | 
			
		||||
    const Component = {
 | 
			
		||||
      render() {},
 | 
			
		||||
      mixins: [availabilityMixin],
 | 
			
		||||
    };
 | 
			
		||||
    jest
 | 
			
		||||
      .useFakeTimers('modern')
 | 
			
		||||
      .setSystemTime(new Date('Thu Apr 14 2022 06:04:46 GMT+0530'));
 | 
			
		||||
    const Constructor = Vue.extend(Component);
 | 
			
		||||
    const vm = new Constructor().$mount();
 | 
			
		||||
    const wrapper = createWrapper(vm);
 | 
			
		||||
    expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true);
 | 
			
		||||
    jest.useRealTimers();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('returns valid isInBetweenWorkingHours if in same timezone', () => {
 | 
			
		||||
    global.chatwootWebChannel.utcOffset = '+05:30';
 | 
			
		||||
    const Component = {
 | 
			
		||||
      render() {},
 | 
			
		||||
      mixins: [availabilityMixin],
 | 
			
		||||
    };
 | 
			
		||||
    jest
 | 
			
		||||
      .useFakeTimers('modern')
 | 
			
		||||
      .setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
 | 
			
		||||
    const Constructor = Vue.extend(Component);
 | 
			
		||||
    const wrapper = createWrapper(new Constructor().$mount());
 | 
			
		||||
    expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true);
 | 
			
		||||
    jest.useRealTimers();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -35,6 +35,7 @@
 | 
			
		||||
    "core-js": "3.11.0",
 | 
			
		||||
    "country-code-emoji": "^1.0.0",
 | 
			
		||||
    "date-fns": "2.21.1",
 | 
			
		||||
    "date-fns-tz": "^1.3.3",
 | 
			
		||||
    "dompurify": "2.2.7",
 | 
			
		||||
    "dotenv": "^8.0.0",
 | 
			
		||||
    "foundation-sites": "~6.5.3",
 | 
			
		||||
 
 | 
			
		||||
@@ -5454,6 +5454,11 @@ data-urls@^2.0.0:
 | 
			
		||||
    whatwg-mimetype "^2.3.0"
 | 
			
		||||
    whatwg-url "^8.0.0"
 | 
			
		||||
 | 
			
		||||
date-fns-tz@^1.3.3:
 | 
			
		||||
  version "1.3.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.3.tgz#7884a4b3ed6cd95bfd81831d608e5ef8be500c86"
 | 
			
		||||
  integrity sha512-Gks46gwbSauBQnV3Oofluj1wTm8J0tM7sbSJ9P+cJq/ZnTCpMohTKmmO5Tn+jQ7dyn0+b8G7cY4O2DZ5P/LXcA==
 | 
			
		||||
 | 
			
		||||
date-fns@2.21.1:
 | 
			
		||||
  version "2.21.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.21.1.tgz#679a4ccaa584c0706ea70b3fa92262ac3009d2b0"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user