Fix: add option to choose 24 hour working slot (#4018)

* Add option to choose 24 hour working slot

* fix spec

* add check to update open hour and close hour for open all day

* update 24 hour working slot change in widget side

* add validation to check open_all_day and closed_all_day true at the same time
This commit is contained in:
Aswin Dev P.S 2022-02-22 01:28:49 -08:00 committed by GitHub
parent 7ba24b90c4
commit e348db1e37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 151 additions and 19 deletions

View file

@ -463,7 +463,8 @@
"HOURS": "hours", "HOURS": "hours",
"VALIDATION_ERROR": "Starting time should be before closing time.", "VALIDATION_ERROR": "Starting time should be before closing time.",
"CHOOSE": "Choose" "CHOOSE": "Choose"
} },
"ALL_DAY":"All-Day"
}, },
"IMAP": { "IMAP": {
"TITLE": "IMAP", "TITLE": "IMAP",

View file

@ -4,7 +4,7 @@
<input <input
v-model="isDayEnabled" v-model="isDayEnabled"
name="enable-day" name="enable-day"
class="enable-day" class="enable-checkbox"
type="checkbox" type="checkbox"
:title="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.ENABLE')" :title="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.ENABLE')"
/> />
@ -14,26 +14,38 @@
</div> </div>
<div v-if="isDayEnabled" class="hours-select-wrap"> <div v-if="isDayEnabled" class="hours-select-wrap">
<div class="hours-range"> <div class="hours-range">
<div class="checkbox-wrap open-all-day">
<input
v-model="isOpenAllDay"
name="enable-open-all-day"
class="enable-checkbox"
type="checkbox"
:title="$t('INBOX_MGMT.BUSINESS_HOURS.ALL_DAY')"
/>
<span>{{ $t('INBOX_MGMT.BUSINESS_HOURS.ALL_DAY') }}</span>
</div>
<multiselect <multiselect
v-model="fromTime" v-model="fromTime"
:options="timeSlots" :options="fromTimeSlots"
deselect-label="" deselect-label=""
select-label="" select-label=""
selected-label="" selected-label=""
:placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')" :placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')"
:allow-empty="false" :allow-empty="false"
:disabled="isOpenAllDay"
/> />
<div class="separator-icon"> <div class="separator-icon">
<fluent-icon icon="subtract" type="solid" size="16" /> <fluent-icon icon="subtract" type="solid" size="16" />
</div> </div>
<multiselect <multiselect
v-model="toTime" v-model="toTime"
:options="timeSlots" :options="toTimeSlots"
deselect-label="" deselect-label=""
select-label="" select-label=""
selected-label="" selected-label=""
:placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')" :placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')"
:allow-empty="false" :allow-empty="false"
:disabled="isOpenAllDay"
/> />
</div> </div>
<div v-if="hasError" class="date-error"> <div v-if="hasError" class="date-error">
@ -79,9 +91,14 @@ export default {
}, },
}, },
computed: { computed: {
timeSlots() { fromTimeSlots() {
return timeSlots; return timeSlots;
}, },
toTimeSlots() {
return timeSlots.filter(slot => {
return slot !== '12:00 AM';
});
},
isDayEnabled: { isDayEnabled: {
get() { get() {
return this.timeSlot.from && this.timeSlot.to; return this.timeSlot.from && this.timeSlot.to;
@ -93,12 +110,14 @@ export default {
from: timeSlots[0], from: timeSlots[0],
to: timeSlots[16], to: timeSlots[16],
valid: true, valid: true,
openAllDay: false,
} }
: { : {
...this.timeSlot, ...this.timeSlot,
from: '', from: '',
to: '', to: '',
valid: false, valid: false,
openAllDay: false,
}; };
this.$emit('update', newSlot); this.$emit('update', newSlot);
}, },
@ -146,15 +165,39 @@ export default {
return parse(this.toTime, 'hh:mm a', new Date()); return parse(this.toTime, 'hh:mm a', new Date());
}, },
totalHours() { totalHours() {
const totalHours = differenceInMinutes(this.toDate, this.fromDate) / 60; if (this.timeSlot.openAllDay) {
if (this.toTime === '12:00 AM') { return 24;
return 24 + totalHours;
} }
const totalHours = differenceInMinutes(this.toDate, this.fromDate) / 60;
return totalHours; return totalHours;
}, },
hasError() { hasError() {
return !this.timeSlot.valid; return !this.timeSlot.valid;
}, },
isOpenAllDay: {
get() {
return this.timeSlot.openAllDay;
},
set(value) {
if (value) {
this.$emit('update', {
...this.timeSlot,
from: '12:00 AM',
to: '11:59 PM',
valid: true,
openAllDay: value,
});
} else {
this.$emit('update', {
...this.timeSlot,
from: '09:00 AM',
to: '05:00 PM',
valid: true,
openAllDay: value,
});
}
},
},
}, },
}; };
</script> </script>
@ -182,7 +225,7 @@ export default {
box-sizing: content-box; box-sizing: content-box;
border-bottom: 1px solid var(--color-border-light); border-bottom: 1px solid var(--color-border-light);
} }
.enable-day { .enable-checkbox {
margin: 0; margin: 0;
} }
@ -240,4 +283,17 @@ export default {
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
color: var(--r-300); color: var(--r-300);
} }
.open-all-day {
margin-right: var(--space-medium);
span {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
margin-left: var(--space-smaller);
}
input {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
}
}
</style> </style>

