import 'bootstrap.css';
import '@mescius/wijmo.styles/wijmo.css';
import './styles.css';
import { CollectionView } from '@mescius/wijmo';
import { createSchedulerDefinitionPreset, FlexScheduler, SchedulerBuiltInViewItem, SchedulerAxisBindingType, SchedulerTimeUnit, } from '@mescius/wijmo.scheduler';
import { createDate, getEvents, getTeams } from './data';
const splitWeekdays = [
[],
[{ start: '13:00', end: '17:00' }],
[{ start: '13:00', end: '17:00' }],
[{ start: '13:00', end: '16:00' }],
[{ start: '13:00', end: '17:00' }],
[{ start: '13:00', end: '15:00' }],
[],
];
const compressedWeek = [
[],
[{ start: '08:00', end: '16:00' }],
[{ start: '08:00', end: '16:00' }],
[{ start: '08:00', end: '16:00' }],
[{ start: '08:00', end: '16:00' }],
[],
[],
];
const lunchClosures = [11, 12, 13, 14, 15].map((day) => ({ start: createDate(day, 12), end: createDate(day, 13) }));
const scenarios = {
split: {
workHours: [{ start: '09:00', end: '12:00' }],
workDays: splitWeekdays,
nonworkingTime: [],
primary: 'Shared 09:00-12:00',
secondary: 'Different afternoon hours',
summary: 'Shared morning hours are combined with weekday-specific afternoon shifts.',
},
standard: {
workHours: [{ start: '09:00', end: '17:00' }],
workDays: null,
nonworkingTime: [],
primary: '09:00-17:00',
secondary: 'Monday-Friday',
summary: 'A single workHours rule marks the same office hours across the work week.',
},
lunch: {
workHours: [{ start: '08:00', end: '18:00' }],
workDays: null,
nonworkingTime: lunchClosures,
primary: '08:00-18:00',
secondary: 'Lunch 12:00-13:00',
exception: '5 recurring closures',
summary: 'Concrete non-working ranges close lunch each day inside otherwise open hours.',
},
maintenance: {
workHours: [{ start: '08:00', end: '18:00' }],
workDays: null,
nonworkingTime: [
{ start: createDate(12, 10), end: createDate(12, 12) },
{ start: createDate(14, 14), end: createDate(14, 17) },
],
primary: '08:00-18:00',
secondary: 'Normal weekday coverage',
exception: 'Tue + Thu maintenance',
summary: 'Two date-specific maintenance windows override normal working hours.',
},
compressed: {
workHours: null,
workDays: compressedWeek,
nonworkingTime: [],
primary: '08:00-16:00',
secondary: 'Monday-Thursday',
exception: 'Friday closed',
summary: 'workDays defines a four-day compressed schedule with Friday fully unavailable.',
},
};
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();
function init() {
const preset = createSchedulerDefinitionPreset(SchedulerBuiltInViewItem.WorkWeek);
const scheduler = new FlexScheduler('#theScheduler', {
itemsSource: new CollectionView(getEvents()),
resources: [
{
fieldExpr: 'teamId',
label: 'team',
dataSource: getTeams(),
valueExpr: 'id',
displayExpr: 'name',
backgroundColorExpr: 'backgroundColor',
foreColorExpr: 'foreColor',
},
],
definition: Object.assign(Object.assign({}, preset), { rows: {
bindings: [
{
type: SchedulerAxisBindingType.Time,
unit: SchedulerTimeUnit.Hour,
visible: (args) => args.hour >= 8 && args.hour < 18,
},
],
} }),
toolbarViewItems: [{ name: 'Work Week', key: SchedulerBuiltInViewItem.WorkWeek }],
displayDate: createDate(13, 9),
});
const scenario = document.querySelector('#scenario');
const status = document.querySelector('#scenarioStatus');
const primaryFact = document.getElementById('primaryFact');
const secondaryFact = document.getElementById('secondaryFact');
const exceptionFact = document.getElementById('exceptionFact');
const applyScenario = () => {
const config = scenarios[scenario.value];
scheduler.workHours = config.workHours;
scheduler.workDays = config.workDays;
scheduler.nonworkingTime = config.nonworkingTime;
primaryFact.textContent = config.primary;
secondaryFact.textContent = config.secondary;
exceptionFact.textContent = config.exception || '';
exceptionFact.hidden = !config.exception;
status.value = config.summary;
status.title = config.summary;
};
scenario.addEventListener('change', applyScenario);
applyScenario();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MESCIUS Wijmo Scheduler Working Time</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
<!-- SystemJS -->
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.5/dist/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('./src/app');
</script>
</head>
<body>
<main class="container-fluid">
<div class="working-time-toolbar">
<div class="scenario-control">
<label for="scenario"><i class="bi bi-clock-history" aria-hidden="true"></i> Scenario</label>
<select id="scenario" class="form-select form-select-sm" aria-describedby="scenarioStatus">
<option value="split">Split shifts</option>
<option value="standard">Standard office hours</option>
<option value="lunch">Daily lunch closure</option>
<option value="maintenance">Maintenance exceptions</option>
<option value="compressed">Compressed Monday-Thursday</option>
</select>
</div>
<div class="scenario-facts" aria-label="Active working-time rules">
<span id="primaryFact" class="scenario-fact shared-fact">Shared 09:00-12:00</span>
<span id="secondaryFact" class="scenario-fact weekday-fact">Weekday afternoons</span>
<span id="exceptionFact" class="scenario-fact exception-fact" hidden></span>
</div>
<output id="scenarioStatus" for="scenario" aria-live="polite"></output>
</div>
<div id="theScheduler"></div>
</main>
</body>
</html>
export function createDate(day, hour, minute = 0) {
return new Date(2026, 4, day, hour, minute);
}
export function getTeams() {
return [
{ id: 'operations', name: 'Operations', backgroundColor: '#2563eb', foreColor: '#ffffff' },
{ id: 'design', name: 'Design', backgroundColor: '#7c3aed', foreColor: '#ffffff' },
{ id: 'support', name: 'Support', backgroundColor: '#047857', foreColor: '#ffffff' },
{ id: 'facilities', name: 'Facilities', backgroundColor: '#b45309', foreColor: '#ffffff' },
];
}
export function getEvents() {
return [
{
id: 1,
subject: 'Shift Handoff',
start: createDate(11, 9, 30),
end: createDate(11, 10, 30),
teamId: 'operations',
},
{ id: 2, subject: 'Studio Review', start: createDate(11, 14), end: createDate(11, 15, 30), teamId: 'design' },
{ id: 3, subject: 'Customer Clinic', start: createDate(12, 13), end: createDate(12, 14), teamId: 'support' },
{ id: 4, subject: 'Safety Walk', start: createDate(12, 15), end: createDate(12, 16), teamId: 'facilities' },
{
id: 5,
subject: 'Service Planning',
start: createDate(13, 13, 30),
end: createDate(13, 15),
teamId: 'operations',
},
{ id: 6, subject: 'Prototype Review', start: createDate(14, 9), end: createDate(14, 10, 30), teamId: 'design' },
{ id: 7, subject: 'Coverage Review', start: createDate(14, 15), end: createDate(14, 16), teamId: 'support' },
{
id: 8,
subject: 'Weekly Inspection',
start: createDate(15, 13),
end: createDate(15, 14),
teamId: 'facilities',
},
];
}
body {
color: #1f2937;
font-size: 15px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
user-select: none;
margin: 0;
}
.container-fluid {
box-sizing: border-box;
margin: 0;
padding: 5px;
}
.working-time-toolbar {
display: grid;
grid-template-columns: minmax(240px, 290px) auto minmax(200px, 1fr);
min-height: 46px;
align-items: center;
gap: 8px 14px;
margin-bottom: 6px;
padding: 5px 10px;
border: 1px solid #dbe2ea;
background: #f8fafc;
}
.scenario-control {
display: flex;
align-items: center;
gap: 8px;
}
.scenario-control label {
display: inline-flex;
align-items: center;
gap: 5px;
margin: 0;
font-size: 13px;
font-weight: 650;
}
.scenario-control select {
min-width: 0;
}
.scenario-facts {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.scenario-fact {
padding: 3px 7px;
border: 1px solid #cbd5e1;
border-radius: 3px;
background: #ffffff;
color: #334155;
font-size: 12px;
font-weight: 600;
}
.shared-fact {
border-left: 3px solid #16803c;
}
.weekday-fact {
border-left: 3px solid #0f6cbd;
}
.exception-fact {
border-left: 3px solid #b42318;
}
.working-time-toolbar output {
overflow: hidden;
min-width: 0;
color: #475569;
font-size: 12px;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
.scenario-control select:focus-visible {
outline: 2px solid #0f6cbd;
outline-offset: 1px;
}
#theScheduler {
height: calc(100vh - 52px);
min-height: 560px;
}
@media (max-width: 1050px) {
.working-time-toolbar {
grid-template-columns: minmax(240px, 1fr) minmax(300px, 1fr);
}
.working-time-toolbar output {
grid-column: 1 / -1;
white-space: normal;
}
}
@media (max-width: 640px) {
.working-time-toolbar {
grid-template-columns: 1fr;
}
.scenario-control {
align-items: stretch;
flex-direction: column;
gap: 4px;
}
.working-time-toolbar output {
grid-column: auto;
}
#theScheduler {
height: 680px;
min-height: 680px;
}
}
input,
textarea,
[contenteditable="true"] {
user-select: text;
}
(function (global) {
System.config({
transpiler: 'plugin-babel',
babelOptions: {
es2015: true,
},
meta: {
'*.css': { loader: 'css' },
},
paths: {
// paths serve as alias
'npm:': 'node_modules/',
'cdn:': 'https://cdn.mescius.io/demoapps/packages/wijmojs/5.20262.60-rc/',
'pcdn:': 'https://cdn.jsdelivr.net/npm/',
},
// map tells the System loader where to look for things
map: {
'@mescius/wijmo': 'cdn:@mescius/wijmo/index.js',
'@mescius/wijmo.input': 'cdn:@mescius/wijmo.input/index.js',
'@mescius/wijmo.scheduler': 'cdn:@mescius/wijmo.scheduler/index.js',
'@mescius/wijmo.styles': 'cdn:@mescius/wijmo.styles',
'@mescius/wijmo.cultures': 'cdn:@mescius/wijmo.cultures',
jszip: 'pcdn:jszip@3.8.0/dist/jszip.js',
'bootstrap.css': 'pcdn:bootstrap@5.3.8/dist/css/bootstrap.min.css',
css: 'pcdn:systemjs-plugin-css@0.1.37/css.js',
'plugin-babel': 'pcdn:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'pcdn:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
},
packages: {
src: {
defaultExtension: 'js',
},
node_modules: {
defaultExtension: 'js',
},
},
});
})(this);