Settings — Working Hours
Audience: Sales · Support · QA · Management · Engineering · Where in app: Settings → Working Hours (/settings/working-hours) · Plan availability: All plans (no permission gate on the personal screen)
This is the personal Working Hours screen — a team member's own availability for calls and chats. A user can either inherit the organization's hours (Default) or define their own per-day schedule (Custom). Working hours feed the widget's online/offline state, call/chat routing, and AI fallback. The company-wide default schedule and timezone live on a separate Organization Working Hours screen.
What it does
- Lets a user pick Default (use the organization's working hours) or Custom (define their own).
- In Custom mode, per weekday: a toggle for whether that day is worked, plus start and end times.
- Saves to the user's
working_hoursarray (empty array = "use organization hours"). - Validates that, for each enabled day, the end time is after the start time.
How it works
The form (settings/WorkingHours.vue) is a vee-validate + zod form. Default vs Custom is a radio (hours_type).
Default mode
- Stores an empty
working_hoursarray. The UI shows: "Your availability follows your organization's working hours." - On save:
PUT /user/<id>with{ working_hours: [] }.
Custom mode
- Seven weekday rows (Monday–Sunday). Default seed: weekdays enabled, weekends disabled, 09:00–17:00.
- Each enabled day uses
FormTimePickerfor start/end (time objects{ hours, minutes }). - On save, only enabled days are sent, formatted as
HH:MM:
{
"working_hours": [{ "day": "Monday", "start_time": "09:00", "end_time": "17:00" }]
}
- Validation (
superRefine, custom mode only): each enabled day's start/end must be valid times and end must be after start ("End time must be after start time").
Loading existing data
When the user record loads: an empty/absent working_hours → Default mode; otherwise Custom mode with each stored day enabled and its times parsed (parseTimeFromApi tolerates both {hours,minutes} objects and "HH:MM" strings).
How working hours affect the product
Per the working-hours knowledge base, online/offline status drives widget and routing behavior. There are two levels:
| Level | Stored on | Controls |
|---|---|---|
| Organization | company.working_hours[] + company.timezone (+ timezone_offset) | Company default; the Organization Working Hours screen. |
| User (this screen) | user.working_hours (+ user.timezone) | Personal availability. An agent shows online only when both org AND personal hours match. |
When online: the widget shows online copy, chat can connect directly to a human (per chat routing), calls ring active agents, and auto-triggers fire per URL rules. When offline: the widget shows offline copy, chat falls back to capturing an email/question, calls fall back to a callback request or inbound AI call (if enabled), and a missed-call email can trigger. The widget computes isOnline client-side using the company timezone, and the backend re-checks server-side for any decision that matters (routing, AI takeover). See Widget Working Hours.
Configuration & options
| Control | Field | Notes |
|---|---|---|
| Default / Custom | hours_type (default | custom) | Default writes an empty working_hours. |
| Per-day enabled | <day>_enabled | Custom mode only; disabled days are excluded from the payload (= "Closed"). |
| Start / end time | <day>_start, <day>_end → start_time, end_time | HH:MM; end must be after start. |
Saved via PUT /user/<id>.
Behaviors & edge cases
- Empty array means inherit:
working_hours: []is the signal to use the organization schedule — not "unavailable always." - A day with no entry = closed that day (it's simply omitted).
- Validation is custom-mode only: in Default mode time fields aren't validated.
- Both levels must agree: per KB, an agent is online only when org hours AND personal hours are both within range — a user with custom hours can be offline even during company hours, and vice-versa.
- Timezone: the personal screen edits the schedule; the company timezone (and
timezone_offset) governs interpretation. Whether a separateuser.timezoneis edited here is verify (this screen editsworking_hours; timezone is set elsewhere/onboarding). - Unsaved-changes guard: leaving dirty prompts to save (
onBeforeRouteLeave+swal). - Relationship to the widget doc: Widget → Working Hours describes the runtime/online-offline behavior and the org-level schema; this page is the personal settings screen that writes
user.working_hours. They describe the same system at different levels.
Plan & limits
Available on all plans; the personal screen has no permission gate. The Organization Working Hours screen (company default) is gated by settings.organization. No per-plan limits on number of days/hours in the code reviewed.
Technical implementation
- View:
frontend/src/views/settings/WorkingHours.vue(vee-validate + zod; days Monday–Sunday) - Route: name
settings-working-hours, path/settings/working-hours, meta{ title: 'Working Hours' }(no permission) —frontend/src/router/index.js - Org counterpart:
frontend/src/views/settings/organization/WorkingHours.vue, route/settings/organization/working-hours, gated bysettings.organization - Save:
PUT /user/<id>{ working_hours: [{ day, start_time, end_time }] }(or[]for default) - Schema/fields:
user.working_hours,company.working_hours[],company.timezone,company.timezone_offset - Runtime use: widget
isOnlinecomputation (client) + server-side re-check for routing/AI takeover. See services/widget and services/ms-communication.
Related
- Widget — Working Hours — runtime online/offline behavior and the org-level schema.
- Settings — Call Preferences — its "After Hours" card depends on online/offline; AI handles after-hours calls when enabled.
- Auto-triggers — fire based on online state and URL rules.
- Data Model — Company Config — where
working_hours/timezonelive.