import 'bootstrap.css';
import './styles.css';
import '@mescius/wijmo.styles/wijmo.css';
import { FlexGrid, AllowDragging, AllowSorting, SelectionMode, CellType, GroupRow } from '@mescius/wijmo.grid';
import { getData, getMonthlyWorkPlan, getMonthlyWorkAct, getMonthlyOverTime } from './data';
import { InputDate, DateSelectionMode } from '@mescius/wijmo.input';
import { changeType, getType, CollectionView, toggleClass, Tooltip, glbz } from '@mescius/wijmo';
import * as wjGridXlsx from '@mescius/wijmo.grid.xlsx';
import '@mescius/wijmo.cultures/wijmo.culture.ja';
document.readyState === 'complete' ? init() : window.onload = init;
function init() {
let waitingMessage = '承認待ち';
let waitingValue = '----';
let waitingChar = '△';
let tipMessage = '現在、上長の承認待ちです。<br> 承認完了後、労働時間と残業時間は確定されます。';
const setCollectionVew = (data) => {
let item = new CollectionView(data, {
getError: (item, prop) => {
if (prop == 'workEndAct' && item.workEndAct == '' && item.workStart != '') {
return '申告漏れがあります。';
}
//avoid errors for column containing the edit button
if (prop == "apply") {
return null;
}
// no errors
return null;
}
});
return item;
};
let currentData = getData(3);
const flexGrid = new FlexGrid('#flexGrid', {
itemsSource: setCollectionVew(currentData),
headersVisibility: 'Column',
autoGenerateColumns: false,
isReadOnly: true,
allowDragging: AllowDragging.None,
allowSorting: AllowSorting.None,
columns: [
{
binding: 'date',
header: '日付',
width: 80,
isReadOnly: true,
align: 'center',
},
{
binding: 'leaveReason',
header: '休暇事由',
cellTemplate: '<span class="badge bg-secondary bg-${text}">${text}</span>',
isReadOnly: true,
align: 'center',
},
{ binding: 'remarks', header: '備考', width: 100, align: 'center' },
{ binding: 'workStart', header: '始業予定', width: 110, align: 'center' },
{ binding: 'workEnd', header: '終業予定', width: 110, align: 'center' },
{ binding: 'workStartAct', header: '始業時刻', width: 110, align: 'center' },
{ binding: 'workEndAct', header: '終業時刻', width: 108, align: 'center' },
{ binding: 'actualWork', header: '労働時間', width: 110, align: 'center' },
{ binding: 'overtime', header: '残業時間', width: 110, align: 'center' },
{ binding: 'approval', header: '承認', width: 50, align: 'center' },
{ binding: 'apply', header: '申請', width: 180, align: 'center', isRequired: false }
],
frozenColumns: 1,
selectionMode: SelectionMode.None,
formatItem: function (s, e) {
if (e.panel == s.cells) {
if (e.col !== 0)
return;
if (s.getCellData(e.row, e.col).indexOf('日') > 0
|| s.getCellData(e.row, e.col + 2) !== '' && s.getCellData(e.row, e.col + 1) !== '有給休暇' && s.getCellData(e.row, e.col + 1) !== '午後休') {
e.cell.innerHTML = '<div class="sunday">' +
e.cell.innerHTML + '</div>';
}
if (s.getCellData(e.row, e.col).indexOf('土') > 0) {
e.cell.innerHTML = '<div class="saturday">' +
e.cell.innerHTML + '</div>';
}
}
},
});
const setFooterData = (currentData) => {
flexGrid.columnFooters.setCellData(0, 4, '計 ' + getMonthlyWorkPlan(currentData));
flexGrid.columnFooters.setCellData(0, 7, '計 ' + getMonthlyWorkAct(currentData));
flexGrid.columnFooters.setCellData(0, 8, '計 ' + getMonthlyOverTime(currentData));
};
flexGrid.columnFooters.rows.push(new GroupRow());
flexGrid.columnFooters.rows.defaultSize = 43;
setFooterData(currentData);
const inputDateRange = new InputDate('#theInputDateRange', {
alwaysShowCalendar: true,
//predefinedRanges: getPredefinedRanges(),
value: new Date(2023, 3, 1),
//rangeEnd:new Date(2023, 3, 31),
selectionMode: DateSelectionMode.Month,
min: new Date(2023, 0, 1),
max: new Date(2023, 3, 1),
format: 'yyyy年M月度',
valueChanged: (s, e) => {
currentData = getData(s.value.getMonth());
flexGrid.itemsSource = setCollectionVew(currentData);
setFooterData(currentData);
}
});
let mod = false;
flexGrid.formatItem.addHandler(function (s, e) {
if (e.panel == s.cells) {
let col = s.columns[e.col], item = s.rows[e.row].dataItem;
if (item == currentEditItem && currentEditItem.workEndAct == ''
|| item == currentEditItem && mod) {
switch (col.binding) {
case 'apply':
e.cell.innerHTML = document.getElementById('tplBtnEditMode').innerHTML;
e.cell['dataItem'] = item;
break;
case 'workEndAct':
e.cell.innerHTML = '<input class="form-control" type="time" value="18:00" min="18:00" max="24:00"' +
'id="' + col.binding + '" ' +
'value="' + s.getCellData(e.row, e.col, true) + '"/>';
break;
}
}
else {
switch (col.binding) {
case 'apply':
if (s.getCellData(e.row, e.col - 4, true) == '' && s.getCellData(e.row, e.col - 7, true) != '') {
e.cell.innerHTML = document.getElementById('tplBtnViewMode').innerHTML;
e.cell['dataItem'] = item;
}
else if (s.getCellData(e.row, e.col, true) == waitingMessage) {
toggleClass(e.cell, 'approval-wait', true);
e.cell.innerHTML = document.getElementById('tplBtnModifyMode').innerHTML;
}
break;
case 'workEndAct':
if (s.getCellData(e.row, e.col + 4, true) == waitingMessage) {
toggleClass(e.cell, 'approval-wait', true);
}
break;
case 'actualWork':
if (s.getCellData(e.row, e.col, true) == waitingValue) {
toggleClass(e.cell, 'approval-wait', true);
}
break;
case 'overtime':
if (s.getCellData(e.row, e.col, true) == waitingValue) {
toggleClass(e.cell, 'approval-wait', true);
}
break;
case 'approval':
if (s.getCellData(e.row, e.col, true) == waitingChar) {
toggleClass(e.cell, 'approval-wait', true);
}
break;
}
}
}
});
//
// handle button clicks
flexGrid.addEventListener(flexGrid.hostElement, 'click', function (e) {
let targetBtn;
if (e.target instanceof HTMLButtonElement) {
targetBtn = e.target;
}
else if (e.target instanceof HTMLSpanElement && e.target.classList.contains('glyphicon')) {
targetBtn = e.target.parentElement;
}
if (targetBtn) {
let ht = flexGrid.hitTest(e);
let item = flexGrid.rows[ht.row].dataItem;
switch (targetBtn.id) {
case 'btnEdit':
editItem(item);
break;
case 'btnModify':
mod = true;
editItem(item);
break;
case 'btnOK':
commitEdit();
break;
case 'btnCancel':
cancelEdit();
break;
}
}
});
document.getElementById('saveXlsx').addEventListener('click', () => {
wjGridXlsx.FlexGridXlsxConverter.saveAsync(flexGrid, {}, glbz `FlexGird_${new Date()}:d${new Date()}:T.xlsx`);
});
let tip = new Tooltip();
let rng = null;
flexGrid.hostElement.addEventListener('mousemove', function (e) {
let ht = flexGrid.hitTest(e.pageX, e.pageY);
if (!ht.range.equals(rng)) {
if (ht.cellType == CellType.Cell && flexGrid.getCellData(ht.row, 10, true) === waitingMessage && ht.col > 5) {
rng = ht.range;
let cellElement = document.elementFromPoint(e.clientX, e.clientY), cellBounds = flexGrid.getCellBoundingRect(ht.row, ht.col);
if (cellElement.className.indexOf('wj-cell') > -1) {
tip.show(flexGrid.hostElement, tipMessage, cellBounds);
}
}
}
});
flexGrid.hostElement.addEventListener('mouseout', function (e) {
tip.hide();
rng = null;
});
//
flexGrid.rows.defaultSize = 40;
// exit edit mode when scrolling the grid or losing focus
flexGrid.scrollPositionChanged.addHandler(cancelEdit);
flexGrid.lostFocus.addHandler(cancelEdit);
//
// editing commands
let currentEditItem = null;
//
function editItem(item) {
cancelEdit();
currentEditItem = item;
flexGrid.invalidate();
}
//
function commitEdit() {
if (currentEditItem) {
flexGrid.columns.forEach(function (col) {
let input = flexGrid.hostElement.querySelector('#' + col.binding);
if (input) {
let value = changeType(input.value, col.dataType, col.format);
if (getType(value) == col.dataType) {
currentEditItem[col.binding] = value;
currentEditItem['approval'] = waitingChar;
currentEditItem['apply'] = waitingMessage;
currentEditItem['actualWork'] = waitingValue;
currentEditItem['overtime'] = waitingValue;
}
}
});
}
currentEditItem = null;
flexGrid.invalidate();
}
//
function cancelEdit() {
if (currentEditItem) {
currentEditItem = null;
flexGrid.invalidate();
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MESCIUS Wijmo Monthly Report</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SystemJS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('./src/app');
</script>
</head>
<body>
<div class="container-fluid">
<label for="theInputDateRange">処理期間: </label>
<input id="theInputDateRange">
<button id="saveXlsx" class="btn btn-default">Excelエクスポート</button>
<div id="flexGrid"></div>
<!-- template for buttons on items in view mode -->
<div id="tplBtnViewMode" style="display:none">
<button id="btnEdit" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-pencil"></span> 申請漏れを入力する
</button>
</div>
<div id="tplBtnModifyMode" style="display:none">
<button id="btnModify" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-pencil"></span> 申請時刻を変更する
</button>
</div>
<!-- template for buttons on items in edit mode -->
<div id="tplBtnEditMode" style="display:none">
<button id="btnOK" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-ok"></span> 申請
</button>
<button id="btnCancel" class="btn btn-warning btn-sm">
<span class="glyphicon glyphicon-ban-circle"></span> キャンセル
</button>
</div>
</div>
</body>
</html>
const dayOfweek = ['日', '月', '火', '水', '木', '金', '土'];
export function getData(month) {
const data = [];
const nextMonth = new Date(2023, month + 1, 0);
for (let i = 1; i <= nextMonth.getDate(); i++) {
let date = new Date(2023, month, i);
let isHoliday = date.getDay() === 0 || date.getDay() === 6;
let holiday = '公休';
let workStartAct = '8:' + (30 + Math.floor(Math.random() * 29));
let workEndAct = 18 +
Math.floor(Math.random() * 3) +
':' +
(10 + Math.floor(Math.random() * 49));
let actualWork = getSubTime(workEndAct);
let overTime = getOverTime(actualWork);
let remarks = '';
let approval = '◎';
let paid = false;
let halfPaid = false;
//休日設定
if (i == 2 && month == 0 || i == 9 && month == 0 || i == 23 && month == 1 || i == 21 && month == 2) {
isHoliday = true;
holiday = '公休';
remarks = getRemarks(month, i);
approval = '';
}
//任意の有給設定
if (i == 28 && month == 3 || i == 20 && month == 2 || i == 24 && month == 1) {
isHoliday = true;
holiday = '有給休暇';
remarks = '私用のため';
approval = '◎';
paid = true;
}
if (i == 12 && month == 3) {
halfPaid = true;
holiday = '午後休';
remarks = '体調不良';
approval = '◎';
paid = true;
workStartAct = '8:45';
workEndAct = '12:08';
actualWork = '3:00';
overTime = '0:00';
}
if (i == 7 && month == 3 || i == 13 && month == 3 || i == 25 && month == 3) {
overTime = '';
actualWork = '';
workEndAct = '';
}
data.push({
date: date.getMonth() +
1 +
'/' +
date.getDate() +
' (' +
dayOfweek[date.getDay()] +
') ',
leaveReason: isHoliday || halfPaid ? holiday : '',
remarks: remarks,
workStart: isHoliday ? '' : '9:00',
workEnd: isHoliday ? '' : '18:00',
workStartAct: isHoliday ? '' : workStartAct,
workEndAct: isHoliday ? '' : workEndAct,
actualWork: isHoliday ? '' : actualWork,
overtime: isHoliday ? '' : overTime,
approval: isHoliday && !paid ? '' : approval
});
}
return data;
}
export const getMonthlyWorkPlan = (data) => {
let items = data.filter(item => item.leaveReason !== '');
let workDayCount = data.length - items.length;
let workMonthlydata = 8 * workDayCount;
let halfPaid = data.filter(item => item.leaveReason == '午後休');
if (halfPaid.length > 0) {
workMonthlydata = workMonthlydata + 3;
}
return workMonthlydata + ':00';
};
export const getMonthlyWorkAct = (data) => {
let items = data.filter(item => item.actualWork !== '');
let workMonthlyAct = '0:00';
items.forEach((item) => {
workMonthlyAct = getActTime(workMonthlyAct, item.actualWork);
});
return workMonthlyAct;
};
export const getMonthlyOverTime = (data) => {
let items = data.filter(item => item.overtime !== '');
let workMonthlyOverTime = '0:00';
items.forEach((item) => {
workMonthlyOverTime = getActTime(workMonthlyOverTime, item.overtime);
});
//4月限定処理
let aprilData = data.filter(item => item.date.includes('4/1'));
if (aprilData.length > 0) {
let data = workMonthlyOverTime.split(':');
let subHour = Number(data[0]) - 8;
workMonthlyOverTime = subHour + ':' + data[1];
}
return workMonthlyOverTime;
};
const getSubTime = (time2) => {
let time2a = time2.split(':');
let time2s = time2a[0] * 60 * 60 + time2a[1] * 60;
let subTime = time2s - 36000;
let hour = Math.floor(subTime / 3600);
let min = Math.floor((subTime % 3600) / 60);
return hour + ':' + min;
};
const getActTime = (time1, time2) => {
let time1a = time1.split(':');
let time2a = time2.split(':');
let hour = Number(time1a[0]) + Number(time2a[0]);
let min = Number(time1a[1]) + Number(time2a[1]);
if (min >= 60) {
min = min - 60;
hour++;
}
if (min < 10) {
min = '0' + min;
}
return hour + ':' + min;
};
const getOverTime = (time) => {
let timea = time.split(':');
let overtimeh = Number(timea[0]) - 8;
return overtimeh + ':' + timea[1];
};
const getRemarks = (m, d) => {
if (d == 2 && m == 0) {
return '振替休日';
}
else if (d == 9 && m == 0) {
return '成人の日';
}
else if (d == 23 && m == 1) {
return '天皇誕生日';
}
else if (d == 21 && m == 2) {
return '春分の日';
}
};
.wj-flexgrid {
height: 500px;
}
.bg-公休 {
background-color: green;
}
.bg-有給休暇 {
background-color: skyblue;
}
.bg-午後休 {
background-color: rgba(27, 161, 56, 0.466);
}
.badge {
padding: 6px 25px;
}
.saturday {
color:blue;
}
.sunday {
color: red;
}
.wj-cell.wj-align-center.approval-wait{
background-color:rgba(84, 221, 255, 0.808);
}
.wj-cell{
font-size: 15px;
}
(function (global) {
System.config({
transpiler: 'plugin-babel',
babelOptions: {
es2015: true
},
meta: {
'*.css': { loader: 'css' }
},
paths: {
// paths serve as alias
'npm:': 'node_modules/'
},
// map tells the System loader where to look for things
map: {
'jszip': 'npm:jszip/dist/jszip.js',
'@mescius/wijmo': 'npm:@mescius/wijmo/index.js',
'@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js',
'@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles',
'@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures',
'@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js',
'@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js',
'@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js',
'@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js',
'@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js',
'@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js',
'@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js',
'@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js',
'@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js',
'@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js',
'@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js',
'@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js',
'@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js',
'@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js',
'@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js',
'@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js',
'@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js',
'@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js',
'@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js',
'@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js',
'@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js',
'@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js',
'@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js',
'@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js',
'@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js',
'@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js',
'@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js',
'@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js',
'@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js',
'@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js',
'@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js',
'@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js',
'@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js',
'@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js',
'@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js',
'@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js',
'@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js',
'@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js',
'@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js',
'@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js',
'@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js',
'@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js',
'jszip': 'npm:jszip/dist/jszip.js',
'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css',
'css': 'npm:systemjs-plugin-css/css.js',
'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js'
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
src: {
defaultExtension: 'js'
},
"node_modules": {
defaultExtension: 'js'
},
}
});
})(this);