import 'bootstrap.css';
import '@mescius/wijmo.styles/wijmo.css';
import './styles.css';
//
import { CollectionView } from '@mescius/wijmo';
import * as wjcScheduler from '@mescius/wijmo.scheduler';
import { getDisplayDate, getEvents, getOtherRoomCandidate, getRooms, getSharedRoomCandidate, getUpdateValues, } from './data';
//
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();
function init() {
const eventView = new CollectionView(getEvents());
const candidateSelect = document.querySelector('#candidateSelect');
const tryButton = document.querySelector('#tryButton');
const status = document.querySelector('#conflictStatus');
let pendingAction = '';
let pendingConflictNames = [];
const scheduler = new wjcScheduler.FlexScheduler('#theScheduler', {
itemsSource: eventView,
eventFields: {
subject: 'subject',
start: 'start',
end: 'end',
},
resources: [
{
fieldExpr: 'roomId',
label: 'Room',
dataSource: getRooms(),
valueExpr: 'id',
displayExpr: 'name',
backgroundColorExpr: 'color',
foreColorExpr: 'textColor',
},
],
definition: getWorkWeekDefinition(),
displayDate: getDisplayDate(),
workHours: [{ start: '08:00', end: '17:00' }],
toolbarViewItems: [{ name: 'Work Week', key: wjcScheduler.SchedulerBuiltInViewItem.WorkWeek }],
});
scheduler.conflictChecked.addHandler((_sender, args) => {
const conflicts = args.result.conflicts;
if (conflicts.length) {
pendingConflictNames = Array.from(new Set(conflicts.flatMap((conflict) => conflict.type === wjcScheduler.SchedulerConflictType.TimeOverlap
? conflict.conflictingItems.map((item) => item.subject)
: [conflict.item.subject])));
setStatus(`Detected before commit | Conflicts with: ${pendingConflictNames.join(', ')}`, 'conflict');
}
});
scheduler.itemChanged.addHandler(() => {
const message = pendingConflictNames.length
? `Committed ${pendingAction} | Conflicts highlighted: ${pendingConflictNames.join(', ')}`
: `Completed ${pendingAction} | No conflict | ${getEventCount()} events`;
setStatus(message, pendingConflictNames.length ? 'conflict' : 'completed');
pendingConflictNames = [];
updateCandidateAvailability();
});
candidateSelect.addEventListener('change', updateCandidateAvailability);
tryButton.addEventListener('click', () => {
pendingConflictNames = [];
switch (candidateSelect.value) {
case 'other':
pendingAction = 'add Support Handoff in Cedar Room';
scheduler.create(getOtherRoomCandidate());
break;
case 'shared':
pendingAction = 'add Roadmap Review in Atlas Room';
scheduler.create(getSharedRoomCandidate());
break;
case 'update': {
const item = getSourceItems().find((event) => event.id === 2);
if (item) {
pendingAction = 'update Client Prep in Atlas Room';
scheduler.update(item, getUpdateValues());
}
break;
}
}
});
function getSourceItems() {
return eventView.sourceCollection;
}
function getEventCount() {
return getSourceItems().length;
}
function updateCandidateAvailability() {
const candidateId = candidateSelect.value === 'other' ? 101 : 102;
const wasAdded = candidateSelect.value !== 'update' && getSourceItems().some((item) => item.id === candidateId);
tryButton.disabled = wasAdded;
tryButton.textContent = wasAdded ? 'Candidate committed' : 'Commit candidate';
}
function setStatus(message, state) {
const icon = document.createElement('i');
const text = document.createElement('span');
icon.className = `bi ${state === 'conflict' ? 'bi-exclamation-octagon-fill' : state === 'completed' ? 'bi-check-circle-fill' : 'bi-shield-check'}`;
icon.setAttribute('aria-hidden', 'true');
text.textContent = message;
status.replaceChildren(icon, text);
status.className = `conflict-status is-${state}`;
}
}
function getWorkWeekDefinition() {
const definition = wjcScheduler.createSchedulerDefinitionPreset(wjcScheduler.SchedulerBuiltInViewItem.WorkWeek);
return Object.assign(Object.assign({}, definition), { rows: {
bindings: [
{
type: wjcScheduler.SchedulerAxisBindingType.Time,
unit: wjcScheduler.SchedulerTimeUnit.Hour,
visible: (args) => args.hour >= 8 && args.hour < 17,
},
],
} });
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MESCIUS Wijmo Scheduler Conflict Detection</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="conflict-toolbar" role="group" aria-label="Conflict check controls">
<div class="conflict-controls">
<div class="field-group candidate-field">
<label for="candidateSelect">Candidate</label>
<select id="candidateSelect" class="form-select form-select-sm">
<option value="other">Different room at same time (no conflict)</option>
<option value="shared">Same room at same time (conflict)</option>
<option value="update">Move existing event into conflict</option>
</select>
</div>
<button id="tryButton" class="btn btn-primary btn-sm" type="button"><i class="bi bi-shield-check" aria-hidden="true"></i> Commit candidate</button>
</div>
<div class="conflict-feedback">
<span>Conflicts are detected before commit, then marked with the Scheduler conflict class</span>
<p id="conflictStatus" class="conflict-status is-ready" role="status" aria-live="assertive"><i class="bi bi-shield-check" aria-hidden="true"></i><span>Ready | 6 events</span></p>
</div>
</div>
<div id="theScheduler"></div>
</main>
</body>
</html>
function createDate(day, hour, minute = 0) {
return new Date(2026, 4, day, hour, minute);
}
export function getDisplayDate() {
return createDate(13, 9);
}
export function getRooms() {
return [
{ id: 'atlas', name: 'Atlas Room', color: '#1d4ed8', textColor: '#ffffff' },
{ id: 'cedar', name: 'Cedar Room', color: '#087f5b', textColor: '#ffffff' },
{ id: 'studio', name: 'Design Studio', color: '#7e22ce', textColor: '#ffffff' },
{ id: 'summit', name: 'Summit Room', color: '#a1420b', textColor: '#ffffff' },
];
}
export function getEvents() {
return [
{ id: 1, subject: 'Team Planning', start: createDate(12, 10), end: createDate(12, 11), roomId: 'atlas' },
{ id: 2, subject: 'Client Prep', start: createDate(12, 13), end: createDate(12, 14), roomId: 'atlas' },
{ id: 3, subject: 'Design Review', start: createDate(11, 14), end: createDate(11, 15), roomId: 'studio' },
{
id: 4,
subject: 'Customer Workshop',
start: createDate(13, 9),
end: createDate(13, 10, 30),
roomId: 'summit',
},
{ id: 5, subject: 'Release Checkpoint', start: createDate(14, 11), end: createDate(14, 12), roomId: 'cedar' },
{ id: 6, subject: 'Weekly Wrap-up', start: createDate(15, 15), end: createDate(15, 16), roomId: 'studio' },
];
}
export function getOtherRoomCandidate() {
return {
id: 101,
subject: 'Support Handoff',
start: createDate(12, 10, 30),
end: createDate(12, 11, 30),
roomId: 'cedar',
};
}
export function getSharedRoomCandidate() {
return {
id: 102,
subject: 'Roadmap Review',
start: createDate(12, 10, 30),
end: createDate(12, 11, 30),
roomId: 'atlas',
};
}
export function getUpdateValues() {
return {
subject: 'Client Prep - moved',
start: createDate(12, 10, 15),
end: createDate(12, 10, 45),
};
}
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;
}
.conflict-toolbar {
display: flex;
flex-direction: column;
gap: 5px;
margin-bottom: 6px;
padding: 6px 9px;
border: 1px solid #dbe2ea;
background: #f8fafc;
}
.conflict-controls,
.conflict-feedback {
display: flex;
align-items: center;
gap: 8px 12px;
width: 100%;
}
.conflict-feedback {
color: #526170;
font-size: 13px;
}
.field-group {
display: flex;
align-items: center;
gap: 7px;
min-width: 0;
}
.field-group label {
margin: 0;
font-weight: 600;
white-space: nowrap;
}
#modeSelect {
width: 118px;
}
#candidateSelect {
width: 286px;
}
.conflict-status {
display: flex;
min-height: 34px;
align-items: center;
gap: 8px;
min-width: 0;
margin: 0 0 1px auto;
padding: 6px 10px;
border: 1px solid transparent;
border-radius: 4px;
color: #334155;
font-size: 13px;
font-weight: 600;
line-height: 1.35;
}
.conflict-status .bi {
flex: 0 0 auto;
font-size: 16px;
}
.conflict-status.is-completed {
border-color: #86d7b0;
background: #edf9f3;
color: #146c43;
}
.conflict-status.is-conflict {
border-color: #f0a4a8;
background: #fff1f2;
color: #a1101d;
box-shadow: 0 0 0 3px rgb(180 35 24 / 8%);
}
.conflict-status.is-ready {
border-color: #cbd5e1;
background: #ffffff;
color: #475569;
}
.form-select:focus,
.btn:focus-visible {
border-color: #0f6cbd;
box-shadow: 0 0 0 2px rgb(15 108 189 / 22%);
}
#theScheduler {
height: calc(100vh - 82px);
min-height: 600px;
}
@media (max-width: 760px) {
.conflict-controls {
align-items: stretch;
flex-direction: column;
}
.field-group {
flex: 0 0 auto;
width: 100%;
}
.field-group select,
#modeSelect,
#candidateSelect {
width: 100%;
}
.field-group label {
width: 70px;
flex: 0 0 70px;
}
#tryButton {
align-self: flex-start;
}
.conflict-status {
flex: 0 0 auto;
margin-left: 0;
overflow-wrap: anywhere;
}
.conflict-feedback {
align-items: flex-start;
flex-direction: column;
}
#theScheduler {
height: 680px;
min-height: 680px;
}
}
.wj-scheduler-item-conflict {
outline: 3px solid #dc2626;
outline-offset: -2px;
box-shadow: 0 0 0 3px rgb(220 38 38 / 20%);
animation: conflict-pulse 1.2s ease-in-out 2;
}
@keyframes conflict-pulse {
50% {
filter: brightness(1.22) saturate(1.35);
}
}
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);