View file

@ -86,6 +86,7 @@ export const timeSlotParse = timeSlots => {
close_hour: closeHour, close_hour: closeHour,
close_minutes: closeMinutes, close_minutes: closeMinutes,
closed_all_day: closedAllDay, closed_all_day: closedAllDay,
open_all_day: openAllDay,
} = slot; } = slot;
const from = closedAllDay ? '' : getTime(openHour, openMinutes); const from = closedAllDay ? '' : getTime(openHour, openMinutes);
const to = closedAllDay ? '' : getTime(closeHour, closeMinutes); const to = closedAllDay ? '' : getTime(closeHour, closeMinutes);
@ -95,13 +96,15 @@ export const timeSlotParse = timeSlots => {
to, to,
from, from,
valid: !closedAllDay, valid: !closedAllDay,
openAllDay,
}; };
}); });
}; };
export const timeSlotTransform = timeSlots => { export const timeSlotTransform = timeSlots => {
return timeSlots.map(slot => { return timeSlots.map(slot => {
const closed = !(slot.to && slot.from); const closed = slot.openAllDay ? false : !(slot.to && slot.from);
const openAllDay = slot.openAllDay;
let fromDate = ''; let fromDate = '';
let toDate = ''; let toDate = '';
let openHour = ''; let openHour = '';
@ -125,6 +128,7 @@ export const timeSlotTransform = timeSlots => {
open_minutes: openMinutes, open_minutes: openMinutes,
close_hour: closeHour, close_hour: closeHour,
close_minutes: closeMinutes, close_minutes: closeMinutes,
open_all_day: openAllDay,
}; };
}); });
}; };

View file

@ -40,6 +40,7 @@ describe('#timeSlotParse', () => {
close_hour: 4, close_hour: 4,
close_minutes: 30, close_minutes: 30,
closed_all_day: false, closed_all_day: false,
open_all_day: false,
}; };
expect(timeSlotParse([slot])).toStrictEqual([ expect(timeSlotParse([slot])).toStrictEqual([
@ -48,6 +49,7 @@ describe('#timeSlotParse', () => {
from: '01:30 AM', from: '01:30 AM',
to: '04:30 AM', to: '04:30 AM',
valid: true, valid: true,
openAllDay: false,
}, },
]); ]);
}); });
@ -60,6 +62,7 @@ describe('#timeSlotTransform', () => {
from: '01:30 AM', from: '01:30 AM',
to: '04:30 AM', to: '04:30 AM',
valid: true, valid: true,
openAllDay: false,
}; };
expect(timeSlotTransform([slot])).toStrictEqual([ expect(timeSlotTransform([slot])).toStrictEqual([
@ -70,6 +73,7 @@ describe('#timeSlotTransform', () => {
close_hour: 4, close_hour: 4,
close_minutes: 30, close_minutes: 30,
closed_all_day: false, closed_all_day: false,
open_all_day: false,
}, },
]); ]);
}); });

View file

