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, getSpecialists } from './data';
//
const avatarUrls = {
'Maya Chen': 'resources/avatars/maya-chen.png',
'Noah Reed': 'resources/avatars/noah-reed.png',
'Priya Shah': 'resources/avatars/priya-shah.png',
'Liam Brooks': 'resources/avatars/liam-brooks.png',
};
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();
function init() {
const specialists = getSpecialists();
const specialistMap = new Map(specialists.map((person) => [person.id, person]));
renderLegend(specialists);
new wjcScheduler.FlexScheduler('#theScheduler', {
itemsSource: new CollectionView(getEvents()),
resources: [
{
fieldExpr: 'specialistIds',
label: 'Specialists',
dataSource: specialists,
allowMultiple: true,
valueExpr: 'id',
displayExpr: 'name',
backgroundColorExpr: 'color',
foreColorExpr: 'textColor',
},
],
definition: getDefinition((ctx, element) => renderEvent(ctx, element, specialistMap)),
displayDate: getDisplayDate(),
workHours: [{ start: '08:00', end: '18:00' }],
toolbarViewItems: [{ name: 'Day', key: wjcScheduler.SchedulerBuiltInViewItem.Day }],
});
}
function getDefinition(itemTemplate) {
return {
key: 'multiAssignment',
rows: {
bindings: [
{
type: wjcScheduler.SchedulerAxisBindingType.Time,
unit: wjcScheduler.SchedulerTimeUnit.Hour,
visible: (args) => args.hour >= 8 && args.hour < 18,
},
],
},
columns: {
bindings: [
{
type: wjcScheduler.SchedulerAxisBindingType.Resource,
fieldExpr: 'specialistIds',
},
],
},
cellSemantic: wjcScheduler.SchedulerCellSemantic.Timed,
itemTemplate,
};
}
function renderEvent(ctx, element, specialists) {
const item = ctx.item;
const header = document.createElement('span');
const icon = document.createElement('i');
const avatars = document.createElement('span');
header.className = 'assignment-title';
icon.className = `bi ${getTypeIcon(item.type)}`;
icon.setAttribute('aria-hidden', 'true');
header.append(icon, document.createTextNode(item.subject));
avatars.className = 'event-avatars';
item.specialistIds.forEach((id) => {
const person = specialists.get(id);
const avatar = document.createElement('span');
avatar.className = 'event-avatar';
avatar.title = `${person.name}, ${person.role}`;
appendAvatarImage(avatar, person);
avatars.appendChild(avatar);
});
element.textContent = '';
element.append(header, avatars);
return null;
}
function getTypeIcon(type) {
switch (type) {
case 'installation':
return 'bi-router';
case 'review':
return 'bi-clipboard-check';
case 'handoff':
return 'bi-person-check';
}
}
function renderLegend(specialists) {
const legend = document.querySelector('#peopleLegend');
legend.replaceChildren(...specialists.map((person) => {
const item = document.createElement('li');
const avatar = document.createElement('span');
avatar.className = 'person-avatar';
avatar.style.backgroundColor = person.color;
appendAvatarImage(avatar, person);
item.append(avatar, document.createTextNode(person.name));
return item;
}));
}
function appendAvatarImage(element, person) {
const image = document.createElement('img');
image.src = avatarUrls[person.name] || '';
image.alt = person.name;
image.addEventListener('error', () => {
element.textContent = person.initials;
}, { once: true });
element.appendChild(image);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MESCIUS Wijmo Scheduler Multi-assignment</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">
<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="assignment-toolbar">
<div>
<strong><i class="bi bi-people-fill" aria-hidden="true"></i> Shared assignments</strong>
<span>One event can be assigned to multiple specialists.</span>
</div>
<ul id="peopleLegend" class="people-legend" aria-label="Specialists"></ul>
</div>
<div id="theScheduler"></div>
</main>
</body>
</html>
function date(hour, minute = 0) {
return new Date(2026, 4, 13, hour, minute);
}
export function getDisplayDate() {
return date(9);
}
export function getSpecialists() {
return [
{ id: 1, name: 'Maya Chen', initials: 'MC', role: 'Network', color: '#2563eb', textColor: '#ffffff' },
{ id: 2, name: 'Noah Reed', initials: 'NR', role: 'Field ops', color: '#0f766e', textColor: '#ffffff' },
{ id: 3, name: 'Priya Shah', initials: 'PS', role: 'Customer', color: '#7c3aed', textColor: '#ffffff' },
{ id: 4, name: 'Liam Brooks', initials: 'LB', role: 'Safety', color: '#a3460a', textColor: '#ffffff' },
];
}
export function getEvents() {
return [
{
id: 1,
subject: 'Branch Installation',
start: date(8, 30),
end: date(10),
specialistIds: [1, 2],
type: 'installation',
},
{ id: 2, subject: 'Safety Review', start: date(10, 30), end: date(12), specialistIds: [2, 4], type: 'review' },
{
id: 3,
subject: 'Customer Handoff',
start: date(13),
end: date(14, 30),
specialistIds: [1, 3],
type: 'handoff',
},
{
id: 4,
subject: 'Commissioning',
start: date(15),
end: date(16, 30),
specialistIds: [1, 2, 3],
type: 'installation',
},
];
}
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;
}
.assignment-toolbar {
display: flex;
align-items: center;
gap: 10px 20px;
min-height: 54px;
margin-bottom: 6px;
padding: 6px 10px;
border: 1px solid #dbe2ea;
background: #f8fafc;
}
.assignment-toolbar > div {
display: flex;
flex-direction: column;
gap: 2px;
}
.assignment-toolbar strong {
display: flex;
align-items: center;
gap: 7px;
}
.assignment-toolbar > div > span {
color: #64748b;
font-size: 12px;
}
.people-legend {
display: flex;
flex-wrap: wrap;
gap: 6px 14px;
margin: 0 0 0 auto;
padding: 0;
list-style: none;
}
.people-legend li {
display: flex;
align-items: center;
gap: 6px;
color: #475569;
font-size: 12px;
}
.person-avatar,
.event-avatar {
display: inline-grid;
place-items: center;
border-radius: 50%;
color: #ffffff;
font-weight: 700;
}
.person-avatar {
width: 25px;
height: 25px;
font-size: 10px;
}
.person-avatar img,
.event-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.assignment-title {
display: flex;
align-items: center;
gap: 5px;
overflow: hidden;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.event-avatars {
display: flex;
margin-top: 4px;
padding-left: 5px;
}
.event-avatar {
width: 22px;
height: 22px;
margin-left: -5px;
border: 2px solid rgb(255 255 255 / 80%);
background: rgb(15 23 42 / 45%);
font-size: 8px;
}
#theScheduler {
height: calc(100vh - 62px);
min-height: 600px;
}
@media (max-width: 760px) {
.assignment-toolbar {
align-items: flex-start;
flex-direction: column;
}
.people-legend {
margin-left: 0;
}
#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);