import 'bootstrap.css';
import '@mescius/wijmo.styles/wijmo.css';
import './styles.css';
//
import { CollectionView, Globalize } from '@mescius/wijmo';
import * as wjcGrid from '@mescius/wijmo.grid';
import * as wjcInput from '@mescius/wijmo.input';
import * as wjcScheduler from '@mescius/wijmo.scheduler';
import { createOperationsEvent, getDisplayDate, getEvents, getTeams } 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 teams = getTeams();
const eventView = new CollectionView(getEvents());
const agendaView = new CollectionView([]);
const scheduler = new wjcScheduler.FlexScheduler('#theScheduler', {
itemsSource: eventView,
resources: [
{
fieldExpr: 'teamId',
label: 'Team',
dataSource: teams,
valueExpr: 'id',
displayExpr: 'name',
backgroundColorExpr: 'color',
foreColorExpr: 'textColor',
},
],
definition: createDayDefinition(renderEvent),
displayDate: getDisplayDate(),
workHours: [{ start: '08:00', end: '18:00' }],
toolbarViewItems: [{ name: 'Day', key: wjcScheduler.SchedulerBuiltInViewItem.Day }],
});
const dateInput = new wjcInput.InputDate('#displayDate', { value: scheduler.displayDate, format: 'MMM d, yyyy' });
const teamInput = new wjcInput.ComboBox('#teamFilter', {
itemsSource: [{ id: '', name: 'All teams' }, ...teams],
displayMemberPath: 'name',
selectedValuePath: 'id',
selectedIndex: 0,
});
const startEditor = new wjcInput.InputDateTime(document.createElement('div'), { format: 'g' });
const endEditor = new wjcInput.InputDateTime(document.createElement('div'), { format: 'g' });
const statusMap = new wjcGrid.DataMap(['Scheduled', 'In progress', 'Ready']);
const agendaGrid = new wjcGrid.FlexGrid('#agendaGrid', {
itemsSource: agendaView,
autoGenerateColumns: false,
isReadOnly: false,
selectionMode: wjcGrid.SelectionMode.Row,
headersVisibility: wjcGrid.HeadersVisibility.Column,
loadedRows: (control) => {
control.autoSizeColumns();
control.columns[2].width = 160;
},
columns: [
{ binding: 'start', header: 'Start', format: 'M/d/yy h:mm', editor: startEditor },
{ binding: 'end', header: 'End', format: 'M/d/yy h:mm', editor: endEditor },
{ binding: 'subject', header: 'Event' },
{ binding: 'status', header: 'Status', dataMap: statusMap },
{ binding: 'owner', header: 'Owner' },
{ binding: 'location', header: 'Location' },
],
});
let selectedItem = null;
const refreshAgenda = () => {
const start = new Date(scheduler.displayDate.getFullYear(), scheduler.displayDate.getMonth(), scheduler.displayDate.getDate());
const end = new Date(start);
end.setDate(end.getDate() + 1);
const items = scheduler.getEvents(start, end);
items.forEach((item) => Object.assign(item, createOperationsEvent(item)));
const source = agendaView.sourceCollection;
// keep the grid's view and selection when only event values change
if (source.length !== items.length || source.some((item, index) => item !== items[index])) {
agendaView.sourceCollection = items;
}
else {
agendaView.refresh();
}
document.querySelector('#agendaSummary').value =
`${items.length} ${items.length === 1 ? 'event' : 'events'} on ${Globalize.format(start, 'MMM d')}`;
// re-render the detail panel so it always shows the current values
showDetails(items.includes(selectedItem) ? selectedItem : null);
};
scheduler.itemChanging.addHandler((_sender, args) => {
if (args.request.action === wjcScheduler.SchedulerItemChangeAction.Create && args.request.values) {
Object.assign(args.request.values, createOperationsEvent(args.request.values));
}
});
scheduler.itemChanged.addHandler(() => refreshAgenda());
dateInput.valueChanged.addHandler(() => {
if (dateInput.value)
scheduler.displayDate = dateInput.value;
});
scheduler.displayDateChanged.addHandler(() => {
var _a;
if (((_a = dateInput.value) === null || _a === void 0 ? void 0 : _a.getTime()) !== scheduler.displayDate.getTime())
dateInput.value = scheduler.displayDate;
refreshAgenda();
});
teamInput.selectedIndexChanged.addHandler(() => {
const teamId = teamInput.selectedValue;
eventView.filter = teamId ? (item) => item.teamId === teamId : null;
refreshAgenda();
});
scheduler.hostElement.addEventListener('click', (event) => {
const hit = scheduler.hitTest(event);
if (hit.hitTestType !== wjcScheduler.SchedulerHitTestType.Item)
return;
const item = hit.getHitInfo().item;
const rowIndex = agendaGrid.rows.findIndex((row) => row.dataItem === item);
if (rowIndex >= 0)
agendaGrid.select(rowIndex, 0);
showDetails(item);
});
agendaGrid.selectionChanged.addHandler(() => {
const row = agendaGrid.selection.row;
showDetails(row >= 0 ? agendaGrid.rows[row].dataItem : null);
});
agendaGrid.cellEditEnded.addHandler((_sender, args) => {
eventView.refresh();
refreshAgenda();
agendaGrid.autoSizeColumn(args.col);
agendaGrid.columns[2].width = 160;
});
agendaGrid.formatItem.addHandler((_sender, args) => {
if (args.panel.cellType === wjcGrid.CellType.Cell &&
args.panel.columns[args.col].binding === 'status') {
const item = args.panel.rows[args.row].dataItem;
args.cell.classList.add(`grid-status-${item.status.toLocaleLowerCase().replace(' ', '-')}`);
}
});
function showDetails(item) {
selectedItem = item;
const host = document.querySelector('#eventDetails');
if (!item) {
const empty = document.createElement('div');
empty.className = 'empty-details';
empty.append(createIcon('bi-cursor'), document.createTextNode('Select an event to see its operational details.'));
host.replaceChildren(empty);
return;
}
const title = document.createElement('div');
const icon = document.createElement('span');
const heading = document.createElement('div');
const subject = document.createElement('strong');
const status = document.createElement('span');
title.className = 'details-title';
icon.className = 'details-icon';
icon.appendChild(createIcon(getStatusIcon(item.status)));
subject.textContent = item.subject;
status.className = `details-status status-${item.status.toLocaleLowerCase().replace(' ', '-')}`;
status.textContent = item.status;
heading.append(subject, status);
title.append(icon, heading);
host.replaceChildren(title, createDetail('bi-clock', `${Globalize.format(item.start, 'h:mm tt')} - ${Globalize.format(item.end, 'h:mm tt')}`), createDetail('bi-people', item.team), createOwnerDetail(item.owner), createDetail('bi-geo-alt', item.location));
}
refreshAgenda();
}
function createDayDefinition(itemTemplate) {
return {
key: 'integratedDay',
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: 'teamId' }] },
cellSemantic: wjcScheduler.SchedulerCellSemantic.Timed,
itemTemplate,
};
}
function renderEvent(ctx, element) {
const item = ctx.item;
const title = document.createElement('strong');
const meta = document.createElement('span');
const status = document.createElement('span');
title.textContent = item.subject;
meta.textContent = `${item.owner} | ${item.location}`;
status.className = 'scheduler-event-status';
status.textContent = item.status;
element.textContent = '';
element.append(title, meta, status);
return null;
}
function createOwnerDetail(owner) {
const row = createDetail('bi-person-badge', '');
const image = document.createElement('img');
image.className = 'owner-avatar';
image.src = avatarUrls[owner] || '';
image.alt = owner;
image.addEventListener('error', () => image.remove(), { once: true });
row.append(image, document.createTextNode(owner));
return row;
}
function createDetail(iconName, value) {
const row = document.createElement('div');
row.className = 'detail-row';
row.append(createIcon(iconName), document.createTextNode(value));
return row;
}
function createIcon(iconName) {
const icon = document.createElement('i');
icon.className = `bi ${iconName}`;
icon.setAttribute('aria-hidden', 'true');
return icon;
}
function getStatusIcon(status) {
if (status === 'In progress')
return 'bi-arrow-repeat';
if (status === 'Ready')
return 'bi-check2-circle';
return 'bi-calendar2-check';
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MESCIUS Wijmo Scheduler Component Integration</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="integration-toolbar">
<div class="integration-heading"><strong><i class="bi bi-columns-gap" aria-hidden="true"></i> Daily operations</strong><span>Scheduler, InputDate, ComboBox and FlexGrid share one workflow.</span></div>
<label class="toolbar-field"><span>Date</span><div id="displayDate"></div></label>
<label class="toolbar-field"><span>Team</span><div id="teamFilter"></div></label>
<output id="agendaSummary" aria-live="polite"></output>
</div>
<div class="integration-workspace">
<div id="theScheduler"></div>
<aside class="agenda-panel">
<div class="agenda-header"><div><i class="bi bi-list-check" aria-hidden="true"></i><strong>Daily agenda</strong></div><span>Edit a row to update the schedule</span></div>
<div id="agendaGrid"></div>
<section id="eventDetails" class="event-details" aria-live="polite">
<div class="empty-details"><i class="bi bi-cursor" aria-hidden="true"></i><span>Select an event to see its operational details.</span></div>
</section>
</aside>
</div>
</main>
</body>
</html>
export const DefaultOperationsEvent = {
status: 'Scheduled',
owner: 'Priya Shah',
location: 'Harbor Office',
};
export function createOperationsEvent(values) {
return Object.assign(Object.assign({}, values), { status: values.status || DefaultOperationsEvent.status, owner: values.owner || DefaultOperationsEvent.owner, location: values.location || DefaultOperationsEvent.location });
}
function date(day, hour, minute = 0) {
return new Date(2026, 4, day, hour, minute);
}
export function getDisplayDate() {
return date(13, 9);
}
export function getTeams() {
return [
{ id: 'field', name: 'Field Services', color: '#1d4ed8', textColor: '#ffffff' },
{ id: 'network', name: 'Network Ops', color: '#047857', textColor: '#ffffff' },
{ id: 'customer', name: 'Customer Success', color: '#7e22ce', textColor: '#ffffff' },
{ id: 'facilities', name: 'Facilities', color: '#b45309', textColor: '#ffffff' },
];
}
export function getEvents() {
const teams = new Map(getTeams().map((team) => [team.id, team.name]));
return [
createEvent(1, 'Router Installation', 11, 9, 0, 10, 30, 'field', 'In progress', 'Maya Chen', 'Riverside Branch'),
createEvent(2, 'Capacity Review', 11, 13, 0, 14, 30, 'network', 'Ready', 'Noah Reed', 'Network Center'),
createEvent(3, 'Customer Onboarding', 12, 10, 0, 11, 30, 'customer', 'Scheduled', 'Priya Shah', 'Harbor Office'),
createEvent(4, 'Safety Inspection', 12, 14, 0, 15, 30, 'facilities', 'Scheduled', 'Liam Brooks', 'North Campus'),
createEvent(5, 'Fiber Commissioning', 13, 8, 30, 10, 0, 'network', 'In progress', 'Noah Reed', 'Market Street Hub'),
createEvent(6, 'Branch Site Survey', 13, 10, 30, 12, 0, 'field', 'Scheduled', 'Maya Chen', 'Central Plaza'),
createEvent(7, 'Success Plan Review', 13, 13, 0, 14, 0, 'customer', 'Ready', 'Priya Shah', 'Remote'),
createEvent(8, 'Generator Maintenance', 13, 15, 0, 16, 30, 'facilities', 'Scheduled', 'Liam Brooks', 'West Annex'),
createEvent(13, 'Change Approval', 13, 12, 0, 13, 0, 'network', 'Ready', 'Noah Reed', 'Network Center'),
createEvent(14, 'Customer Check-in', 13, 14, 0, 15, 0, 'customer', 'Scheduled', 'Priya Shah', 'Harbor Office'),
createEvent(15, 'Site Handoff', 13, 16, 30, 17, 30, 'field', 'In progress', 'Maya Chen', 'Central Plaza'),
createEvent(9, 'Access Point Upgrade', 14, 9, 0, 10, 30, 'field', 'Scheduled', 'Maya Chen', 'Airport Terminal'),
createEvent(10, 'Release Monitoring', 14, 13, 30, 15, 0, 'network', 'Ready', 'Noah Reed', 'Network Center'),
createEvent(11, 'Executive Briefing', 15, 10, 0, 11, 30, 'customer', 'Scheduled', 'Priya Shah', 'Summit Room'),
createEvent(12, 'Compliance Walk', 15, 14, 0, 15, 30, 'facilities', 'Ready', 'Liam Brooks', 'South Campus'),
];
function createEvent(id, subject, day, startHour, startMinute, endHour, endMinute, teamId, status, owner, location) {
return createOperationsEvent({
id,
subject,
start: date(day, startHour, startMinute),
end: date(day, endHour, endMinute),
teamId,
team: teams.get(teamId),
status,
owner,
location,
});
}
}
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;
}
.integration-toolbar {
display: flex;
min-height: 58px;
align-items: center;
gap: 12px 18px;
margin-bottom: 6px;
padding: 6px 10px;
border: 1px solid #dbe2ea;
background: #f8fafc;
}
.integration-heading {
display: flex;
min-width: 260px;
flex-direction: column;
}
.integration-heading strong {
display: flex;
align-items: center;
gap: 7px;
}
.integration-heading span {
color: #64748b;
font-size: 12px;
}
.toolbar-field {
display: flex;
min-width: 160px;
flex-direction: column;
gap: 2px;
margin: 0;
}
.toolbar-field > span {
color: #475569;
font-size: 11px;
font-weight: 650;
}
.toolbar-field .wj-control {
width: 100%;
}
#agendaSummary {
margin-left: auto;
color: #075ea8;
font-size: 13px;
font-weight: 650;
}
.integration-workspace {
display: grid;
grid-template-columns: minmax(0, 1fr) 410px;
border: 1px solid #cbd5e1;
}
#theScheduler {
min-width: 0;
height: calc(100vh - 68px);
min-height: 620px;
}
.agenda-panel {
min-width: 0;
border-left: 1px solid #cbd5e1;
background: #f8fafc;
}
.agenda-header {
display: flex;
min-height: 48px;
align-items: center;
justify-content: space-between;
padding: 0 12px;
border-bottom: 1px solid #dbe2ea;
background: #eef3f8;
}
.agenda-header > div {
display: flex;
align-items: center;
gap: 7px;
}
.agenda-header span {
color: #64748b;
font-size: 11px;
}
#agendaGrid {
height: 300px;
border: 0;
border-bottom: 1px solid #cbd5e1;
}
#agendaGrid .wj-header {
background: #f8fafc;
color: #475569;
font-size: 11px;
font-weight: 650;
}
#agendaGrid .wj-cell {
display: flex;
align-items: center;
font-size: 12px;
}
#agendaGrid .wj-cell:not(.wj-header) {
cursor: text;
}
#agendaGrid .grid-status-scheduled {
color: #475569;
}
#agendaGrid .grid-status-in-progress {
color: #075ea8;
font-weight: 650;
}
#agendaGrid .grid-status-ready {
color: #16704a;
font-weight: 650;
}
.event-details {
display: flex;
flex-direction: column;
gap: 9px;
padding: 14px;
background: #fff;
}
.empty-details {
display: flex;
align-items: center;
gap: 8px;
color: #64748b;
font-size: 13px;
line-height: 1.45;
}
.details-title {
display: flex;
align-items: center;
gap: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #e2e8f0;
}
.details-title > div {
display: flex;
min-width: 0;
flex-direction: column;
gap: 3px;
}
.details-title strong {
overflow: hidden;
color: #0f172a;
text-overflow: ellipsis;
white-space: nowrap;
}
.details-icon {
display: inline-grid;
width: 38px;
height: 38px;
place-items: center;
border-radius: 4px;
background: #e0effc;
color: #075ea8;
font-size: 18px;
}
.details-status {
width: fit-content;
padding: 1px 6px;
border-radius: 8px;
background: #eef2f6;
color: #475569;
font-size: 10px;
font-weight: 700;
}
.details-status.status-in-progress {
background: #e0effc;
color: #075ea8;
}
.details-status.status-ready {
background: #def5e9;
color: #16704a;
}
.detail-row {
display: flex;
align-items: center;
gap: 8px;
color: #475569;
font-size: 12px;
}
.detail-row .bi {
width: 16px;
color: #64748b;
text-align: center;
}
.owner-avatar {
width: 24px;
height: 24px;
margin-left: 2px;
border-radius: 50%;
object-fit: cover;
}
#theScheduler .wj-scheduler-item strong {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#theScheduler .wj-scheduler-item > span {
display: block;
overflow: hidden;
margin-top: 2px;
font-size: 10px;
opacity: .9;
text-overflow: ellipsis;
white-space: nowrap;
}
.scheduler-event-status {
font-weight: 650;
}
.wj-control:focus-within {
outline: 2px solid rgb(15 108 189 / 24%);
outline-offset: 1px;
}
@media (max-width: 900px) {
.integration-toolbar {
align-items: flex-end;
flex-wrap: wrap;
}
.integration-heading {
flex: 1 1 100%;
}
#agendaSummary {
margin-left: 0;
}
.integration-workspace {
grid-template-columns: 1fr;
}
.agenda-panel {
border-top: 1px solid #cbd5e1;
border-left: 0;
}
#theScheduler {
height: 680px;
min-height: 680px;
}
}
@media (max-width: 520px) {
.toolbar-field {
flex: 1 1 140px;
min-width: 0;
}
#agendaSummary {
flex: 1 1 100%;
}
.agenda-header {
align-items: flex-start;
flex-direction: column;
justify-content: center;
gap: 2px;
}
}
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.grid': 'cdn:@mescius/wijmo.grid/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);