Merge pull request 'task-stepper' (#1) from task-stepper into master

Reviewed-on: #1
This commit is contained in:
Christian Seyfferth 2021-03-12 23:17:46 +01:00
commit 67674f1a67
3 changed files with 389 additions and 276 deletions

228
app.js
View File

@ -24,19 +24,7 @@ if (!FileReader.prototype.readAsBinaryString) {
} }
} }
var options = {
timepicker: {
twelveHour: false,
}
};
document.addEventListener('DOMContentLoaded', function() {
M.AutoInit();
var elems = document.querySelectorAll('.timepicker');
var timepickers = M.Timepicker.init(elems, options.timepicker);
});
//localStorage persistence //localStorage persistence
var SHIFT_STORAGE_KEY = "dienstplan_chrosey"; var SHIFT_STORAGE_KEY = "dienstplan_chrosey";
var RULE_STORAGE_KEY = "regeln_chrosey"; var RULE_STORAGE_KEY = "regeln_chrosey";
var shiftStorage = { var shiftStorage = {
@ -65,13 +53,13 @@ var ruleStorage = {
fetch: function () { fetch: function () {
'use strict'; 'use strict';
var parsed = JSON.parse(localStorage.getItem(RULE_STORAGE_KEY)) | []; var parsed = JSON.parse(localStorage.getItem(RULE_STORAGE_KEY)) | [];
var rules = parsed.length > 0 var rules = parsed.length > 0 ?
? parsed.map((e,i) => { parsed.map((e, i) => {
var r = Rule.thaw(e); var r = Rule.thaw(e);
r.id = i; r.id = i;
return r; return r;
}) }) :
: Rule.defaults(); Rule.defaults();
ruleStorage.uid = rules.length; ruleStorage.uid = rules.length;
return rules; return rules;
}, },
@ -82,7 +70,7 @@ var ruleStorage = {
} }
}; };
Vue.component('ask-format-modal',{ Vue.component('ask-format-modal', {
template: ` template: `
<div id="ask-format-modal" class="modal"> <div id="ask-format-modal" class="modal">
<div class="modal-content"> <div class="modal-content">
@ -131,10 +119,10 @@ Vue.component('chip-input', {
} }
}, },
watch:{ watch: {
initData:{ initData: {
deep: true, deep: true,
handler(n,o) { handler(n, o) {
if (n !== o) { if (n !== o) {
this.initialize(); this.initialize();
this.$emit('init'); this.$emit('init');
@ -150,14 +138,14 @@ Vue.component('chip-input', {
methods: { methods: {
initialize() { initialize() {
this.chips = this.initData.map(e => e); this.chips = this.initData.map(e => e);
var el = $('#'+this.name)[0]; var el = $('#' + this.name)[0];
this.instance = M.Chips.init(el, { this.instance = M.Chips.init(el, {
data: this.chips, data: this.chips,
onChipAdd: () => { onChipAdd: () => {
this.$emit("change",this.chipsData); this.$emit("change", this.chipsData);
}, },
onChipDelete: () => { onChipDelete: () => {
this.$emit("change",this.chipsData); this.$emit("change", this.chipsData);
} }
}); });
} }
@ -177,6 +165,7 @@ var app = new Vue({
blob: null, blob: null,
dp_sheet: '', dp_sheet: '',
deletedShift: '', deletedShift: '',
format: '',
remaining: shiftStorage.count(), remaining: shiftStorage.count(),
selectedShift: new Shift({}), selectedShift: new Shift({}),
selectedShiftIndex: -1, selectedShiftIndex: -1,
@ -184,7 +173,10 @@ var app = new Vue({
selectedRuleIndex: -1, selectedRuleIndex: -1,
saveto: 'dienstplan.ics', saveto: 'dienstplan.ics',
uploadFileName: "", uploadFileName: "",
availableFormats: ["Erfurt","Stuttgart","X"], availableFormats: ["Erfurt", "Stuttgart", "X"],
stepper: null,
timepickers: null,
config: { config: {
moment: { moment: {
parse_formats: [ parse_formats: [
@ -194,11 +186,16 @@ var app = new Vue({
parse_language: 'en', parse_language: 'en',
display_language: 'de' display_language: 'de'
}, },
stepper: {
firstActive: 0,
},
toast: { toast: {
displayLength: 3000 displayLength: 3000
},
timepicker: {
twelveHour: false,
} }
}, },
workbook: null,
}, },
watch: { watch: {
shifts: { shifts: {
@ -206,6 +203,8 @@ var app = new Vue({
'use strict'; 'use strict';
shiftStorage.save(shifts); shiftStorage.save(shifts);
this.remaining = shifts.length; this.remaining = shifts.length;
this.icsFile = null;
this.blob = null;
this.makeToast("Änderungen gespeichert."); this.makeToast("Änderungen gespeichert.");
}, },
deep: true deep: true
@ -222,16 +221,16 @@ var app = new Vue({
computed: { computed: {
groupedTermine() { groupedTermine() {
return _.groupBy(this.shifts, e => e.Datum); return _.chain(this.shifts).sortBy(e => e.Datum).groupBy(e => e.Datum).value();
} }
}, },
methods: { methods: {
updateArten(value){ updateArten(value) {
this.selectedRule.Arten = value; this.selectedRule.Arten = value;
}, },
updateTitel(value){ updateTitel(value) {
this.selectedRule.Titel = value; this.selectedRule.Titel = value;
}, },
@ -241,6 +240,19 @@ var app = new Vue({
M.toast(toastOptions); M.toast(toastOptions);
}, },
openModal(elementID) {
var element = document.getElementById(elementID);
var modal = M.Modal.getInstance(element);
modal.open();
},
closeModal(elementID) {
var element = document.getElementById(elementID);
var modal = M.Modal.getInstance(element);
modal.close();
},
onFileChange: function (event) { onFileChange: function (event) {
var files = event.target.files || event.dataTransfer.files; var files = event.target.files || event.dataTransfer.files;
this.handleInputFile(files[0]); this.handleInputFile(files[0]);
@ -259,7 +271,7 @@ var app = new Vue({
}); });
var isErfurterDienstplan = workbook.SheetNames.indexOf("Dienstplan") > -1; var isErfurterDienstplan = workbook.SheetNames.indexOf("Dienstplan") > -1;
if(isErfurterDienstplan){ if (isErfurterDienstplan) {
vm.parseForErfurt(workbook.Sheets["Dienstplan"]); vm.parseForErfurt(workbook.Sheets["Dienstplan"]);
} else { } else {
this.workbook = workbook; this.workbook = workbook;
@ -269,15 +281,16 @@ var app = new Vue({
reader.readAsBinaryString(file); reader.readAsBinaryString(file);
}, },
askForDienstplanFormat: function() { askForDienstplanFormat: function () {
this.makeToast("Dienstplanformat nicht erkannt."); this.makeToast("Dienstplanformat nicht erkannt.");
$('#ask-format-modal').modal("open"); this.openModal('ask-format-modal');
}, },
parseForErfurt: function (dp) { parseForErfurt: function (dp) {
var arr = XLSX.utils.sheet_to_row_object_array(dp, { var arr = XLSX.utils.sheet_to_row_object_array(dp, {
range: 1 range: 1
}); });
this.format = "Erfurt";
var vm = this; var vm = this;
var day; var day;
@ -289,23 +302,80 @@ var app = new Vue({
day = moment(element.Datum); day = moment(element.Datum);
} }
if (element.hasOwnProperty('Dienst')) { if (element.hasOwnProperty('Dienst')) {
var time = element.Zeit ? moment(element.Zeit) : day;
var termin = { let times = [];
datum: day.clone().hour(time.hour()).minute(time.minute()), let art, beschreibung, name = "";
art: element.Dienst ? element.Dienst.trim() : "", if (element.Zeit.toString().indexOf(' + ') > 0) {
beschreibung: element.Bemerkung ? element.Bemerkung.trim(): "", // in der Zeitspalte stehen mehrere Uhrzeiten.
name: element.__EMPTY ? element.__EMPTY.trim() : "" let tempTimes = element.Zeit.toString().split(' + ');
tempTimes.forEach(function(time) {
let mom = moment(time, 'HH:mm');
times.push([mom.hour(), mom.minute()]);
})
} else {
if (element.Zeit) {
let mom = moment(element.Zeit);
times.push([mom.hour(), mom.minute()]);
} else {
times.push([0,0]);
}
} }
vm.addShift(new Shift(termin)); if (element.hasOwnProperty('Bemerkung')) {
if (element.Bemerkung.toString().search(/\d\d:\d\d\s/) >= 0) {
// prüfe ob eine Uhrzeit drinnen steht
let sonderzeit = moment(element.Bemerkung, 'HH:mm');
let termin = {
datum: day.clone().hour(sonderzeit.hour()).minute(sonderzeit.minute()),
art: '',
beschreibung: '',
name: element.Bemerkung.toString().replace(/\d\d:\d\d\s/, '').trim()
}
vm.addShift(new Shift(termin));
beschreibung = '';
} else {
beschreibung = element.Bemerkung;
}
}
if (element.hasOwnProperty('Dienst')) {
art = element.Dienst.trim();
}
if (element.hasOwnProperty('__EMPTY')) {
name = element.__EMPTY.trim();
}
times.forEach(time => {
var termin = {
datum: day.clone().hour(time[0]).minute(time[1]),
art: art,
beschreibung: beschreibung,
name: name
}
vm.addShift(new Shift(termin));
});
} }
}); });
}, },
changeTime: function (hours) {
let temp = this.shifts;
this.shifts = [];
temp.forEach(shift => {
shift.updateBeginn(hours);
this.addShift(shift);
});
},
updateBeginn: function (shift, hours) {
shift.updateBeginn(hours);
this.shifts.splice(this.shifts.indexOf(shift), 1, shift);
},
parseForStuttgart: function (dp) { parseForStuttgart: function (dp) {
var arr = XLSX.utils.sheet_to_json(dp, { var arr = XLSX.utils.sheet_to_json(dp, {
header: "A", header: "A",
blankrows: false, blankrows: false,
}); });
var vm = this; var vm = this;
var day; var day;
@ -320,16 +390,16 @@ var app = new Vue({
var termin = { var termin = {
ort: element.H ? element.H.trim() : "", ort: element.H ? element.H.trim() : "",
art: element.E ? element.E.trim() : "", art: element.E ? element.E.trim() : "",
beschreibung: element.H ? element.H.trim(): "", beschreibung: element.H ? element.H.trim() : "",
name: element.F ? element.F.trim() : "" name: element.F ? element.F.trim() : ""
} }
var time = day.clone(); var time = day.clone();
if (typeof(element.D) === "object") { if (typeof (element.D) === "object") {
time = moment(element.D); time = moment(element.D);
termin.datum = day.clone().hour(time.hour()).minute(time.minute()); termin.datum = day.clone().hour(time.hour()).minute(time.minute());
} else if(element.D.indexOf("-") > -1 ) { } else if (element.D.indexOf("-") > -1) {
var tArray = element.D.split(" - "); var tArray = element.D.split(" - ");
time = moment(tArray[0],"HH:mm"); time = moment(tArray[0], "HH:mm");
termin.datum = day.clone().hour(time.hour()).minute(time.minute()); termin.datum = day.clone().hour(time.hour()).minute(time.minute());
termin.ende = moment(tArray[1], "HH:mm").format("HH:mm"); termin.ende = moment(tArray[1], "HH:mm").format("HH:mm");
} }
@ -342,11 +412,11 @@ var app = new Vue({
parseForX: function (dp, sheetName) { parseForX: function (dp, sheetName) {
moment.locale(this.config.moment.parse_language); moment.locale(this.config.moment.parse_language);
var arr = XLSX.utils.sheet_to_json(dp, { var arr = XLSX.utils.sheet_to_json(dp, {
header: "A", header: "A",
blankrows: false, blankrows: false,
}); });
var vm = this; var vm = this;
var month = moment(sheetName.substr(sheetName.indexOf(" ")+1), "MMMM YYYY", "de"); var month = moment(sheetName.substr(sheetName.indexOf(" ") + 1), "MMMM YYYY", "de");
var day; var day;
arr.forEach(element => { arr.forEach(element => {
@ -354,10 +424,10 @@ var app = new Vue({
var time = moment().hour(0).minute(0); var time = moment().hour(0).minute(0);
try { try {
if (element.hasOwnProperty('A')) { if (element.hasOwnProperty('A')) {
day = moment(element.A, "D.","de"); day = moment(element.A, "D.", "de");
day = Number(element.A); day = Number(element.A);
} }
if ((!element.F && !element.H && !element.J) || element.I == "Spielort/ Extras" || element.J == "Spielort/Extras"){ if ((!element.F && !element.H && !element.J) || element.I == "Spielort/ Extras" || element.J == "Spielort/Extras") {
} else { } else {
@ -367,28 +437,22 @@ var app = new Vue({
time = moment(element.D); time = moment(element.D);
art = element.E.trim(); art = element.E.trim();
name = element.F ? element.F.trim() : ""; name = element.F ? element.F.trim() : "";
beschreibung = typeof(element.I) != 'undefined' ? element.I.trim() beschreibung = typeof (element.I) != 'undefined' ? element.I.trim() :
: typeof(element.J) != 'undefined' ? element.J.trim() typeof (element.J) != 'undefined' ? element.J.trim() :
: ""; "";
} } else if (element.hasOwnProperty("G") && element.hasOwnProperty("H")) {
else if (element.hasOwnProperty("G") && element.hasOwnProperty("H"))
{
// Vorstellung // Vorstellung
time = moment(element.G); time = moment(element.G);
art = "VS"; art = "VS";
name = element.H.trim(); name = element.H.trim();
beschreibung = typeof(element.I) != 'undefined' ? element.I.trim() beschreibung = typeof (element.I) != 'undefined' ? element.I.trim() :
: typeof(element.J) != 'undefined' ? element.J.trim() typeof (element.J) != 'undefined' ? element.J.trim() :
: ""; "";
} } else if (element.hasOwnProperty("I")) {
else if (element.hasOwnProperty("I"))
{
// Spielort/Extras // Spielort/Extras
beschreibung = element.I.trim(); beschreibung = element.I.trim();
name = name ? name : "Spielort/Extras"; name = name ? name : "Spielort/Extras";
} } else if (element.hasOwnProperty("J")) {
else if (element.hasOwnProperty("J"))
{
// Spielort/Extras Fallback // Spielort/Extras Fallback
beschreibung = element.J.trim(); beschreibung = element.J.trim();
name = name ? name : "Spielort/Extras"; name = name ? name : "Spielort/Extras";
@ -472,21 +536,22 @@ var app = new Vue({
selectShift: function (shift) { selectShift: function (shift) {
this.selectedShift = Shift.thaw(shift); this.selectedShift = Shift.thaw(shift);
this.keepShift = shift; this.keepShift = shift;
M.updateTextFields(); M.updateTextFields();
$('#shiftModal').modal('open'); this.openModal('shiftModal');
}, },
saveChanges: function (changedShift) { saveChanges: function (changedShift) {
this.shifts.splice(this.shifts.indexOf(this.keepShift), 1, changedShift); this.shifts.splice(this.shifts.indexOf(this.keepShift), 1, changedShift);
$('#shiftModal').modal('close'); this.closeModal('shiftModal');
this.keepShift = ''; this.keepShift = '';
this.selectedShift = ''; this.selectedShift = '';
}, },
discardChanges: function (changedShift) { discardChanges: function (changedShift) {
$('#shiftModal').modal('close'); this.closeModal('shiftModal');
this.keepShift = ''; this.keepShift = '';
this.selectedShift = ''; this.selectedShift = '';
}, },
@ -494,20 +559,28 @@ var app = new Vue({
editRule: function (rule) { editRule: function (rule) {
this.selectedRule = Rule.thaw(rule); this.selectedRule = Rule.thaw(rule);
this.selectedRuleIndex = this.rules.indexOf(rule); this.selectedRuleIndex = this.rules.indexOf(rule);
$('#ruleModal').modal('open'); this.openModal('ruleModal');
}, },
saveRule: function () { saveRule: function () {
this.rules[this.selectedRuleIndex] = this.selectedRule; this.rules[this.selectedRuleIndex] = this.selectedRule;
$('#ruleModal').modal('close'); this.closeModal('ruleModal');
this.selectedRule = new Rule(); this.selectedRule = new Rule();
}, },
discardRule: function () { discardRule: function () {
$('#ruleModal').modal('close'); this.closeModal('ruleModal');
this.selectedRule = new Rule(); this.selectedRule = new Rule();
},
applyRules: function () {
var shifts = this.shifts;
shifts.forEach(function (shift) {
Shift.setDurationFromRules(shift, this.rules);
});
this.shifts = shifts;
} }
}, },
@ -517,5 +590,12 @@ var app = new Vue({
el.focus(); el.focus();
} }
} }
},
mounted: function () {
M.AutoInit();
var els = document.querySelectorAll('.timepicker');
this.timepickers = M.Timepicker.init(els, this.config.timepicker);
var el = document.querySelector(".stepper");
this.stepper = new MStepper(el, this.config.stepper);
} }
}) })

View File

@ -12,14 +12,16 @@
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#e6b300"> <link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#e6b300">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link rel="stylesheet" href="https://unpkg.com/materialize-stepper@3.1.0/dist/css/mstepper.min.css">
<link rel="stylesheet" href="css/page.css"> <link rel="stylesheet" href="css/page.css">
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<nav class="nav-extended indigo"> <nav class="nav blue">
<div class="nav-wrapper container "> <div class="container">
<span class="brand-logo"> <span class="brand-logo">
<img src="/img/logo.svg" height="30"> <img src="/img/logo.svg" height="30">
Dienstplan Converter Dienstplan Converter
@ -28,157 +30,143 @@
<link rel="icon" type="image/png" href="/favicons/favicon-16x16.png" sizes="16x16"> <link rel="icon" type="image/png" href="/favicons/favicon-16x16.png" sizes="16x16">
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#e6b300"> <link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#e6b300">
</span> </span>
<a href="#" data-target="mobile-nav" class="sidenav-trigger"><i class="material-icons">menu</i></a>
<ul id="nav-mobile" class="right hide-on-med-and-down">
<li class=""><a class="active" href="#termine">Termine</a></li>
<li class=""><a href="#Einstellungen">Regeln</a></li>
</ul>
</ul>
<ul class="tabs tabs-transparent">
<li class="tab"><a class="active" href="#termine">Termine</a></li>
<li class="tab"><a href="#Einstellungen">Regeln</a></li>
</ul>
</div> </div>
</nav> </nav>
<ul class="sidenav" id="mobile-nav">
</ul>
<div id="body"> <div id="body">
<main class="container"> <main class="container">
<div id="termine"> <ul class="stepper">
<li class="step active">
<div class="" v-if="shifts.length > 0"> <div class="step-title waves-effect">Dienstplan-Datei auswählen</div>
<h3 class="title">Termine</h3> <div class="step-content">
<table class="highlight"> <label class="btn waves-effect waves-green" data-position="left"
<thead>
<tr>
<th>Datum</th>
<th>Dauer</th>
<th>Info</th>
<th></th>
</tr>
</thead>
<tbody>
<template v-for="g in groupedTermine">
<tr v-for="(s,i) in g">
<th v-if="i == 0" :rowspan="g.length">
{{s.FormattedDatum}}
</th>
<td>{{ s.Beginn }} - {{ s.Ende }} Uhr</td>
<td>
<h6>{{ s.VEventTitle }}</h6>
<p v-if="s.Ort!=''">
<i class="material-icons red-text">location_on</i>
{{ s.Ort }}
</p>
<blockquote v-if="s.Ort != s.Beschreibung">
{{ s.Beschreibung }}
</blockquote>
</td>
<td>
<button class="btn-flat" @click="removeShift(s)">löschen</button>
<button class="btn-flat" @click="selectShift(s)">bearbeiten</button>
</td>
</tr>
</template>
</tbody>
</table>
<div class="card-action">
<button class="btn-flat waves-effect" @click="createDownloadFile"
:disabled=" (remaining > 0) ? null : 'disabled'">
Kalenderdatei erstellen
</button>
<a class="btn-flat waves-effect" :href="icsFile" :class="[ icsFile ? '' : 'disabled']"
download="dienstplan.ics" @click="downloadFile">
Kalenderdatei herunterladen
</a>
<a class="btn-flat red-text waves-effect" @click="cleanStorage"
:disabled=" (remaining > 0) ? null : 'disabled'">
Termine löschen
</a>
</div>
</div>
<div class="card" v-else>
<div class="card-content center-align" style="height:100px;">
<h3 class="card-title">
Noch keine Termine da
</h3>
</div>
<div class="card-action">
<label class="btn-flat waves-effect waves-green" data-position="left"
data-tooltip="Dienstplan einlesen" for="fileInput"> data-tooltip="Dienstplan einlesen" for="fileInput">
Datei auswählen Datei auswählen
</label> </label>
</div> <div class="card-panel" v-if="format">Erkanntes Format: {{format}}</div>
</div> <div class="step-actions">
</div> <button class="waves-effect waves-dark btn next-step">Weiter</button>
<div class="fixed-action-btn">
<a class="btn-floating btn-large orange darken-2 waves-effect">
<i class="large material-icons">work</i>
</a>
<ul>
<li>
<label class="btn-floating green tooltipped" data-position="left"
data-tooltip="Dienstplan einlesen" for="fileInput">
<i class="material-icons">publish</i>
</label>
</li>
<li>
<button class="btn-floating blue tooltipped" data-position="left"
data-tooltip="Kalenderdatei erstellen" @click="createDownloadFile"
:disabled=" (remaining > 0) ? null : 'disabled'">
<i class="material-icons">description</i>
</button>
</li>
<li>
<a class="btn-floating yellow tooltipped" data-position="left"
data-tooltip="Kalenderdatei herunterladen" :href="icsFile"
v-bind:class="[ icsFile ? '' : 'disabled']" download="dienstplan.ics"
@click="downloadFile">
<i class="material-icons">get_app</i>
</a>
</li>
<li>
<a class="btn-floating red tooltipped" data-position="left" data-tooltip="Tabelle leeren"
@click="cleanStorage" :disabled=" (remaining > 0) ? null : 'disabled'"><i
class="material-icons">clear_all</i></a>
</li>
</ul>
</div>
<div class="" id="Einstellungen">
<div class="">
<h3 class="title">Regeln für die Dauer</h3>
<div>
<span class="chip orange">Titel der Veranstaltung</span>
<span class="chip indigo">Art des Dienstes</span>
</div>
<div class="collection">
<a class="collection-item avatar" v-for="(r,i) in rules" :key="'rule-'+i"
@click="editRule(r)">
<i class="circle">#{{ i+1 }}</i>
<span class="title">{{ r.Name}} </span>
<div class="secondary-content">
<span class="badge new" data-badge-caption="Minuten">{{ r.Dauer }}</span>
</div>
<div>
<span class="chip indigo" :class="'lighten-'+(r.Arten.length > 1 ? '3' : '1')"
v-for="a in r.Arten">
{{a.tag}}
</span>
<span class="chip orange" :class="'lighten-'+(r.Titel.length > 1 ? '3' : '1')" <a class="btn-flat red-text waves-effect" @click="cleanStorage"
v-for="t in r.Titel"> :disabled=" (remaining > 0) ? null : 'disabled'">
{{t.tag}} {{ remaining }} Termine löschen
</span> </a>
</div>
</div>
</a>
</div> </div>
</div> </li>
</div> <li class="step">
<div class="step-title waves-effect" data-step-label="Termin-Dauer-Bestimmung">
Regeln prüfen
</div>
<div class="step-content">
<div>
<span class="chip orange">Titel der Veranstaltung</span>
<span class="chip indigo">Art des Dienstes</span>
</div>
<div class="collection">
<a class="collection-item avatar" v-for="(r,i) in rules" :key="'rule-'+i"
@click="editRule(r)">
<i class="circle">#{{ i+1 }}</i>
<span class="title">{{ r.Name}} </span>
<div class="secondary-content">
<span class="badge new" data-badge-caption="Minuten">{{ r.Dauer }}</span>
</div>
<div>
<span class="chip indigo" :class="'lighten-'+(r.Arten.length > 1 ? '3' : '1')"
v-for="a in r.Arten">
{{a.tag}}
</span>
<span class="chip orange" :class="'lighten-'+(r.Titel.length > 1 ? '3' : '1')"
v-for="t in r.Titel">
{{t.tag}}
</span>
</div>
</a>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn-flat" @click.prevent="applyRules">
Regeln anwenden
</button>
<button class="waves-effect waves-dark btn next-step">Weiter</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect">Termine prüfen</div>
<div class="step-content">
<table class="highlight">
<thead>
<tr>
<th>Datum</th>
<th>Dauer</th>
<th>Info</th>
<th></th>
</tr>
</thead>
<tbody>
<template v-for="g in groupedTermine">
<tr v-for="(s,i) in g">
<th v-if="i == 0" :rowspan="g.length">
{{s.FormattedDatum}}
</th>
<td>
<button class="waves-effect waves-light btn-small btn-flat" @click.prevent="updateBeginn(s, -1)" title="1 Stunde früher">
<i class="material-icons">skip_previous</i>
</button>
{{ s.Beginn }} - {{ s.Ende }} Uhr
<button class="waves-effect waves-light btn-small btn-flat" @click.prevent="updateBeginn(s, 1)" title="1 Stunde später">
<i class="material-icons">skip_next</i>
</button>
</td>
<td>
<h6>{{ s.VEventTitle }}</h6>
<p v-if="s.Ort!=''">
<i class="material-icons red-text">location_on</i>
{{ s.Ort }}
</p>
<blockquote v-if="s.Ort != s.Beschreibung">
{{ s.Beschreibung }}
</blockquote>
</td>
<td>
<button class="btn-flat"
@click.prevent="removeShift(s)">löschen</button>
<button class="btn-flat"
@click.prevent="selectShift(s)">bearbeiten</button>
</td>
</tr>
</template>
</tbody>
</table>
<div class="step-actions">
<button class="waves-effect waves-dark btn blue" @click.prevent="changeTime(-1)">alle 1 h früher</button>
<button class="waves-effect waves-dark btn blue" @click.prevent="changeTime(1)">alle 1 h später</button>
<button class="waves-effect waves-dark btn next-step">Weiter</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect">Kalenderdatei speichern</div>
<div class="step-content">
<p>
Erst die Kalenderdatei erstellen. Danach kann sie gespeichert werden.
</p>
<div class="step-actions">
<button class="btn waves-effect" @click.prevent="createDownloadFile"
:disabled=" (remaining > 0) ? null : 'disabled'">
Kalenderdatei erstellen
</button>
<a class="btn waves-effect" :href="icsFile" :class="[ icsFile ? '' : 'disabled']"
download="dienstplan.ics" @click.stop="downloadFile">
Kalenderdatei speichern
</a>
</div>
</div>
</li>
</ul>
</main> </main>
<div id="confirmModal" class="modal active"> <div id="confirmModal" class="modal active">
<div class="modal-content"> <div class="modal-content">
@ -189,7 +177,7 @@
<button class="btn-flat" @click="false">Abbrechen</button> <button class="btn-flat" @click="false">Abbrechen</button>
</div> </div>
</div> </div>
<div id="ruleModal" class="modal bottom-sheet active"> <div id="ruleModal" class="modal modal-fixed-footer active">
<div class="modal-content"> <div class="modal-content">
<h4>Regel anpassen</h4> <h4>Regel anpassen</h4>
<div class="row"> <div class="row">
@ -218,7 +206,7 @@
@click="discardRule(selectedRule)">Verwerfen</button> @click="discardRule(selectedRule)">Verwerfen</button>
</div> </div>
</div> </div>
<div id="shiftModal" class="modal bottom-sheet"> <div id="shiftModal" class="modal modal-fixed-footer">
<div class="modal-content"> <div class="modal-content">
<h4>{{ selectedShift.VEventTitle || 'kein Titel'}}</h4> <h4>{{ selectedShift.VEventTitle || 'kein Titel'}}</h4>
<div class="row"> <div class="row">
@ -231,17 +219,18 @@
<label class="active" for="shift_kind">Art</label> <label class="active" for="shift_kind">Art</label>
</div> </div>
<div class="input-field col s6 "> <div class="input-field col s6 ">
<input id="shift_date" type="date" class="datepicker" v-model="selectedShift.Datum" <input id="shift_date" type="date" class="datepicker" v-model.lazy="selectedShift.Datum"
placeholder="Datum"> placeholder="Datum">
<label class="active" for="shift_date">Datum</label> <label class="active" for="shift_date">Datum</label>
</div> </div>
<div class="input-field col s6"> <div class="input-field col s6">
<input id="shift_begin" type="time" v-model="selectedShift.Beginn" placeholder="Uhrzeit" <input id="shift_begin" type="time" v-model.lazy="selectedShift.Beginn"
class="timepicker no-autoinit" :data-default="selectedShift.Beginn"> placeholder="Uhrzeit" class="timepicker no-autoinit"
:data-default="selectedShift.Beginn">
<label class="active" for="shift_begin">Anfang</label> <label class="active" for="shift_begin">Anfang</label>
</div> </div>
<div class="input-field col s6 "> <div class="input-field col s6 ">
<input id="shift_end" type="time" v-model="selectedShift.Ende" placeholder="Ende" <input id="shift_end" type="time" v-model.lazy="selectedShift.Ende" placeholder="Ende"
class="timepicker no-autoinit"> class="timepicker no-autoinit">
<label class="active" for="shift_end">Ende</label> <label class="active" for="shift_end">Ende</label>
</div> </div>
@ -251,7 +240,7 @@
<label class="active" for="shift_desc">Beschreibung</label> <label class="active" for="shift_desc">Beschreibung</label>
</div> </div>
<div class="input-field col s6 "> <div class="input-field col s6 ">
<input id="shift_location" type="text" v-model="selectedShift.Ort" placeholder="keine Ort"> <input id="shift_location" type="text" v-model="selectedShift.Ort" placeholder="kein Ort">
<label class="active" for="shift_location">Ort</label> <label class="active" for="shift_location">Ort</label>
</div> </div>
</div> </div>
@ -272,6 +261,8 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="https://unpkg.com/materialize-stepper@3.1.0/dist/js/mstepper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.1/xlsx.full.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.1/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.23.0/moment-with-locales.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.23.0/moment-with-locales.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>

View File

@ -7,10 +7,17 @@ var TIMEZONE_NAME = "Europe/Berlin";
//requires moment.js //requires moment.js
Array.prototype.asChipData = function () { Array.prototype.asChipData = function () {
return this.map((e,i) => {return {tag:e,id:i}; }) return this.map((e, i) => {
return {
tag: e,
id: i
};
})
} }
Array.prototype.fromChipData = function () { Array.prototype.fromChipData = function () {
return this.map(e => {return e.tag; }) return this.map(e => {
return e.tag;
})
} }
function Rule() { function Rule() {
@ -19,10 +26,10 @@ function Rule() {
if (arguments[0]) options = arguments[0]; if (arguments[0]) options = arguments[0];
var default_args = { var default_args = {
'arten' : [], 'arten': [],
'dauer' : 60, 'dauer': 60,
'titel': [], 'titel': [],
'name' : "Standard" 'name': "Standard"
} }
for (var index in default_args) { for (var index in default_args) {
if (typeof options[index] == "undefined") options[index] = default_args[index]; if (typeof options[index] == "undefined") options[index] = default_args[index];
@ -83,7 +90,7 @@ Rule.prototype.fits = function (art, title) {
return artMatch && nameMatch; return artMatch && nameMatch;
} }
Rule.thaw = function(json) { Rule.thaw = function (json) {
return new Rule({ return new Rule({
dauer: json._duration, dauer: json._duration,
name: json._name, name: json._name,
@ -93,16 +100,48 @@ Rule.thaw = function(json) {
} }
Rule.defaults = function () { Rule.defaults = function () {
var rules = []; var rules = [];
var input = [ var input = [{
{name: "EF #01", dauer: 60, arten: ['VS'], titel: ['Kinderkonzert']}, name: "EF #01",
{name: "EF #02", dauer: 120, arten: ['VS'], titel: ["Expeditionskonzert", "Sinfoniekonzert"]}, dauer: 60,
{name: "EF #03", dauer: 150, arten: ['Oa', 'GP'], titel: ['Expeditionskonzert']}, arten: ['VS'],
{name: "EF #04", dauer: 60, arten: ['Oa', 'GP'], titel: ['Expeditionskonzert']}, titel: ['Kinderkonzert']
{name: "EF #05", dauer: 150, arten: ['Oa', 'OSP']}, },
{name: "EF #06", dauer: 180, arten: ["VS", "BO", "OHP", "HP", "GP", "Prem", "WA"]}, {
{name: "Standard", dauer: 60} name: "EF #02",
dauer: 120,
arten: ['VS'],
titel: ["Expeditionskonzert", "Sinfoniekonzert"]
},
{
name: "EF #03",
dauer: 150,
arten: ['Oa', 'GP'],
titel: ['Expeditionskonzert']
},
{
name: "EF #04",
dauer: 60,
arten: ['Oa', 'GP'],
titel: ['Expeditionskonzert']
},
{
name: "EF #05",
dauer: 150,
arten: ['Oa', 'OSP']
},
{
name: "EF #06",
dauer: 180,
arten: ["VS", "BO", "OHP", "HP", "GP", "Prem", "WA"]
},
{
name: "Standard",
dauer: 60
}
]; ];
input.forEach(e => { rules.push(new Rule(e)); }); input.forEach(e => {
rules.push(new Rule(e));
});
return rules; return rules;
} }
@ -114,11 +153,11 @@ function Shift() {
if (arguments[0]) options = arguments[0]; if (arguments[0]) options = arguments[0];
var default_args = { var default_args = {
'art' : "", 'art': "",
'name' : "DUMMY", 'name': "DUMMY",
'datum' : moment(), 'datum': moment(),
'beschreibung' : "", 'beschreibung': "",
'ort' : "", 'ort': "",
} }
for (var index in default_args) { for (var index in default_args) {
if (typeof options[index] == "undefined") options[index] = default_args[index]; if (typeof options[index] == "undefined") options[index] = default_args[index];
@ -135,7 +174,7 @@ function Shift() {
this.Ende = options.ende; this.Ende = options.ende;
} else { } else {
if (!options.dontSetDurationFromRules) { if (!options.dontSetDurationFromRules) {
Shift.setDurationFromRules(this); Shift.setDurationFromRules(this, DURATION_RULES);
} }
} }
@ -173,7 +212,8 @@ Shift.prototype = {
this._begin = dateMoment.clone(); this._begin = dateMoment.clone();
}, },
get Ende() { get Ende() {
return this._begin.clone().add(this._duration).format(TIME_FORMAT); var ende = this._begin.clone().add(this._duration).format(TIME_FORMAT);
return ende;
}, },
set Ende(value) { set Ende(value) {
var dateMoment = moment(this.Datum + " " + value, DATE_INPUT_FORMAT + " " + TIME_FORMAT); var dateMoment = moment(this.Datum + " " + value, DATE_INPUT_FORMAT + " " + TIME_FORMAT);
@ -227,7 +267,7 @@ Shift.prototype = {
} }
} }
Shift.setDurationFromRules = function (shift) { Shift.setDurationFromRules = function (shift, rules) {
'use strict'; 'use strict';
var isAllDayEvent = shift.Beginn == "00:00"; var isAllDayEvent = shift.Beginn == "00:00";
if (isAllDayEvent) { if (isAllDayEvent) {
@ -237,8 +277,8 @@ Shift.setDurationFromRules = function (shift) {
var art = shift.Art.toLowerCase(); var art = shift.Art.toLowerCase();
var titel = shift.Name.toLowerCase(); var titel = shift.Name.toLowerCase();
var duration = 60; var duration = 60;
for (var rIndex in DURATION_RULES) { for (var rIndex in rules) {
var rule = DURATION_RULES[rIndex]; var rule = rules[rIndex];
if (rule.fits(art, titel)) { if (rule.fits(art, titel)) {
duration = rule.Dauer; duration = rule.Dauer;
@ -260,16 +300,18 @@ Shift.prototype.toVEvent = function () {
Shift.thaw = function (jsonShift) { Shift.thaw = function (jsonShift) {
moment.locale(MOMENT_LOCALE); moment.locale(MOMENT_LOCALE);
var begin = moment(jsonShift._begin); var begin = moment(jsonShift._begin);
var shift = new Shift( var shift = new Shift({
{ art: jsonShift._kind,
art: jsonShift._kind, name: jsonShift._name,
name: jsonShift._name, datum: begin,
datum: begin, beschreibung: jsonShift._description,
beschreibung: jsonShift._description, ort: jsonShift._ort,
ort: jsonShift._ort,
}); });
shift.id = jsonShift.id; shift.id = jsonShift.id;
shift.Dauer = moment.duration(jsonShift._duration); shift.Dauer = moment.duration(jsonShift._duration);
return shift; return shift;
}; };
Shift.prototype.updateBeginn = function(hour) {
this._begin = this._begin.add('hours', hour);
}