This commit is contained in:
Steffen Probst
2025-03-21 18:47:49 +01:00
commit 832ebf099a
19 changed files with 1249 additions and 0 deletions

26
static/config.yaml Normal file
View File

@ -0,0 +1,26 @@
phone_rules:
# Präfixe, die von Telefonnummern entfernt werden sollen
# trim_prefixes:
# - "0049"
# - "+49"
# - "49"
# - "00"
# - "0"
# Regeln zur Formatierung von Telefonnummern
#format_rules:
# "+49": "0" # Ersetzt internationale Vorwahl durch nationale
# "00": "0" # Ersetzt internationale Vorwahl durch nationale
# Standard-Präfix für Nummern ohne Ländervorwahl
#default_prefix: "+49"
# Ungültige Telefonnummer, die ignoriert werden soll
invalid_number: "+49 5331 89"
# Konfiguration für Deutschland
country:
prefix: "49" # Ländervorwahl ohne '+' oder '00'
area_codes:
- "5331" # Vorwahl für Wolfenbüttel
internal_prefix: "89" # Präfix für interne Nummern

169
static/css/main.css Normal file
View File

@ -0,0 +1,169 @@
:root {
--background-body: #f5f5f5;
--background: #e0e0e0;
--text-main: #363636;
--text-bright: #000;
--links: #0076d1;
--focus: rgba(0,150,191,0.67);
--border: #dbdbdb;
--code: #000;
--animation-duration: 0.1s;
--button-hover: #9b9b9b;
--scrollbar-thumb: #aaa;
--form-placeholder: #949494;
--form-text: #1d1d1d;
--table-header: #d0d0d0;
--table-row-odd: #e8e8e8;
--table-row-even: #f0f0f0;
--sort-arrow: #666;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
line-height: 1.4;
max-width: 80%;
margin: 0 auto;
padding: 0 10%;
color: var(--text-main);
background: var(--background-body);
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
background-color: var(--table-header);
font-weight: bold;
cursor: pointer;
position: relative;
padding-right: 20px; /* Space for sort indicator */
}
tr:nth-child(odd) {
background-color: var(--table-row-odd);
}
tr:nth-child(even) {
background-color: var(--table-row-even);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.logo {
width: 100px;
height: 100px;
}
h1 {
text-align: center;
flex-grow: 1;
margin: 0 20px;
}
#searchInput {
width: 100%;
font-size: 16px;
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
}
/* Sort indicators */
th::after {
content: '\25B2'; /* Upward triangle */
position: absolute;
right: 5px;
opacity: 0.3;
color: var(--sort-arrow);
}
th.asc::after {
content: '\25B2'; /* Upward triangle */
opacity: 1;
}
th.desc::after {
content: '\25BC'; /* Downward triangle */
opacity: 1;
}
th:hover::after {
opacity: 0.6;
}
footer {
margin-top: 20px;
padding: 10px 0;
background-color: var(--background);
font-size: 8px;
}
.footer-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
a[href^="tel:"]:before {
content: "📞 ";
}
a[href^="mailto:"]:before {
content: "📧 ";
}
/* Responsive design */
@media (max-width: 768px) {
body {
max-width: 95%;
padding: 0 2.5%;
}
.header-container {
flex-direction: column;
}
.logo {
margin-bottom: 10px;
}
h1 {
font-size: 24px;
}
th, td {
padding: 6px;
}
}
/* Accessibility improvements */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

107
static/css/print.css Normal file
View File

