commit
ec1b43754a
@ -0,0 +1,23 @@ |
||||
# 📚 Digitales Berichtsheft |
||||
Eine WebApp zur digitalen führung des Berichtshefts. |
||||
|
||||
## Beweggründe |
||||
Ich schreibe nicht gerne von Hand und die Prüfer der IHK bevorzugen mit sicherheit auch ein gut lesbares Dokument. |
||||
Alternativen waren entweder umständlich/schwer zugänglich (Word Dokumente) oder hinter einer paywall. |
||||
|
||||
## Zustand des Projekts |
||||
Die meisten Templates und Funktionen zum anzeigen des Berichtshefts sind geschrieben. \ |
||||
Aktuell muss man die Berichtsheftdaten noch händisch in das "userdata" objekt einfügen. |
||||
|
||||
## Feature Roadmap für v1.0 |
||||
- [ ] Templates |
||||
- [x] Titelseite |
||||
- [x] Leer/Platzhalter |
||||
- [ ] Fehlzeiten |
||||
- [x] Wochenbericht |
||||
- [ ] Import & Export von Berichtsheftdaten |
||||
- [ ] Session storage |
||||
- [ ] Funktionen |
||||
- [x] Hinzufügen von Seiten |
||||
- [ ] Löschen von Seiten |
||||
- [ ] Formulare zum eintragen/modifizieren von Berichtsheftseiten |
@ -0,0 +1,193 @@ |
||||
body { |
||||
margin: 0; |
||||
padding: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
background: darkgray; |
||||
} |
||||
|
||||
hr { |
||||
border-top: 1px solid black; |
||||
} |
||||
|
||||
td { |
||||
height: 1.5em; |
||||
} |
||||
|
||||
table.week { |
||||
width: 100%; |
||||
height: 80%; |
||||
} |
||||
|
||||
table.week th { |
||||
height: 2em; |
||||
font-weight: normal; |
||||
text-align: center; |
||||
vertical-align: middle; |
||||
background-color: #f2f2f2; |
||||
} |
||||
|
||||
table.week tbody:nth-child(odd) tr:nth-child(odd) { |
||||
background-color: #f2f2f2; |
||||
} |
||||
|
||||
table.week tbody:nth-child(even) tr:nth-child(even) { |
||||
background-color: #f2f2f2; |
||||
} |
||||
|
||||
table.week thead th:nth-last-child(-n+2) { |
||||
width: 10%; |
||||
font-size: 80%; |
||||
} |
||||
|
||||
table.week td:nth-last-child(-n+2) { |
||||
text-align: center; |
||||
} |
||||
|
||||
table.week tbody:last-child tr:last-child td:first-child { |
||||
text-align: right; |
||||
} |
||||
|
||||
.vtable { |
||||
width: 1em; |
||||
} |
||||
|
||||
.vertical { |
||||
vertical-align: middle; |
||||
writing-mode: vertical-lr; |
||||
transform: rotate(180deg); |
||||
} |
||||
|
||||
.sign { |
||||
position: absolute; |
||||
bottom: 0; |
||||
width: 100%; |
||||
height: 15%; |
||||
} |
||||
|
||||
.sign table { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
.sign table, .sign table td { |
||||
border: 1px solid black; |
||||
border-collapse: collapse; |
||||
} |
||||
|
||||
.sign td { |
||||
width: 50%; |
||||
} |
||||
|
||||
.sign tr:last-child td { |
||||
height: 90%; |
||||
text-align: center; |
||||
vertical-align: bottom; |
||||
font-size: 0.7em; |
||||
} |
||||
|
||||
.page { |
||||
position: relative; |
||||
margin: auto; |
||||
margin-bottom: 0.25cm; |
||||
width: 21cm; |
||||
height: 29.7cm; |
||||
display: grid; |
||||
grid-template-rows: 2cm auto 2cm; |
||||
background: white; |
||||
} |
||||
|
||||
.page:nth-child(odd) { |
||||
grid-template-columns: 2.41cm auto 0.81cm; |
||||
} |
||||
|
||||
.page:nth-child(even) { |
||||
grid-template-columns: 0.81cm auto 2.42cm; |
||||
} |
||||
|
||||
.pg-head { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
grid-row: 1 / 2; |
||||
grid-column: 2 / 3; |
||||
background: white; |
||||
} |
||||
|
||||
.pg-head .flex { |
||||
position: absolute; |
||||
top: 1cm; |
||||
text-align: center; |
||||
} |
||||
|
||||
.pg-head img { |
||||
position: absolute; |
||||
top: .5cm; |
||||
height: .5cm; |
||||
} |
||||
|
||||
.page:nth-child(odd) .pg-head img { |
||||
right: 0; |
||||
} |
||||
|
||||
.pg-content { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
grid-row: 2 / 3; |
||||
grid-column: 2 / 3; |
||||
} |
||||
|
||||
.pg-footer { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
grid-row: 3 / 4; |
||||
grid-column: 2 / 3; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
white-space: pre; |
||||
} |
||||
|
||||
div.sticky { |
||||
position: sticky; |
||||
position: -webkit-sticky; |
||||
align-self: flex-start; |
||||
top: 0; |
||||
} |
||||
|
||||
.flex { |
||||
width: 100%; |
||||
display: flex; |
||||
white-space: pre; |
||||
} |
||||
|
||||
.eightyw { |
||||
width: 80%; |
||||
} |
||||
|
||||
.halfw { |
||||
width: 50%; |
||||
} |
||||
|
||||
.quarterw { |
||||
width: 25%; |
||||
} |
||||
|
||||
.tenw { |
||||
width: 10%; |
||||
} |
||||
|
||||
.halfh { |
||||
height: 50%; |
||||
} |
||||
|
||||
@media print { |
||||
body, .page { |
||||
margin: none; |
||||
} |
||||
.sticky, button { |
||||
display: none; |
||||
} |
||||
} |
@ -0,0 +1,140 @@ |
||||
"use strict"; |
||||
|
||||
const app = document.getElementById("appContent"); |
||||
let userdata; |
||||
load(); |
||||
const emptyPages = { |
||||
"title": {"type": "title"}, |
||||
"blank": {"type": "blank"}, |
||||
"report": { |
||||
"type": "report", |
||||
"days": [ [], [], [], [], [], ], |
||||
}, |
||||
|
||||
}; |
||||
let templates; |
||||
|
||||
// const templates = {
|
||||
// "blank": (await fetchTemplate("blank"))
|
||||
// };
|
||||
// let blank = fetchTemplate("blank").then(b=>{blank=b});
|
||||
// let blank = fetchTemplate("blank")
|
||||
async function init() { |
||||
if (typeof templates === "undefined") { |
||||
templates = { |
||||
"title": await fetchTemplate("title"), |
||||
"blank": await fetchTemplate("blank"), |
||||
"report": await fetchTemplate("report"), |
||||
}; |
||||
} |
||||
[...app.children].forEach(child => app.removeChild(child)) |
||||
|
||||
userdata.pages.forEach((e,i) => { |
||||
pageAdd(e.type); |
||||
populate(i+1); |
||||
}) |
||||
} |
||||
|
||||
function pageAdd(template) { |
||||
pageNew(template); |
||||
populate(app.children.length); |
||||
} |
||||
|
||||
function pageNew(template) { |
||||
const node = templates[template].content.cloneNode(true); |
||||
node.children[0].id = `Page-${app.children.length+1}`; |
||||
app.appendChild(node); |
||||
fixPageCount(); |
||||
userdata.pages.push(emptyPages[template]) |
||||
} |
||||
|
||||
function populate(pnum) { |
||||
if (pnum > 0 && pnum <= app.children.length <= userdata.pages.length) { |
||||
const node = app.children[pnum-1]; |
||||
const data = userdata.pages[pnum-1]; |
||||
node.querySelectorAll(".logo").forEach(i=>i.src = userdata.meta.logo); |
||||
if (node.classList.contains(data.type)) { |
||||
switch (data.type) { |
||||
case "report": |
||||
let startDate = data.start; |
||||
if (typeof startDate === "undefined") { |
||||
const dates = (pnum > 1) && app.children[pnum-2].getElementsByClassName("date") || []; |
||||
startDate = dates.length > 1 && dates[1].textContent.split(".").reverse().map(Number) || undefined |
||||
if (typeof startDate === "undefined") { |
||||
startDate = userdata.meta.start || [0,0,0]; |
||||
} else { |
||||
startDate[1] -= 1; |
||||
startDate[2] += 3; |
||||
} |
||||
} |
||||
let endDate = data.end; |
||||
if (typeof endDate === "undefined") { |
||||
endDate = [...startDate]; |
||||
endDate[2] += 4; |
||||
} |
||||
const date = node.querySelectorAll(".date"); |
||||
date[0].textContent = getDate(startDate); |
||||
date[1].textContent = getDate(endDate); |
||||
|
||||
const week = node.getElementsByClassName("week")[0].querySelectorAll(".day"); |
||||
let weekh = null; |
||||
for (let i=0; i < week.length && i < data.days.length; i++) { |
||||
let day = week[i].children; |
||||
let dayh = null; |
||||
for (let j=0; j < day.length && j < data.days[i].length; j++) { |
||||
day[j].querySelectorAll("td")[0].textContent = data.days[i][j][0]; |
||||
day[j].querySelectorAll("td")[1].textContent = data.days[i][j][1]; |
||||
dayh += data.days[i][j][1]; |
||||
} |
||||
day[day.length-1].querySelector(".subtotal").textContent = dayh; |
||||
weekh += dayh; |
||||
} node.querySelector(".total").textContent = weekh; |
||||
|
||||
case "blank": |
||||
node.querySelector(".pnum").textContent = pnum; |
||||
break; |
||||
case "title": |
||||
let tdate = node.querySelectorAll(".tdate"); |
||||
tdate[0].textContent = getDate(userdata.meta.start); |
||||
tdate[1].textContent = getDate(userdata.meta.end); |
||||
node.querySelector(".name").textContent = `${userdata.meta.lastName}, ${userdata.meta.firstName}`; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
function getDate(date) { |
||||
let d = date && new Date(...date) || new Date(); |
||||
return `${String(d.getDate()).padStart(2, "0")}.${String(d.getMonth()+1).padStart(2, "0")}.${d.getFullYear()}` |
||||
} |
||||
|
||||
function fixPageCount() { |
||||
const pc = document.getElementsByClassName("pcount"); |
||||
for (let i=0; i < pc.length; i++) { |
||||
pc[i].textContent = app.children.length; |
||||
} |
||||
} |
||||
|
||||
async function fetchTemplate(tname) { |
||||
let template = document.createElement("template"); |
||||
template.innerHTML = await ( await fetch(`assets/templates/${tname}.html`)).text(); |
||||
return template.content.getElementById(tname); |
||||
} |
||||
|
||||
function load() { |
||||
userdata = JSON.parse(localStorage.getItem("userdata")) || |
||||
{ "meta": { |
||||
"firstName": "", |
||||
"lastName": "", |
||||
"start": null, |
||||
"end": null, |
||||
"logo": "" |
||||
}, |
||||
"pages": [], |
||||
}; |
||||
} |
||||
|
||||
function save() { |
||||
localStorage.setItem("userdata", JSON.stringify(userdata)) |
||||
} |
@ -0,0 +1,10 @@ |
||||
<template id="blank"> |
||||
<div class="page blank"> |
||||
<div class="pg-footer"> |
||||
<p>Seite </p> |
||||
<p class="pnum"></p> |
||||
<p> von </p> |
||||
<p class="pcount"</p> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,197 @@ |
||||
<template id="report"> |
||||
<div class="page report"> |
||||
<div class="pg-head"> |
||||
<img src="" class="logo"/> |
||||
<!-- <hr> --> |
||||
<div class="flex"> |
||||
<p class="quarterw">Woche vom:</p> |
||||
<p class="quarterw date"></p> |
||||
<p class="quarterw">bis:</p> |
||||
<p class="quarterw date"></p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="pg-content"> |
||||
<table class="week"> |
||||
<thead> |
||||
<tr> |
||||
<th colspan="2">Unterweisungen, ausgeführte Arbeiten usw.</th> |
||||
<th>Einzel-<br>Stunden</th> |
||||
<th>Gesammt-<br>Stunden</th> |
||||
</thead> |
||||
<tbody class="day"> |
||||
<tr> |
||||
<th rowspan="5" class="vtable"><div class="vertical">Montag</div></th> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td class="subtotal"></td> |
||||
</tr> |
||||
</tbody> |
||||
|
||||
<tbody class="day"> |
||||
<tr> |
||||
<th rowspan="5" class="vtable"><div class="vertical">Dienstag</div></th> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td class="subtotal"></td> |
||||
</tr> |
||||
</tbody> |
||||
|
||||
<tbody class="day"> |
||||
<tr> |
||||
<th rowspan="5" class="vtable"><div class="vertical">Mittwoch</div></th> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td class="subtotal"></td> |
||||
</tr> |
||||
</tbody> |
||||
|
||||
<tbody class="day"> |
||||
<tr> |
||||
<th rowspan="5" class="vtable"><div class="vertical">Donnerstag</div></th> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td class="subtotal"></td> |
||||
</tr> |
||||
</tbody> |
||||
|
||||
<tbody class="day"> |
||||
<tr> |
||||
<th rowspan="5" class="vtable"><div class="vertical">Freitag</div></th> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td></td> |
||||
<td class="subtotal"></td> |
||||
</tr> |
||||
</tbody> |
||||
<tbody> |
||||
<tr style="height:1em"> |
||||
<td colspan="3">Wochenstunden</td> |
||||
<td class="total"></td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
<div class="sign"> |
||||
<table> |
||||
<tr> |
||||
<td colspan="2">Für die Richtigkeit:</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Auszubildender</td> |
||||
<td>Ausbilder</td> |
||||
</tr> |
||||
<tr> |
||||
<td><hr class="eightyw">Datun, Unterschrift</td> |
||||
<td><hr class="eightyw">Datun, Unterschrift</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="pg-footer"> |
||||
<p>Seite </p> |
||||
<p class="pnum"></p> |
||||
<p> von </p> |
||||
<p class="pcount"</p> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,22 @@ |
||||
<template id="title"> |
||||
<div class="page title"> |
||||
<div class="pg-content"> |
||||
<div class="halfh"> |
||||
<img src="" class="eightyw logo"/> |
||||
</div> |
||||
<h1>Berichtsheft</h1> |
||||
<div class="flex"> |
||||
<p>Ausbildungsbeginn: </p> |
||||
<p class="tdate"></p> |
||||
</div> |
||||
<div class="flex"> |
||||
<p>Ausbildungsende: </p> |
||||
<p class="tdate"></p> |
||||
</div> |
||||
<div class="flex"> |
||||
<p>Name: </p> |
||||
<p class="name"></p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,23 @@ |
||||
<!doctype html> |
||||
<html lang="de-de"> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<link rel="stylesheet" href="assets/css/notebook.css" /> |
||||
<title>Mein Berichtsheft</title> |
||||
</head> |
||||
<body> |
||||
<div class="sticky"> |
||||
<button onclick='pageAdd("title")'>Neue Titelseite</button> |
||||
<button onclick='pageAdd("blank")'>Neue leere Seite</button> |
||||
<button onclick='pageAdd("report")'>Neue Berichtsseite</button> |
||||
</div> |
||||
|
||||
<main id="appContent"></main> |
||||
</body> |
||||
<footer> |
||||
<script src="assets/js/notebook.js"></script> |
||||
<script>init();</script> |
||||
</footer> |
||||
</html> |
Loading…
Reference in new issue