updated handling of entries in comment
This commit is contained in:
commit
b82d9748ac
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/orchester_dienstplan.iml" filepath="$PROJECT_DIR$/.idea/orchester_dienstplan.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/orchester_dienstplan.iml
generated
Normal file
8
.idea/orchester_dienstplan.iml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
633
app.js
Normal file
633
app.js
Normal file
@ -0,0 +1,633 @@
|
|||||||
|
if (!String.prototype.includes) {
|
||||||
|
String.prototype.includes = function () {
|
||||||
|
'use strict';
|
||||||
|
return String.prototype.indexOf.apply(this, arguments) !== -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//extend FileReader
|
||||||
|
if (!FileReader.prototype.readAsBinaryString) {
|
||||||
|
FileReader.prototype.readAsBinaryString = function (fileData) {
|
||||||
|
var binary = "";
|
||||||
|
var pt = this;
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
var bytes = new Uint8Array(reader.result);
|
||||||
|
var length = bytes.byteLength;
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
//pt.result - readonly so assign binary
|
||||||
|
pt.content = binary;
|
||||||
|
$(pt).trigger('onload');
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//localStorage persistence
|
||||||
|
var SHIFT_STORAGE_KEY = "dienstplan_chrosey";
|
||||||
|
var RULE_STORAGE_KEY = "regeln_chrosey";
|
||||||
|
var shiftStorage = {
|
||||||
|
fetch: function () {
|
||||||
|
'use strict';
|
||||||
|
var parsed = JSON.parse(localStorage.getItem(SHIFT_STORAGE_KEY) || '[]'),
|
||||||
|
shifts = [];
|
||||||
|
parsed.forEach(function (el, index) {
|
||||||
|
var shift = Shift.thaw(el);
|
||||||
|
shift.id = index;
|
||||||
|
shifts.push(shift);
|
||||||
|
});
|
||||||
|
shiftStorage.uid = shifts.length;
|
||||||
|
return shifts;
|
||||||
|
},
|
||||||
|
save : function (shifts) {
|
||||||
|
'use strict';
|
||||||
|
var json = JSON.stringify(shifts)
|
||||||
|
localStorage.setItem(SHIFT_STORAGE_KEY, json);
|
||||||
|
},
|
||||||
|
count: function () {
|
||||||
|
return JSON.parse(localStorage.getItem(SHIFT_STORAGE_KEY) || '[]').length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ruleStorage = {
|
||||||
|
fetch: function () {
|
||||||
|
'use strict';
|
||||||
|
var parsed = JSON.parse(localStorage.getItem(RULE_STORAGE_KEY)) | [];
|
||||||
|
var rules = parsed.length > 0 ?
|
||||||
|
parsed.map((e, i) => {
|
||||||
|
var r = Rule.thaw(e);
|
||||||
|
r.id = i;
|
||||||
|
return r;
|
||||||
|
}) :
|
||||||
|
Rule.defaults();
|
||||||
|
ruleStorage.uid = rules.length;
|
||||||
|
return rules;
|
||||||
|
},
|
||||||
|
save : function (rules) {
|
||||||
|
'use strict';
|
||||||
|
var json = JSON.stringify(rules);
|
||||||
|
localStorage.setItem(RULE_STORAGE_KEY, json);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Vue.component('ask-format-modal', {
|
||||||
|
template: `
|
||||||
|
<div id="ask-format-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Dienstplanformat</h4>
|
||||||
|
<p>Welches Format soll eingelesen werden?</p>
|
||||||
|
<label v-for="option in options" >
|
||||||
|
<input name="dpFormat" type="radio" :value="option" v-model="picked"/>
|
||||||
|
<span>{{ option }}</span>
|
||||||
|
</label>
|
||||||
|
<p>Ausgewählt: {{ picked }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#!" class="modal-close waves-effect waves-green btn-flat" @click.stop="submitPick">Bastätigen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
picked: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props : ["options"],
|
||||||
|
methods: {
|
||||||
|
submitPick() {
|
||||||
|
this.$emit('picked-format', this.picked);
|
||||||
|
$('#ask-format-modal').modal('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('chip-input', {
|
||||||
|
template: `
|
||||||
|
<div class="chips no-autoinit" :id="name"></div>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
instance: null,
|
||||||
|
chips : []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
chipsData() {
|
||||||
|
return this.instance.chipsData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
initData: {
|
||||||
|
deep: true,
|
||||||
|
handler(n, o) {
|
||||||
|
if (n !== o) {
|
||||||
|
this.initialize();
|
||||||
|
this.$emit('init');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
name : String,
|
||||||
|
initData: Array
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
initialize() {
|
||||||
|
this.chips = this.initData.map(e => e);
|
||||||
|
var el = $('#' + this.name)[0];
|
||||||
|
this.instance = M.Chips.init(el, {
|
||||||
|
data : this.chips,
|
||||||
|
onChipAdd : () => {
|
||||||
|
this.$emit("change", this.chipsData);
|
||||||
|
},
|
||||||
|
onChipDelete: () => {
|
||||||
|
this.$emit("change", this.chipsData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = new Vue({
|
||||||
|
el : '#app',
|
||||||
|
data : {
|
||||||
|
shifts : shiftStorage.fetch(),
|
||||||
|
rules : ruleStorage.fetch(),
|
||||||
|
icsFile : null,
|
||||||
|
blob : null,
|
||||||
|
dp_sheet : '',
|
||||||
|
deletedShift : '',
|
||||||
|
format : '',
|
||||||
|
remaining : shiftStorage.count(),
|
||||||
|
selectedShift : new Shift({}),
|
||||||
|
selectedShiftIndex: -1,
|
||||||
|
selectedRule : new Rule({}),
|
||||||
|
selectedRuleIndex : -1,
|
||||||
|
saveto : 'dienstplan.ics',
|
||||||
|
uploadFileName : "",
|
||||||
|
availableFormats : ["Erfurt", "Stuttgart", "X"],
|
||||||
|
stepper : null,
|
||||||
|
timepickers : null,
|
||||||
|
|
||||||
|
config: {
|
||||||
|
moment : {
|
||||||
|
parse_formats : [
|
||||||
|
"ddd, DD/ MMM. 'YY HH:mm",
|
||||||
|
"ddd, DD/ MMM. YYYY HH:mm"
|
||||||
|
],
|
||||||
|
parse_language : 'en',
|
||||||
|
display_language: 'de'
|
||||||
|
},
|
||||||
|
stepper : {
|
||||||
|
firstActive: 0,
|
||||||
|
},
|
||||||
|
toast : {
|
||||||
|
displayLength: 3000
|
||||||
|
},
|
||||||
|
timepicker: {
|
||||||
|
twelveHour: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
shifts: {
|
||||||
|
handler: function (shifts) {
|
||||||
|
'use strict';
|
||||||
|
shiftStorage.save(shifts);
|
||||||
|
this.remaining = shifts.length;
|
||||||
|
this.icsFile = null;
|
||||||
|
this.blob = null;
|
||||||
|
this.makeToast("Änderungen gespeichert.");
|
||||||
|
},
|
||||||
|
deep : true
|
||||||
|
},
|
||||||
|
rules : {
|
||||||
|
handler: function (rules) {
|
||||||
|
'use strict';
|
||||||
|
ruleStorage.save(rules);
|
||||||
|
this.makeToast("Änderungen gespeichert.");
|
||||||
|
},
|
||||||
|
deep : true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
groupedTermine() {
|
||||||
|
return _.chain(this.shifts).sortBy(e => e.Datum).groupBy(e => e.Datum).value();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods : {
|
||||||
|
updateArten(value) {
|
||||||
|
this.selectedRule.Arten = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTitel(value) {
|
||||||
|
this.selectedRule.Titel = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
makeToast(message) {
|
||||||
|
var toastOptions = this.config.toast;
|
||||||
|
toastOptions.html = message;
|
||||||
|
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) {
|
||||||
|
var files = event.target.files || event.dataTransfer.files;
|
||||||
|
this.handleInputFile(files[0]);
|
||||||
|
this.uploadFileName = files[0].name + " [" + Math.round(files[0].size / 1024) + " kB]";
|
||||||
|
this.makeToast(this.uploadFileName + " ausgewählt");
|
||||||
|
},
|
||||||
|
|
||||||
|
handleInputFile: function (file) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
var vm = this;
|
||||||
|
reader.onload = (e) => {
|
||||||
|
var data = !e ? reader.content : e.target.result;
|
||||||
|
var workbook = XLSX.read(data, {
|
||||||
|
type : 'binary',
|
||||||
|
cellDates: true,
|
||||||
|
});
|
||||||
|
var isErfurterDienstplan = workbook.SheetNames.indexOf("Dienstplan") > -1;
|
||||||
|
|
||||||
|
if (isErfurterDienstplan) {
|
||||||
|
vm.parseForErfurt(workbook.Sheets["Dienstplan"]);
|
||||||
|
} else {
|
||||||
|
this.workbook = workbook;
|
||||||
|
this.askForDienstplanFormat();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsBinaryString(file);
|
||||||
|
},
|
||||||
|
|
||||||
|
askForDienstplanFormat: function () {
|
||||||
|
this.makeToast("Dienstplanformat nicht erkannt.");
|
||||||
|
this.openModal('ask-format-modal');
|
||||||
|
},
|
||||||
|
|
||||||
|
parseForErfurt: function (dp) {
|
||||||
|
var arr = XLSX.utils.sheet_to_row_object_array(dp, {
|
||||||
|
range: 1
|
||||||
|
});
|
||||||
|
this.format = "Erfurt";
|
||||||
|
var vm = this;
|
||||||
|
var day;
|
||||||
|
|
||||||
|
this.makeToast("Erfurter Dienstplan erkannt.");
|
||||||
|
|
||||||
|
arr.forEach(element => {
|
||||||
|
moment.locale(vm.config.moment.parse_language);
|
||||||
|
if (element.hasOwnProperty('Datum')) {
|
||||||
|
day = moment(element.Datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 name = element.Bemerkung.toString().replace(/\d\d:\d\d\s/, '').trim();
|
||||||
|
let splittedName = name.split(' ');
|
||||||
|
|
||||||
|
let terminArt = '';
|
||||||
|
splittedName.forEach(function (item) {
|
||||||
|
vm.rules.forEach(function (rule) {
|
||||||
|
rule.Arten.forEach(function (art) {
|
||||||
|
if (art.tag == item) {
|
||||||
|
terminArt = art.tag;
|
||||||
|
name.replace(art.tag, '');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let termin = {
|
||||||
|
datum : day.clone().hour(sonderzeit.hour()).minute(sonderzeit.minute()),
|
||||||
|
art : terminArt,
|
||||||
|
beschreibung: '',
|
||||||
|
name : name.trim()
|
||||||
|
}
|
||||||
|
vm.addShift(new Shift(termin));
|
||||||
|
beschreibung = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.hasOwnProperty('Dienst')) {
|
||||||
|
|
||||||
|
let times = [];
|
||||||
|
let art, beschreibung, name = "";
|
||||||
|
if (element.Zeit.toString().indexOf(' + ') > 0) {
|
||||||
|
// in der Zeitspalte stehen mehrere Uhrzeiten.
|
||||||
|
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.toString().indexOf(' - ') > 0) {
|
||||||
|
// in der Zeitspalte stehen mehrere Uhrzeiten als Zeitspanne
|
||||||
|
let tempTimes = element.Zeit.toString().split(' - ');
|
||||||
|
|
||||||
|
let mom = moment(tempTimes[0], 'HH:mm');
|
||||||
|
let momEnd = moment(tempTimes[1], 'HH:mm');
|
||||||
|
times.push([mom.hour(), mom.minute(), momEnd.hour(), momEnd.minute()]);
|
||||||
|
} else {
|
||||||
|
if (element.Zeit) {
|
||||||
|
let mom = moment(element.Zeit);
|
||||||
|
times.push([mom.hour(), mom.minute()]);
|
||||||
|
} else {
|
||||||
|
times.push([0, 0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
art = element.Dienst.trim();
|
||||||
|
|
||||||
|
if (element.hasOwnProperty('__EMPTY')) {
|
||||||
|
name = element.__EMPTY.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.hasOwnProperty('Bemerkung')
|
||||||
|
&& element.Bemerkung.toString().search(/\d\d:\d\d\s/) == -1) {
|
||||||
|
beschreibung = element.Bemerkung;
|
||||||
|
}
|
||||||
|
|
||||||
|
times.forEach(time => {
|
||||||
|
var termin = {
|
||||||
|
datum : day.clone().hour(time[0]).minute(time[1]),
|
||||||
|
art : art,
|
||||||
|
beschreibung: beschreibung,
|
||||||
|
name : name
|
||||||
|
}
|
||||||
|
if (time.length === 4) {
|
||||||
|
// Wenn die Zeit mehr Werte hat, dann behandle die nächsten 2 als ende
|
||||||
|
termin.end = day.clone().hour(time[2]).minute(time[3]);
|
||||||
|
termin.dontSetDurationFromRules = true;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
var arr = XLSX.utils.sheet_to_json(dp, {
|
||||||
|
header : "A",
|
||||||
|
blankrows: false,
|
||||||
|
});
|
||||||
|
var vm = this;
|
||||||
|
var day;
|
||||||
|
|
||||||
|
this.makeToast("Stuttgarter Dienstplan erkannt.");
|
||||||
|
|
||||||
|
arr.forEach(element => {
|
||||||
|
moment.locale(vm.config.moment.parse_language);
|
||||||
|
if (element.hasOwnProperty('C')) {
|
||||||
|
day = moment(element.C);
|
||||||
|
}
|
||||||
|
if (element.hasOwnProperty('D') && moment(element.D, "HH:mm").isValid()) {
|
||||||
|
var termin = {
|
||||||
|
ort : element.H ? element.H.trim() : "",
|
||||||
|
art : element.E ? element.E.trim() : "",
|
||||||
|
beschreibung: element.H ? element.H.trim() : "",
|
||||||
|
name : element.F ? element.F.trim() : ""
|
||||||
|
}
|
||||||
|
var time = day.clone();
|
||||||
|
if (typeof (element.D) === "object") {
|
||||||
|
time = moment(element.D);
|
||||||
|
termin.datum = day.clone().hour(time.hour()).minute(time.minute());
|
||||||
|
} else if (element.D.indexOf("-") > -1) {
|
||||||
|
var tArray = element.D.split(" - ");
|
||||||
|
time = moment(tArray[0], "HH:mm");
|
||||||
|
termin.datum = day.clone().hour(time.hour()).minute(time.minute());
|
||||||
|
termin.ende = moment(tArray[1], "HH:mm").format("HH:mm");
|
||||||
|
}
|
||||||
|
vm.addShift(new Shift(termin));
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
parseForX: function (dp, sheetName) {
|
||||||
|
moment.locale(this.config.moment.parse_language);
|
||||||
|
var arr = XLSX.utils.sheet_to_json(dp, {
|
||||||
|
header : "A",
|
||||||
|
blankrows: false,
|
||||||
|
});
|
||||||
|
var vm = this;
|
||||||
|
var month = moment(sheetName.substr(sheetName.indexOf(" ") + 1), "MMMM YYYY", "de");
|
||||||
|
var day;
|
||||||
|
|
||||||
|
arr.forEach(element => {
|
||||||
|
var art, name, beschreibung = null;
|
||||||
|
var time = moment().hour(0).minute(0);
|
||||||
|
try {
|
||||||
|
if (element.hasOwnProperty('A')) {
|
||||||
|
day = moment(element.A, "D.", "de");
|
||||||
|
day = Number(element.A);
|
||||||
|
}
|
||||||
|
if ((!element.F && !element.H && !element.J) || element.I == "Spielort/ Extras" || element.J == "Spielort/Extras") {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
if (element.hasOwnProperty("D") && element.hasOwnProperty("E")) {
|
||||||
|
// Probe
|
||||||
|
time = moment(element.D);
|
||||||
|
art = element.E.trim();
|
||||||
|
name = element.F ? element.F.trim() : "";
|
||||||
|
beschreibung = typeof (element.I) != 'undefined' ? element.I.trim() :
|
||||||
|
typeof (element.J) != 'undefined' ? element.J.trim() :
|
||||||
|
"";
|
||||||
|
} else if (element.hasOwnProperty("G") && element.hasOwnProperty("H")) {
|
||||||
|
// Vorstellung
|
||||||
|
time = moment(element.G);
|
||||||
|
art = "VS";
|
||||||
|
name = element.H.trim();
|
||||||
|
beschreibung = typeof (element.I) != 'undefined' ? element.I.trim() :
|
||||||
|
typeof (element.J) != 'undefined' ? element.J.trim() :
|
||||||
|
"";
|
||||||
|
} else if (element.hasOwnProperty("I")) {
|
||||||
|
// Spielort/Extras
|
||||||
|
beschreibung = element.I.trim();
|
||||||
|
name = name ? name : "Spielort/Extras";
|
||||||
|
} else if (element.hasOwnProperty("J")) {
|
||||||
|
// Spielort/Extras Fallback
|
||||||
|
beschreibung = element.J.trim();
|
||||||
|
name = name ? name : "Spielort/Extras";
|
||||||
|
}
|
||||||
|
|
||||||
|
var datumStr = day + '.' + month.clone().format("MM.YY") + time.format(" HH:mm");
|
||||||
|
var termin = {
|
||||||
|
ort : "",
|
||||||
|
art : art,
|
||||||
|
beschreibung: beschreibung,
|
||||||
|
name : name,
|
||||||
|
datum : moment(datumStr, "D.MM.YY HH:mm")
|
||||||
|
}
|
||||||
|
vm.addShift(new Shift(termin));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fehler beim Parsen", element, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
parsePickedFormat: function (format) {
|
||||||
|
this.makeToast(`Versuche ${format} zu lesen.`);
|
||||||
|
switch (format) {
|
||||||
|
case "Erfurt":
|
||||||
|
this.parseForErfurt(this.workbook.Sheets["Dienstplan"]);
|
||||||
|
break;
|
||||||
|
case "Stuttgart":
|
||||||
|
var sheetName = this.workbook.SheetNames[0];
|
||||||
|
this.parseForStuttgart(this.workbook.Sheets[sheetName]);
|
||||||
|
break;
|
||||||
|
case "X":
|
||||||
|
var sheetName = this.workbook.SheetNames[0];
|
||||||
|
this.parseForX(this.workbook.Sheets[sheetName], sheetName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.workbook = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
addShift: function (shift) {
|
||||||
|
this.shifts.push(shift);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeShift: function (shift) {
|
||||||
|
this.shifts.splice(this.shifts.indexOf(shift), 1);
|
||||||
|
this.makeToast(shift.VEventTitle + " gelöscht");
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanStorage: function () {
|
||||||
|
this.shifts = [];
|
||||||
|
this.makeToast("Alle Einträge gelöscht");
|
||||||
|
},
|
||||||
|
|
||||||
|
createDownloadFile: function () {
|
||||||
|
var vCal = new VCalendar("Dienstplan Kalender");
|
||||||
|
this.shifts.forEach(function (shift) {
|
||||||
|
vCal.add(shift.toVEvent());
|
||||||
|
});
|
||||||
|
var calString = vCal.toString();
|
||||||
|
this.blob = new Blob([calString], {
|
||||||
|
type: 'text/plain'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.icsFile !== null) {
|
||||||
|
window.URL.revokeObjectURL(this.icsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.icsFile = window.URL.createObjectURL(this.blob);
|
||||||
|
this.makeToast(this.saveto + " erstellt.");
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadFile: function () {
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
|
window.navigator.msSaveOrOpenBlob(this.blob, this.saveto);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectShift: function (shift) {
|
||||||
|
this.selectedShift = Shift.thaw(shift);
|
||||||
|
|
||||||
|
this.keepShift = shift;
|
||||||
|
M.updateTextFields();
|
||||||
|
this.openModal('shiftModal');
|
||||||
|
},
|
||||||
|
|
||||||
|
saveChanges: function (changedShift) {
|
||||||
|
this.shifts.splice(this.shifts.indexOf(this.keepShift), 1, changedShift);
|
||||||
|
|
||||||
|
this.closeModal('shiftModal');
|
||||||
|
this.keepShift = '';
|
||||||
|
this.selectedShift = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
discardChanges: function (changedShift) {
|
||||||
|
this.closeModal('shiftModal');
|
||||||
|
this.keepShift = '';
|
||||||
|
this.selectedShift = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
editRule: function (rule) {
|
||||||
|
this.selectedRule = Rule.thaw(rule);
|
||||||
|
this.selectedRuleIndex = this.rules.indexOf(rule);
|
||||||
|
this.openModal('ruleModal');
|
||||||
|
},
|
||||||
|
|
||||||
|
saveRule: function () {
|
||||||
|
this.rules[this.selectedRuleIndex] = this.selectedRule;
|
||||||
|
|
||||||
|
this.closeModal('ruleModal');
|
||||||
|
this.selectedRule = new Rule();
|
||||||
|
},
|
||||||
|
|
||||||
|
discardRule: function () {
|
||||||
|
this.closeModal('ruleModal');
|
||||||
|
|
||||||
|
this.selectedRule = new Rule();
|
||||||
|
},
|
||||||
|
|
||||||
|
applyRules: function () {
|
||||||
|
var shifts = this.shifts;
|
||||||
|
shifts.forEach(function (shift) {
|
||||||
|
Shift.setDurationFromRules(shift, this.rules);
|
||||||
|
});
|
||||||
|
this.shifts = shifts;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
'edit-focus': function (el, value) {
|
||||||
|
if (value) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
})
|
||||||
44
css/page.css
Normal file
44
css/page.css
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
.custom-file-control:lang(de)::after {
|
||||||
|
content: "Datei wählen...";
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-file-control:lang(de)::before {
|
||||||
|
content: "Durchsuchen";
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop {
|
||||||
|
padding: 3em;
|
||||||
|
outline: 2px dashed black;
|
||||||
|
outline-offset: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-me {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#body {
|
||||||
|
display: flex;
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
flex-direction: column;
|
||||||
|
padding-top: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteMe {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
max-height: 70%;
|
||||||
|
}
|
||||||
274
index.html
Normal file
274
index.html
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Dienstplan Converter</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" href="/favicons/favicon-32x32.png" sizes="32x32">
|
||||||
|
<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="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://unpkg.com/materialize-stepper@3.1.0/dist/css/mstepper.min.css">
|
||||||
|
<link rel="stylesheet" href="css/page.css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<nav class="nav blue">
|
||||||
|
<div class="container">
|
||||||
|
<span class="brand-logo">
|
||||||
|
<img src="/img/logo.svg" height="30">
|
||||||
|
Dienstplan Converter
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" href="/favicons/favicon-32x32.png" sizes="32x32">
|
||||||
|
<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">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div id="body">
|
||||||
|
<main class="container">
|
||||||
|
<ul class="stepper">
|
||||||
|
<li class="step active">
|
||||||
|
<div class="step-title waves-effect">Dienstplan-Datei auswählen</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<label class="btn waves-effect waves-green" data-position="left"
|
||||||
|
data-tooltip="Dienstplan einlesen" for="fileInput">
|
||||||
|
Datei auswählen
|
||||||
|
</label>
|
||||||
|
<div class="card-panel" v-if="format">Erkanntes Format: {{format}}</div>
|
||||||
|
<div class="step-actions">
|
||||||
|
<button class="waves-effect waves-dark btn next-step">Weiter</button>
|
||||||
|
|
||||||
|
<a class="btn-flat red-text waves-effect" @click="cleanStorage"
|
||||||
|
:disabled=" (remaining > 0) ? null : 'disabled'">
|
||||||
|
{{ remaining }} Termine löschen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<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>
|
||||||
|
<div id="confirmModal" class="modal active">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Aktion bestätigen</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-flat" @click="true">Bestätigen</button>
|
||||||
|
<button class="btn-flat" @click="false">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ruleModal" class="modal modal-fixed-footer active">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Regel anpassen</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="range-field col s12 m6 l4">
|
||||||
|
<label class="active" for="rule_duration">Dauer in Minuten: {{selectedRule.Dauer}}</label>
|
||||||
|
<input id="rule_duration" type="range" v-model="selectedRule.Dauer" min="30" max="300"
|
||||||
|
step="10">
|
||||||
|
</div>
|
||||||
|
<div class="range-field col s12 m6 l8">
|
||||||
|
<label class="active" for="rule_name">Name</label>
|
||||||
|
<input id="rule_name" type="text" v-model="selectedRule.Name">
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<label class="active" for="shift_kind">Arten</label>
|
||||||
|
<chip-input name="regel_arten" :init-data="selectedRule.Arten" @change="updateArten">
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<label class="active" for="rule_titel">Titel</label>
|
||||||
|
<chip-input name="regel_titel" :init-data="selectedRule.Titel" @change="updateTitel">>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="waves-effect btn-flat waves-green" @click="saveRule(selectedRule)">Speichern</button>
|
||||||
|
<button class="waves-effect btn-flat waves-red"
|
||||||
|
@click="discardRule(selectedRule)">Verwerfen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="shiftModal" class="modal modal-fixed-footer">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>{{ selectedShift.VEventTitle || 'kein Titel'}}</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s6 ">
|
||||||
|
<input id="shift_name" type="text" v-model="selectedShift.Name" placeholder="kein Titel">
|
||||||
|
<label class="active" for="shift_name">Titel</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6 ">
|
||||||
|
<input id="shift_kind" type="text" v-model="selectedShift.Art" placeholder="keine Art">
|
||||||
|
<label class="active" for="shift_kind">Art</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6 ">
|
||||||
|
<input id="shift_date" type="date" class="datepicker" v-model.lazy="selectedShift.Datum"
|
||||||
|
placeholder="Datum">
|
||||||
|
<label class="active" for="shift_date">Datum</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input id="shift_begin" type="time" v-model.lazy="selectedShift.Beginn"
|
||||||
|
placeholder="Uhrzeit" class="timepicker no-autoinit"
|
||||||
|
:data-default="selectedShift.Beginn">
|
||||||
|
<label class="active" for="shift_begin">Anfang</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6 ">
|
||||||
|
<input id="shift_end" type="time" v-model.lazy="selectedShift.Ende" placeholder="Ende"
|
||||||
|
class="timepicker no-autoinit">
|
||||||
|
<label class="active" for="shift_end">Ende</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6 ">
|
||||||
|
<input id="shift_desc" type="text" v-model="selectedShift.Beschreibung"
|
||||||
|
placeholder="keine Beschreibung">
|
||||||
|
<label class="active" for="shift_desc">Beschreibung</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6 ">
|
||||||
|
<input id="shift_location" type="text" v-model="selectedShift.Ort" placeholder="kein Ort">
|
||||||
|
<label class="active" for="shift_location">Ort</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="waves-effect btn-flat waves-green"
|
||||||
|
@click="saveChanges(selectedShift)">Speichern</button>
|
||||||
|
<button class="waves-effect btn-flat waves-red"
|
||||||
|
@click="discardChanges(selectedShift)">Verwerfen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ask-format-modal :options="availableFormats" @picked-format="parsePickedFormat"></ask-format-modal>
|
||||||
|
</div>
|
||||||
|
<input type="file" name="fileInput" id="fileInput" @change="onFileChange"
|
||||||
|
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel"
|
||||||
|
style="display:none;">
|
||||||
|
</div>
|
||||||
|
<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/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/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="js/shift.js"></script>
|
||||||
|
<script src="js/vcal.js"></script>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
319
js/shift.js
Normal file
319
js/shift.js
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
var DATE_LOCALE_FORMAT = "D.M.YY"; //10.2.16
|
||||||
|
var DATE_INPUT_FORMAT = "YYYY-MM-DD"; //2016-02-10
|
||||||
|
var TIME_FORMAT = "HH:mm"; // 17:30
|
||||||
|
var WEEKDAY_FORMAT = "dddd"; //Monday
|
||||||
|
var MOMENT_LOCALE = "de";
|
||||||
|
var TIMEZONE_NAME = "Europe/Berlin";
|
||||||
|
//requires moment.js
|
||||||
|
|
||||||
|
Array.prototype.asChipData = function () {
|
||||||
|
return this.map((e, i) => {
|
||||||
|
return {
|
||||||
|
tag: e,
|
||||||
|
id : i
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Array.prototype.fromChipData = function () {
|
||||||
|
return this.map(e => {
|
||||||
|
return e.tag;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function Rule() {
|
||||||
|
var options = {};
|
||||||
|
|
||||||
|
if (arguments[0]) options = arguments[0];
|
||||||
|
|
||||||
|
var default_args = {
|
||||||
|
'arten': [],
|
||||||
|
'dauer': 60,
|
||||||
|
'titel': [],
|
||||||
|
'name' : "Standard"
|
||||||
|
}
|
||||||
|
for (var index in default_args) {
|
||||||
|
if (typeof options[index] == "undefined") options[index] = default_args[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._duration = options.dauer;
|
||||||
|
this._arten = options.arten;
|
||||||
|
this._titel = options.titel;
|
||||||
|
this._name = options.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rule.prototype = {
|
||||||
|
get Dauer() {
|
||||||
|
return this._duration;
|
||||||
|
},
|
||||||
|
set Dauer(value) {
|
||||||
|
this._duration = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
get Arten() {
|
||||||
|
return this._arten.asChipData();
|
||||||
|
},
|
||||||
|
set Arten(value) {
|
||||||
|
this._arten = value.fromChipData();
|
||||||
|
},
|
||||||
|
|
||||||
|
get Titel() {
|
||||||
|
return this._titel.asChipData();
|
||||||
|
},
|
||||||
|
set Titel(value) {
|
||||||
|
this._titel = value.fromChipData();
|
||||||
|
},
|
||||||
|
|
||||||
|
get Name() {
|
||||||
|
return this._name;
|
||||||
|
},
|
||||||
|
set Name(value) {
|
||||||
|
this._name = value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Rule.prototype.fits = function (art, title) {
|
||||||
|
var artMatch = false;
|
||||||
|
var nameMatch = false;
|
||||||
|
|
||||||
|
if (this._arten.length == 0) artMatch = true;
|
||||||
|
else {
|
||||||
|
this._arten.forEach(function (el, i) {
|
||||||
|
if (art.includes(el.toLowerCase())) artMatch = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._titel.length == 0) nameMatch = true;
|
||||||
|
else {
|
||||||
|
this._titel.forEach(function (el, i) {
|
||||||
|
if (name.includes(el.toLowerCase())) nameMatch = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return artMatch && nameMatch;
|
||||||
|
}
|
||||||
|
Rule.thaw = function (json) {
|
||||||
|
return new Rule({
|
||||||
|
dauer: json._duration,
|
||||||
|
name : json._name,
|
||||||
|
arten: json._arten,
|
||||||
|
titel: json._titel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Rule.defaults = function () {
|
||||||
|
var rules = [];
|
||||||
|
var input = [{
|
||||||
|
name : "EF #01",
|
||||||
|
dauer: 60,
|
||||||
|
arten: ['VS'],
|
||||||
|
titel: ['Kinderkonzert']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
var DURATION_RULES = Rule.defaults();
|
||||||
|
|
||||||
|
function Shift() {
|
||||||
|
var options = {};
|
||||||
|
|
||||||
|
if (arguments[0]) options = arguments[0];
|
||||||
|
|
||||||
|
var default_args = {
|
||||||
|
'art' : "",
|
||||||
|
'name' : "DUMMY",
|
||||||
|
'datum' : moment(),
|
||||||
|
'end' : moment(),
|
||||||
|
'beschreibung': "",
|
||||||
|
'ort' : "",
|
||||||
|
}
|
||||||
|
for (var index in default_args) {
|
||||||
|
if (typeof options[index] == "undefined") options[index] = default_args[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Datum = options.datum.format(DATE_INPUT_FORMAT);
|
||||||
|
this.Beginn = options.datum.format(TIME_FORMAT);
|
||||||
|
this.Ende = options.end.format(TIME_FORMAT);
|
||||||
|
this.Art = options.art;
|
||||||
|
this.Beschreibung = options.beschreibung;
|
||||||
|
this.Name = options.name;
|
||||||
|
this.Ort = options.ort;
|
||||||
|
|
||||||
|
if (typeof options.ende != "undefined") {
|
||||||
|
this.Ende = options.ende;
|
||||||
|
} else {
|
||||||
|
if (!options.dontSetDurationFromRules) {
|
||||||
|
Shift.setDurationFromRules(this, DURATION_RULES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Shift.prototype = {
|
||||||
|
get Wochentag() {
|
||||||
|
return this._date.clone().locale(MOMENT_LOCALE).format(WEEKDAY_FORMAT);
|
||||||
|
},
|
||||||
|
set Wochentag(value) {
|
||||||
|
throw "kann nicht gesetzt werden.";
|
||||||
|
},
|
||||||
|
|
||||||
|
get FormattedDatum() {
|
||||||
|
return this._date.clone().format(DATE_LOCALE_FORMAT);
|
||||||
|
},
|
||||||
|
set FormattedDatum(value) {
|
||||||
|
var dateMoment = moment(value, DATE_LOCALE_FORMAT);
|
||||||
|
this._date = dateMoment;
|
||||||
|
},
|
||||||
|
|
||||||
|
get Datum() {
|
||||||
|
return this._date.clone().format(DATE_INPUT_FORMAT);
|
||||||
|
},
|
||||||
|
set Datum(value) {
|
||||||
|
var dateMoment = moment(value).startOf('day');
|
||||||
|
this._date = dateMoment.clone();
|
||||||
|
},
|
||||||
|
|
||||||
|
get Beginn() {
|
||||||
|
return this._begin.clone().format(TIME_FORMAT);
|
||||||
|
},
|
||||||
|
set Beginn(value) {
|
||||||
|
var dateMoment = moment(this.Datum + " " + value, DATE_INPUT_FORMAT + " " + TIME_FORMAT);
|
||||||
|
this._begin = dateMoment.clone();
|
||||||
|
},
|
||||||
|
get Ende() {
|
||||||
|
var ende = this._begin.clone().add(this._duration).format(TIME_FORMAT);
|
||||||
|
return ende;
|
||||||
|
},
|
||||||
|
set Ende(value) {
|
||||||
|
var dateMoment = moment(this.Datum + " " + value, DATE_INPUT_FORMAT + " " + TIME_FORMAT);
|
||||||
|
if (this._begin > dateMoment) {
|
||||||
|
dateMoment.add(1, 'd');
|
||||||
|
}
|
||||||
|
this._duration = moment.duration(dateMoment.diff(this._begin));
|
||||||
|
},
|
||||||
|
|
||||||
|
get Dauer() {
|
||||||
|
return this._duration;
|
||||||
|
},
|
||||||
|
set Dauer(duration) {
|
||||||
|
this._duration = duration;
|
||||||
|
},
|
||||||
|
|
||||||
|
get Name() {
|
||||||
|
return this._name;
|
||||||
|
},
|
||||||
|
set Name(value) {
|
||||||
|
this._name = value ? value.trim() : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
get Ort() {
|
||||||
|
return this._ort;
|
||||||
|
},
|
||||||
|
set Ort(value) {
|
||||||
|
this._ort = value ? value.trim() : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
get VEventTitle() {
|
||||||
|
return this.Name + " " + this.Art;
|
||||||
|
},
|
||||||
|
set VEventTitle(value) {
|
||||||
|
throw "kann nicht gesetzt werden.";
|
||||||
|
},
|
||||||
|
|
||||||
|
get Art() {
|
||||||
|
return this._kind;
|
||||||
|
},
|
||||||
|
set Art(value) {
|
||||||
|
|
||||||
|
this._kind = value ? value.trim() : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
get Beschreibung() {
|
||||||
|
return this._description;
|
||||||
|
},
|
||||||
|
set Beschreibung(value) {
|
||||||
|
this._description = value ? value.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Shift.setDurationFromRules = function (shift, rules) {
|
||||||
|
'use strict';
|
||||||
|
var isAllDayEvent = shift.Beginn == "00:00";
|
||||||
|
if (isAllDayEvent) {
|
||||||
|
shift.Dauer = moment.duration(24, 'h').locale(MOMENT_LOCALE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var art = shift.Art.toLowerCase();
|
||||||
|
var titel = shift.Name.toLowerCase();
|
||||||
|
var duration = 60;
|
||||||
|
for (var rIndex in rules) {
|
||||||
|
var rule = rules[rIndex];
|
||||||
|
|
||||||
|
if (rule.fits(art, titel)) {
|
||||||
|
duration = rule.Dauer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shift.Dauer = moment.duration(duration, 'm').locale(MOMENT_LOCALE);
|
||||||
|
}
|
||||||
|
Shift.prototype.toVEvent = function () {
|
||||||
|
var end = this._begin.clone().add(this._duration);
|
||||||
|
return new VEvent({
|
||||||
|
startMoment: this._begin,
|
||||||
|
endMoment : end,
|
||||||
|
title : this.VEventTitle,
|
||||||
|
description: this.Beschreibung,
|
||||||
|
location : this.Ort
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Shift.thaw = function (jsonShift) {
|
||||||
|
moment.locale(MOMENT_LOCALE);
|
||||||
|
var begin = moment(jsonShift._begin);
|
||||||
|
var shift = new Shift({
|
||||||
|
art : jsonShift._kind,
|
||||||
|
name : jsonShift._name,
|
||||||
|
datum : begin,
|
||||||
|
beschreibung: jsonShift._description,
|
||||||
|
ort : jsonShift._ort,
|
||||||
|
|
||||||
|
});
|
||||||
|
shift.id = jsonShift.id;
|
||||||
|
shift.Dauer = moment.duration(jsonShift._duration);
|
||||||
|
return shift;
|
||||||
|
};
|
||||||
|
Shift.prototype.updateBeginn = function (hour) {
|
||||||
|
this._begin = this._begin.add('hours', hour);
|
||||||
|
}
|
||||||
154
js/vcal.js
Normal file
154
js/vcal.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
var VCAL_DATETIME_FORMAT = "YMMDD[T]HHmmss"; //20160216T130500
|
||||||
|
var UID_FORMAT = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||||
|
var NEW_LINE = "\r\n";
|
||||||
|
var productID = window.title;
|
||||||
|
|
||||||
|
function VMeta() {
|
||||||
|
this.properties = new Map();
|
||||||
|
this.children = [];
|
||||||
|
this.tag = "VMETA";
|
||||||
|
}
|
||||||
|
VMeta.formatDate = function (aMoment) {
|
||||||
|
'use strict';
|
||||||
|
return aMoment.format(VCAL_DATETIME_FORMAT);
|
||||||
|
};
|
||||||
|
VMeta.generateUID = function () {
|
||||||
|
'use strict';
|
||||||
|
return UID_FORMAT.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
VMeta.prototype.set = function (key, value) {
|
||||||
|
'use strict';
|
||||||
|
this.properties.set(key, value);
|
||||||
|
};
|
||||||
|
VMeta.prototype.get = function (key) {
|
||||||
|
'use strict';
|
||||||
|
this.properties.get(key);
|
||||||
|
};
|
||||||
|
VMeta.prototype.add = function (child) {
|
||||||
|
'use strict';
|
||||||
|
this.children.push(child);
|
||||||
|
};
|
||||||
|
VMeta.prototype.mapToString = function () {
|
||||||
|
'use strict';
|
||||||
|
var output = "";
|
||||||
|
this.properties.forEach(function (value, key, map) {
|
||||||
|
output += (NEW_LINE + key + ":" + value);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
VMeta.prototype.childrenToString = function () {
|
||||||
|
'use strict';
|
||||||
|
var output = "";
|
||||||
|
this.children.forEach(function (child) {
|
||||||
|
output += (NEW_LINE + child.toString());
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
VMeta.prototype.toString = function () {
|
||||||
|
'use strict';
|
||||||
|
var output = "BEGIN:" + this.tag;
|
||||||
|
output += this.mapToString();
|
||||||
|
output += this.childrenToString();
|
||||||
|
output += (NEW_LINE + "END:" + this.tag);
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
function VCalendar(calendarName) {
|
||||||
|
'use strict';
|
||||||
|
VMeta.call(this);
|
||||||
|
this.add(VTimeZone.Berlin());
|
||||||
|
this.tag = "VCALENDAR";
|
||||||
|
this.set('X-WR-CALNAME', calendarName);
|
||||||
|
this.set('VERSION', '2.0');
|
||||||
|
this.set('PRODID', productID);
|
||||||
|
this.set('METHOD', "PUBLISH");
|
||||||
|
this.set('X-WR-TIMEZONE', "Europe/Berlin");
|
||||||
|
this.set('CALSCALE', "GREGORIAN");
|
||||||
|
};
|
||||||
|
VCalendar.prototype = Object.create(VMeta.prototype);
|
||||||
|
VCalendar.prototype.constructor = VCalendar;
|
||||||
|
|
||||||
|
function VEvent() {
|
||||||
|
var evt = window.event || arguments[1] || arguments.callee.caller.arguments[0];
|
||||||
|
var target = evt.target || evt.srcElement;
|
||||||
|
|
||||||
|
var options = {};
|
||||||
|
|
||||||
|
if (arguments[0]) options = arguments[0];
|
||||||
|
|
||||||
|
var default_args = {
|
||||||
|
'tzName' : "Europe/Berlin",
|
||||||
|
'startMoment' : moment(),
|
||||||
|
'endMoment' : moment(),
|
||||||
|
'uid' : VMeta.generateUID(),
|
||||||
|
'dtStamp' : VMeta.formatDate(moment()) + "Z",
|
||||||
|
'title' : "",
|
||||||
|
'description' : "",
|
||||||
|
'location' : "",
|
||||||
|
'organizer' : "",
|
||||||
|
|
||||||
|
}
|
||||||
|
for (var index in default_args) {
|
||||||
|
if (typeof options[index] == "undefined") options[index] = default_args[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
VMeta.call(this);
|
||||||
|
this.tag = "VEVENT";
|
||||||
|
|
||||||
|
this.set('DTSTART;TZID=' + options.tzName, VMeta.formatDate(options.startMoment));
|
||||||
|
this.set('DTEND;TZID=' + options.tzName, VMeta.formatDate(options.endMoment));
|
||||||
|
this.set('DTSTAMP', options.dtStamp);
|
||||||
|
this.set('UID', options.uid);
|
||||||
|
this.set('SUMMARY', options.title);
|
||||||
|
this.set('DESCRIPTION', options.description);
|
||||||
|
this.set('LOCATION', options.location)
|
||||||
|
this.set('CLASS', "PUBLIC");
|
||||||
|
this.set('TRANSP', "OPAQUE");
|
||||||
|
this.set('STATUS', "CONFIRMED");
|
||||||
|
this.set('ORGANIZER', options.organizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
VEvent.prototype = Object.create(VMeta.prototype);
|
||||||
|
VEvent.prototype.constructor = VEvent;
|
||||||
|
|
||||||
|
function VTimeZone() {
|
||||||
|
VMeta.call(this);
|
||||||
|
this.tag = "VTIMEZONE";
|
||||||
|
}
|
||||||
|
VTimeZone.prototype = Object.create(VMeta.prototype);
|
||||||
|
VTimeZone.prototype.constructor = VTimeZone;
|
||||||
|
VTimeZone.Berlin = function () {
|
||||||
|
var tz = new VTimeZone();
|
||||||
|
tz.children = [VTimeDef.MESZ(), VTimeDef.MEZ()];
|
||||||
|
tz.set("TZID", "Europe/Berlin");
|
||||||
|
return tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
function VTimeDef(name) {
|
||||||
|
VMeta.call(this);
|
||||||
|
this.tag = name;
|
||||||
|
}
|
||||||
|
VTimeDef.prototype = Object.create(VMeta.prototype);
|
||||||
|
VTimeDef.prototype.constructor = VTimeDef;
|
||||||
|
VTimeDef.MESZ = function () {
|
||||||
|
var timedef = new VTimeDef("DAYLIGHT");
|
||||||
|
timedef.set("TZOFFSETFROM", "+0100");
|
||||||
|
timedef.set("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU");
|
||||||
|
timedef.set("DTSTART", "19819329T020000");
|
||||||
|
timedef.set("TZNAME", "MESZ");
|
||||||
|
timedef.set("TZOFFSETTO", "+0200");
|
||||||
|
return timedef;
|
||||||
|
}
|
||||||
|
VTimeDef.MEZ = function () {
|
||||||
|
var timedef = new VTimeDef("STANDARD");
|
||||||
|
timedef.set("TZOFFSETFROM", "+0200");
|
||||||
|
timedef.set("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU");
|
||||||
|
timedef.set("DTSTART", "19961027T030000");
|
||||||
|
timedef.set("TZNAME", "MEZ");
|
||||||
|
timedef.set("TZOFFSETTO", "+0100");
|
||||||
|
return timedef;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user