@ -0,0 +1,107 @@
@media print {
@page {
size: A4 landscape;
margin: 0.8cm;
@bottom-right {
content: "Seite " counter(page) " von " counter(pages);
font-size: 8pt;
margin-right: -0.5cm;
margin-bottom: -0.5cm;
}
}
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-size: 9pt;
line-height: 1.3;
background: none;
color: #000;
display: flex;
justify-content: center;
align-items: flex-start;
}
.dynamic-table-container {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 1.5cm;
}
table {
width: 100%;
max-width: 29.7cm; /* A4 landscape width minus margins */
border-collapse: collapse;
page-break-inside: auto;
}
thead {
display: table-header-group;
}
tbody {
display: table-row-group;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
th, td {
padding: 0.1cm;
border: 1px solid #000;
text-align: center;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
th {
background-color: #f0f0f0 !important;
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
a {
text-decoration: none;
color: #000;
}
/* Hide elements not needed for print */
#searchInput, .sort-icon::after, .header-container, footer, .print-date {
display: none;
}
/* Column widths */
th:nth-child(1), td:nth-child(1),
th:nth-child(2), td:nth-child(2) {
width: 15%;
}
th:nth-child(3), td:nth-child(3) {
width: 20%;
}
th:nth-child(4), td:nth-child(4) {
width: 10%;
}
th:nth-child(5), td:nth-child(5) {
width: 25%;
}
th:nth-child(6), td:nth-child(6) {
width: 15%;
}
/* Remove top margin for the first page */
.dynamic-table-container:first-child {
margin-top: 0;
}
/* Ensure content on subsequent pages starts at the top */
@page :not(:first) {
margin-top: 0.8cm;
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2018 (64-Bit Evaluation Version) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="20.4978in" height="18.0152in" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 46296.26 40689.13"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil0 {fill:black}
]]>
</style>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1761831900240">
<path class="fil0" d="M23204.91 7530.98c2944.63,-3188.84 6384.04,-4639.01 10366.38,-4077.21 4110.34,579.88 7609.97,3518.41 8854.17,7479.01 957.39,3047.58 559.96,6460.83 -722.09,9573.35 -1993.98,4840.97 -7886.31,11722.09 -18555.24,16532.85 -10668.92,-4810.76 -16561.25,-11691.88 -18555.24,-16532.85 -1282.05,-3112.52 -1679.47,-6525.77 -722.09,-9573.35 1244.19,-3960.6 4743.83,-6899.13 8854.17,-7479.01 3982.46,-561.82 7421.94,888.46 10366.64,4077.48 5.4,5.84 56.52,61.37 56.53,61.36 0.04,0.04 51.9,-56.36 56.79,-61.63zm-56.79 -4522.44c-6431.69,-5048.01 -16512.25,-3730.83 -21147.65,3855.94 -1539.08,2519.03 -2117.14,5447.75 -1981.3,8355.45 235.64,5043.59 2412.75,9452.27 5610.61,13256.78 4306.02,5122.9 10531.26,9148.59 17382.21,12152.72 9.53,4.18 88.63,38.56 136.13,59.69 41.66,-17.53 114.6,-50.41 137.01,-60.3 6815.65,-3004.07 13075.56,-7030.12 17381.33,-12152.12 3198.08,-3804.33 5374.97,-8213.2 5610.61,-13256.78 135.85,-2907.7 -442.2,-5836.43 -1981.3,-8355.45 -4635.4,-7586.77 -14715.95,-8903.95 -21147.65,-3855.94z"/>
<path class="fil0" d="M22983.64 21630.19l-2928.01 -1451.38c-1017.73,1483.99 -1758.21,2488.33 -3897.08,1902.25 -1678.91,-460.05 -2175.85,-2300.18 -2239.67,-3843.76 -87.17,-2108.39 649.94,-4543.46 3168.15,-4413.24 1609.13,83.19 2294.75,1032.23 2661.15,1885.36l3196.99 -1638.9c-1574.75,-3004.31 -5265.13,-4026.05 -8393.32,-3188.81 -3328.66,890.9 -5014.61,3952.95 -4955.5,7255.23 60.43,3375.58 1680.8,6291.51 5161.55,7052.54 1697.16,371.06 3545.13,284.81 5116.74,-503.18 1216.27,-609.83 2567.56,-1786.86 3109,-3056.12zm13802.46 0l-2928.01 -1451.38c-1017.73,1483.99 -1758.21,2488.33 -3897.08,1902.25 -1678.91,-460.05 -2175.86,-2300.18 -2239.67,-3843.76 -87.18,-2108.39 649.94,-4543.46 3168.15,-4413.24 1609.13,83.19 2294.74,1032.23 2661.15,1885.36l3196.99 -1638.9c-1574.75,-3004.31 -5265.14,-4026.05 -8393.32,-3188.81 -3328.66,890.9 -5014.61,3952.95 -4955.5,7255.23 60.42,3375.58 1680.8,6291.51 5161.55,7052.54 1697.16,371.06 3545.13,284.81 5116.74,-503.18 1216.27,-609.83 2567.56,-1786.86 3109,-3056.12z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

21
static/images/logo.svg Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2018 (64-Bit Evaluation Version) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="20.4978in" height="18.0152in" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 46296.26 40689.13"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil0 {fill:black}
]]>
</style>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1761831900240">
<path class="fil0" d="M23204.91 7530.98c2944.63,-3188.84 6384.04,-4639.01 10366.38,-4077.21 4110.34,579.88 7609.97,3518.41 8854.17,7479.01 957.39,3047.58 559.96,6460.83 -722.09,9573.35 -1993.98,4840.97 -7886.31,11722.09 -18555.24,16532.85 -10668.92,-4810.76 -16561.25,-11691.88 -18555.24,-16532.85 -1282.05,-3112.52 -1679.47,-6525.77 -722.09,-9573.35 1244.19,-3960.6 4743.83,-6899.13 8854.17,-7479.01 3982.46,-561.82 7421.94,888.46 10366.64,4077.48 5.4,5.84 56.52,61.37 56.53,61.36 0.04,0.04 51.9,-56.36 56.79,-61.63zm-56.79 -4522.44c-6431.69,-5048.01 -16512.25,-3730.83 -21147.65,3855.94 -1539.08,2519.03 -2117.14,5447.75 -1981.3,8355.45 235.64,5043.59 2412.75,9452.27 5610.61,13256.78 4306.02,5122.9 10531.26,9148.59 17382.21,12152.72 9.53,4.18 88.63,38.56 136.13,59.69 41.66,-17.53 114.6,-50.41 137.01,-60.3 6815.65,-3004.07 13075.56,-7030.12 17381.33,-12152.12 3198.08,-3804.33 5374.97,-8213.2 5610.61,-13256.78 135.85,-2907.7 -442.2,-5836.43 -1981.3,-8355.45 -4635.4,-7586.77 -14715.95,-8903.95 -21147.65,-3855.94z"/>
<path class="fil0" d="M22983.64 21630.19l-2928.01 -1451.38c-1017.73,1483.99 -1758.21,2488.33 -3897.08,1902.25 -1678.91,-460.05 -2175.85,-2300.18 -2239.67,-3843.76 -87.17,-2108.39 649.94,-4543.46 3168.15,-4413.24 1609.13,83.19 2294.75,1032.23 2661.15,1885.36l3196.99 -1638.9c-1574.75,-3004.31 -5265.13,-4026.05 -8393.32,-3188.81 -3328.66,890.9 -5014.61,3952.95 -4955.5,7255.23 60.43,3375.58 1680.8,6291.51 5161.55,7052.54 1697.16,371.06 3545.13,284.81 5116.74,-503.18 1216.27,-609.83 2567.56,-1786.86 3109,-3056.12zm13802.46 0l-2928.01 -1451.38c-1017.73,1483.99 -1758.21,2488.33 -3897.08,1902.25 -1678.91,-460.05 -2175.86,-2300.18 -2239.67,-3843.76 -87.18,-2108.39 649.94,-4543.46 3168.15,-4413.24 1609.13,83.19 2294.74,1032.23 2661.15,1885.36l3196.99 -1638.9c-1574.75,-3004.31 -5265.14,-4026.05 -8393.32,-3188.81 -3328.66,890.9 -5014.61,3952.95 -4955.5,7255.23 60.42,3375.58 1680.8,6291.51 5161.55,7052.54 1697.16,371.06 3545.13,284.81 5116.74,-503.18 1216.27,-609.83 2567.56,-1786.86 3109,-3056.12z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