@ -31,9 +31,12 @@ export default {
closeHour, closeHour,
closeMinute, closeMinute,
closedAllDay, closedAllDay,
openAllDay,
} = this.currentDayAvailability; } = this.currentDayAvailability;
const { utcOffset } = this.channelConfig; const { utcOffset } = this.channelConfig;
if (openAllDay) return true;
if (closedAllDay) return false; if (closedAllDay) return false;
const startTime = buildDateFromTime(openHour, openMinute, utcOffset); const startTime = buildDateFromTime(openHour, openMinute, utcOffset);
@ -56,6 +59,7 @@ export default {
openMinute: workingHourConfig.open_minutes, openMinute: workingHourConfig.open_minutes,
closeHour: workingHourConfig.close_hour, closeHour: workingHourConfig.close_hour,
closeMinute: workingHourConfig.close_minutes, closeMinute: workingHourConfig.close_minutes,
openAllDay: workingHourConfig.open_all_day,
}; };
}, },
isInBusinessHours() { isInBusinessHours() {

View file

@ -3,7 +3,7 @@
module OutOfOffisable module OutOfOffisable
extend ActiveSupport::Concern extend ActiveSupport::Concern
OFFISABLE_ATTRS = %w[day_of_week closed_all_day open_hour open_minutes close_hour close_minutes].freeze OFFISABLE_ATTRS = %w[day_of_week closed_all_day open_hour open_minutes close_hour close_minutes open_all_day].freeze
included do included do
has_many :working_hours, dependent: :destroy_async has_many :working_hours, dependent: :destroy_async
@ -29,7 +29,8 @@ module OutOfOffisable
# "open_hour"=>9, # "open_hour"=>9,
# "open_minutes"=>0, # "open_minutes"=>0,
# "close_hour"=>17, # "close_hour"=>17,
# "close_minutes"=>0},...] # "close_minutes"=>0,
# "open_all_day=>false" },...]
def update_working_hours(params) def update_working_hours(params)
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
params.each do |working_hour| params.each do |working_hour|
@ -41,12 +42,12 @@ module OutOfOffisable
private private
def create_default_working_hours def create_default_working_hours
working_hours.create!(day_of_week: 0, closed_all_day: true) working_hours.create!(day_of_week: 0, closed_all_day: true, open_all_day: false)
working_hours.create!(day_of_week: 1, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) working_hours.create!(day_of_week: 1, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false)
working_hours.create!(day_of_week: 2, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) working_hours.create!(day_of_week: 2, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false)
working_hours.create!(day_of_week: 3, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) working_hours.create!(day_of_week: 3, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false)
working_hours.create!(day_of_week: 4, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) working_hours.create!(day_of_week: 4, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false)
working_hours.create!(day_of_week: 5, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) working_hours.create!(day_of_week: 5, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false)
working_hours.create!(day_of_week: 6, closed_all_day: true) working_hours.create!(day_of_week: 6, closed_all_day: true, open_all_day: false)
end end
end end

View file

@ -7,6 +7,7 @@
# close_minutes :integer # close_minutes :integer
# closed_all_day :boolean default(FALSE) # closed_all_day :boolean default(FALSE)
# day_of_week :integer not null # day_of_week :integer not null
# open_all_day :boolean default(FALSE)
# open_hour :integer # open_hour :integer
# open_minutes :integer # open_minutes :integer
# created_at :datetime not null # created_at :datetime not null
@ -22,6 +23,7 @@
class WorkingHour < ApplicationRecord class WorkingHour < ApplicationRecord
belongs_to :inbox belongs_to :inbox
before_validation :ensure_open_all_day_hours
before_save :assign_account before_save :assign_account
validates :open_hour, presence: true, unless: :closed_all_day? validates :open_hour, presence: true, unless: :closed_all_day?
@ -35,6 +37,7 @@ class WorkingHour < ApplicationRecord
validates :close_minutes, inclusion: 0..59, unless: :closed_all_day? validates :close_minutes, inclusion: 0..59, unless: :closed_all_day?
validate :close_after_open, unless: :closed_all_day? validate :close_after_open, unless: :closed_all_day?
validate :open_all_day_and_closed_all_day
def self.today def self.today
find_by(day_of_week: Date.current.wday) find_by(day_of_week: Date.current.wday)
@ -69,4 +72,19 @@ class WorkingHour < ApplicationRecord
errors.add(:close_hour, 'Closing time cannot be before opening time') errors.add(:close_hour, 'Closing time cannot be before opening time')
end end
def ensure_open_all_day_hours
return unless open_all_day?
self.open_hour = 0
self.open_minutes = 0
self.close_hour = 23
self.close_minutes = 59
end
def open_all_day_and_closed_all_day
return unless open_all_day? && closed_all_day?
errors.add(:base, 'open_all_day and closed_all_day cannot be true at the same time')
end
end end

View file

@ -0,0 +1,5 @@
class AddOpenAllDayToWorkingHour < ActiveRecord::Migration[6.1]
def change
add_column :working_hours, :open_all_day, :boolean, default: false
end
end

View file

@ -773,6 +773,7 @@ ActiveRecord::Schema.define(version: 2022_02_18_120357) do
t.integer "close_minutes" t.integer "close_minutes"
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false
t.boolean "open_all_day", default: false
t.index ["account_id"], name: "index_working_hours_on_account_id" t.index ["account_id"], name: "index_working_hours_on_account_id"
t.index ["inbox_id"], name: "index_working_hours_on_inbox_id" t.index ["inbox_id"], name: "index_working_hours_on_inbox_id"
end end

View file

@ -50,4 +50,42 @@ RSpec.describe WorkingHour do
expect(described_class.today.closed_now?).to be true expect(described_class.today.closed_now?).to be true
end end
end end
context 'when open_all_day is true' do
let(:inbox) { create(:inbox) }
before do
Time.zone = 'UTC'
inbox.working_hours.find_by(day_of_week: 5).update(open_all_day: true)
travel_to '18.02.2022 11:00'.to_datetime
end
it 'updates open hour and close hour' do
expect(described_class.today.open_all_day?).to be true
expect(described_class.today.open_hour).to be 0
expect(described_class.today.open_minutes).to be 0
expect(described_class.today.close_hour).to be 23
expect(described_class.today.close_minutes).to be 59
end
end
context 'when open_all_day and closed_all_day true at the same time' do
let(:inbox) { create(:inbox) }
before do
Time.zone = 'UTC'
inbox.working_hours.find_by(day_of_week: 5).update(open_all_day: true)
travel_to '18.02.2022 11:00'.to_datetime
end
it 'throws validation error' do
working_hour = inbox.working_hours.find_by(day_of_week: 5)
working_hour.closed_all_day = true
expect(working_hour.invalid?).to be true
expect do
working_hour.save!
end.to raise_error(ActiveRecord::RecordInvalid,
'Validation failed: open_all_day and closed_all_day cannot be true at the same time')
end
end
end end