67
static/index.html Normal file
View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="/static/css/print.css">
<title>&#9742; Telefonbuch &#9742;</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png">
<script src="/static/js/phonebook.js" defer></script>
</head>
<body>
<div class="header-container">
<img src="/static/images/logo.svg" alt="MKN Raute" class="logo">
<h1>&#9742; Telefonbuch &#9742;</h1>
<img src="/static/images/logo.svg" alt="MKN Raute" class="logo">
</div>
<input type="text" id="searchInput" onkeyup="searchTable()" placeholder="Suche nach Namen, Telefonnummern, Abteilung oder E-Mail...">
<div class="dynamic-table-container">
<table id="phonebookTable" class="dynamic-table">
<thead>
<tr>
<th class="desc" aria-sort="descending">Nachname</th>
<th class="min-width-cell">Vorname</th>
<th class="min-width-cell dynamic-width">Telefonnummer</th>
<th class="min-width-cell">Interne Rufnummer</th>
<th class="min-width-cell">E-Mail</th>
<th class="min-width-cell dynamic-width">Abteilung</th>
</tr>
</thead>
<tbody>
{{range .}}
<tr>
<td class="min-width-cell">{{.LastName}}</td>
<td class="min-width-cell">{{.FirstName}}</td>
<td class="min-width-cell dynamic-width">
{{range .Phones}}
({{.Type}}) <a href="tel:{{.PhoneNumber}}">{{.PhoneNumber}}</a><br>
{{else}}-{{end}}
</td>
<td class="min-width-cell">{{if .InternalPhone}}<a href="tel:{{.InternalPhone}}">{{.InternalPhone}}</a>{{else}}-{{end}}</td>
<td class="min-width-cell"><a href="mailto:{{.Email}}">{{.Email}}</a></td>
<td class="min-width-cell dynamic-width">{{if .Department}}{{.Department}}{{else}}-{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<div class="print-date">Stand der Liste: <span id="currentDate"></span></div>
<footer>
<div class="footer-container">
<div class="developer">
Entwickelt von Steffen Probst
</div>
<div class="license">
<a href="https://www.gnu.org/licenses/gpl-3.0.html" target="_blank">GPL-3.0 Lizenz</a>
</div>
</div>
</footer>
<script>
document.getElementById('currentDate').textContent = new Date().toLocaleDateString('de-DE');
</script>
</body>
</html>

84
static/js/phonebook.js Normal file
View File

@ -0,0 +1,84 @@
function searchTable() {
const input = document.getElementById("searchInput");
const filter = input.value; // Entfernung von .toUpperCase()
const table = document.getElementById("phonebookTable");
const tr = table.getElementsByTagName("tr");
for (let i = 1; i < tr.length; i++) {
let visible = false;
const td = tr[i].getElementsByTagName("td");
for (let j = 0; j < td.length; j++) {
if (td[j]) {
const txtValue = td[j].textContent || td[j].innerText;
if (txtValue.indexOf(filter) > -1) { // Entfernung von .toUpperCase()
visible = true;
break;
}
}
}
tr[i].style.display = visible ? "" : "none";
}
}
let lastSortedColumn = 0;
let sortDirections = Array(document.querySelectorAll('#phonebookTable th').length).fill('asc');
// Array to keep track of the sort state for each column
let sortStates = [];
function sortTable(n) {
const table = document.getElementById("phonebookTable");
const tbody = table.querySelector("tbody");
const rows = Array.from(tbody.querySelectorAll("tr"));
const th = table.querySelectorAll("th")[n];
// Initialize sort state if not set
if (sortStates[n] === undefined) {
sortStates[n] = 'asc';
} else {
// Toggle sort direction
sortStates[n] = sortStates[n] === 'asc' ? 'desc' : 'asc';
}
const isAsc = sortStates[n] === 'asc';
// Clear all sorting classes
table.querySelectorAll("th").forEach(header => header.classList.remove("asc", "desc"));
// Set the appropriate class for the clicked header
th.classList.add(isAsc ? "asc" : "desc");
rows.sort((a, b) => {
let aValue = a.cells[n].textContent.trim();
let bValue = b.cells[n].textContent.trim();
// Check if the values are numbers
if (!isNaN(aValue) && !isNaN(bValue)) {
return isAsc ? aValue - bValue : bValue - aValue;
}
// For non-numeric values, use localeCompare for proper string comparison
return isAsc ?
aValue.localeCompare(bValue, undefined, {sensitivity: 'base'}) :
bValue.localeCompare(aValue, undefined, {sensitivity: 'base'});
});
// Remove all existing rows
while (tbody.firstChild) {
tbody.removeChild(tbody.firstChild);
}
// Add sorted rows
tbody.append(...rows);
}
// Add event listeners to table headers and initial sort
document.addEventListener('DOMContentLoaded', function() {
const headers = document.querySelectorAll('#phonebookTable th');
headers.forEach((header, index) => {
header.addEventListener('click', () => sortTable(index));
});
// Initial sort on the first column (index 0) in ascending order
sortTable(0);
});