Files
intranet/web/intranet_repo/projects/dashboard/dashboard.php
T

513 lines
27 KiB
PHP

<?php
date_default_timezone_set('Europe/Berlin');
require_once __DIR__ . '/../contentpanel/insertion/news_cleanup.php';
// DB-Verbindung zentral laden (liefert PDO-Objekt zurück)
$pdo = require __DIR__ . '/../../general_files/ini/database.php';
$openWeatherApiKey = intranet_env('OPENWEATHER_API_KEY');
$heute = date('Y-m-d');
// --- Gottesdienst (Bibelwort & Eingangslied) ---
$sql = "SELECT bibelwort, bibelvers, eingang_nummer, eingang_titel
FROM gottesdienst
WHERE bibeldatum = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$heute]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
$bibelwort = $row['bibelwort'];
$bibelvers = $row['bibelvers'];
$eingangNummer = $row['eingang_nummer'];
$eingangTitel = $row['eingang_titel'];
} else {
$bibelwort = "Bibelwort nicht eingetragen";
$bibelvers = "Bibelvers nicht gefunden.";
$eingangNummer = "N/A";
$eingangTitel = "Eingangslied nicht gefunden";
}
// --- Ansagen ---
$sqlAnsagen = "SELECT ansage_datum_von, ansage_datum_bis, ansage_uhrzeit, ansage_ort, ansage_titel, ansage_untertitel
FROM ansagen
WHERE ansage_datum_von >= ? OR ansage_datum_bis >= ?
ORDER BY ansage_datum_von ASC";
$stmtAnsagen = $pdo->prepare($sqlAnsagen);
$stmtAnsagen->execute([$heute, $heute]);
$ansagenResult = $stmtAnsagen->fetchAll(PDO::FETCH_ASSOC);
// Verbindung schließen (optional, aber sauber)
$pdo = null;
// --- Aushänge ---
$pngDirectoryUrl = '../contentpanel/insertion/news/png/';
// Abgelaufene Aushänge einschließlich Original-PDF bereinigen
cleanupExpiredNews();
$pngDirectoryFs = realpath(
__DIR__ . '/../contentpanel/insertion/news/png'
);
$pngFiles = ($pngDirectoryFs && is_dir($pngDirectoryFs))
? scandir($pngDirectoryFs)
: [];
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NAK Reinickendorf</title>
<link rel="icon" type="image/svg+xml" href="/intranet_repo/projects/_central/img/nak-logo.svg">
<link rel="shortcut icon" href="/intranet_repo/projects/_central/img/favicon.ico">
<link rel="stylesheet" href="stylo.css?v=<?php echo time(); ?>">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=SUSE:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Anton&display=swap" rel="stylesheet">
</head>
<body>
<div class="content-board">
<div class="content-column-right">
<div class="content-column-right-upper-box">
<div class="content-column-right-upper-box-right">
<div class="content-item-time">
<div class="time">
<div id="uhrzeit"></div>
</div>
<div class="date">
<center><?php
$months = ["UNDEFINED", "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
echo date("d. ") . $months[intval(date("m"))];
?></center>
</div>
</div>
<div class="content-item-daily">
<div class="header-sticky-note">
<div>Heutige Ansagen</div>
</div>
<div class="news-container">
<div class="news-scroll">
<?php foreach ($ansagenResult as $ansage) : ?>
<div class="news-box">
<div class="news-date">
<?php
$wochentage = [
'Mon' => 'Mo','Tue' => 'Di','Wed' => 'Mi','Thu' => 'Do',
'Fri' => 'Fr','Sat' => 'Sa','Sun' => 'So'
];
$datum_von_timestamp = strtotime($ansage['ansage_datum_von']);
$heute = date("Y-m-d");
$morgen = date("Y-m-d", strtotime('+1 day'));
$datum_von_format = date("Y-m-d", $datum_von_timestamp);
if ($datum_von_format === $heute) {
echo "Heute";
} elseif ($datum_von_format === $morgen) {
echo "Morgen";
} else {
$datum_von = date("d.m.", $datum_von_timestamp);
$wochentag_englisch = date("D", $datum_von_timestamp);
$wochentag = $wochentage[$wochentag_englisch] ?? $wochentag_englisch;
echo $wochentag . '&nbsp;' . $datum_von;
}
if (!empty($ansage['ansage_datum_bis'])) {
$datum_bis = date("d.m.", strtotime($ansage['ansage_datum_bis']));
echo "<br> bis " . $datum_bis;
}
?>
</div>
<div>
<?php echo htmlspecialchars($ansage['ansage_titel']); ?>
<?php if (!empty($ansage['ansage_untertitel'])) : ?>
<div class="news-subtitle"><?php echo htmlspecialchars($ansage['ansage_untertitel']); ?></div>
<?php endif; ?>
<?php if (!empty($ansage['ansage_uhrzeit']) && !empty($ansage['ansage_ort'])) : ?>
<div class="news-subtitle"><i><?php echo date("H:i", strtotime($ansage['ansage_uhrzeit'])) . ' Uhr'; ?>, <?php echo htmlspecialchars($ansage['ansage_ort']); ?></i></div>
<?php elseif (!empty($ansage['ansage_uhrzeit'])) : ?>
<div class="news-subtitle"><i><?php echo date("H:i", strtotime($ansage['ansage_uhrzeit'])) . ' Uhr'; ?></i></div>
<?php elseif (!empty($ansage['ansage_ort'])) : ?>
<div class="news-subtitle"><i><?php echo htmlspecialchars($ansage['ansage_ort']); ?></i></div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<div class="content-column-right-upper-box-left">
<div class="content-item-middle-top">
<div class="header-sticky-note">
<div>Bibelwort</div>
</div>
<div class="bible"><?php echo $bibelwort; ?></div>
<div class="verse"><?php echo $bibelvers; ?></div>
</div>
<div class="content-column-double-item">
<div class="content-item">
<div class="header-sticky-note">
<div>Eingangslied</div>
</div>
<div class="GB"><?php echo $eingangNummer; ?></div>
<div class="GB-title"><?php echo $eingangTitel; ?></div>
</div>
<div class="content-item">
<div class="header-sticky-note">
<div>Temperatur</div>
</div>
<div class="temperature">
<div class="container">
<div id="wetter"></div>
</div>
<div class="container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 60 60" width="64px" height="64px" baseProfile="basic">
<polygon fill="#66e1ff" points="46,22.11 46,24.42 43,26.15 43,34.24 25,44.63 14,38.28 14,35.97 12,34.81 10,35.97 7,34.24 7,20.38 4,20.96 4,18.65 22,8.26 34,5.95"/>
<polygon fill="#4292a6" points="25,30.774 28,34.815 43,26.155 43,34.238 25,44.63"/>
<polygon fill="#ff5757" points="16,18.649 4,20.959 4,18.649 16,16.34 28,32.506 28,34.815"/>
<polygon fill="#4292a6" points="34,8.257 22,10.566 22,8.257 34,5.947 46,22.113 46,24.423"/>
<polygon fill="#e64e4e" points="4,18.649 22,8.257 34,5.947 16,16.34"/>
<polygon fill="#cc4545" points="28,32.506 46,22.113 34,5.947 16,16.34"/>
<polygon fill="#b33d3d" points="28,32.506 46,22.113 46,24.423 28,34.815"/>
<polygon fill="#4292a6" points="17,34.238 22,37.124 22,31.351 17,28.464"/>
<polygon fill="#4292a6" points="10,35.97 12,34.815 12,27.887 10,26.732"/>
<polygon fill="#4ad8ff" points="19,33.083 22,34.815 22,31.351 19,29.619"/>
<polygon fill="#52b1cc" points="17,34.238 22,37.124 22,34.815 19,33.083"/>
<polygon fill="#ff5757" points="12,34.815 14,35.97 14,29.041 12,27.887"/>
</svg>
<div class="text-indoor" id='temperature_txt'></div>
</div>
</div>
</div>
</div>
</div> <!-- /left upper box -->
</div> <!-- /right upper -->
<div class="content-item-bottom-box">
<div class="header-sticky-note">
<div>Aushänge</div>
</div>
<div class="aushang-flex">
<?php
$hasPng = false;
foreach ($pngFiles as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'png') {
$hasPng = true;
echo '<img src="' . $pngDirectoryUrl . $file . '" alt="Aushang" height="95%">';
}
}
if (!$hasPng) {
echo '<p style="text-align: left;">Keine Aushänge vorgeplant.</p>';
}
?>
</div>
</div>
</div> <!-- /right column -->
<div class="content-column-left">
<div class="content-item-video">
<!-- leerer Container, kein Video/HLS -->
<div class="video-container" aria-label="Leer"></div>
</div>
<div class="content-item-month">
<div class="header-sticky-note">
<div>Monatsplan</div>
</div>
<div class="content-item-month-cell">
<img src="month/terminuebersicht-1.png?v=<?php echo time(); ?>" alt="image">
<img src="month/terminuebersicht-2.png?v=<?php echo time(); ?>" alt="image">
</div>
</div>
</div> <!-- /left column -->
</div> <!-- /content-board -->
<script>
function updateClock() {
var now = new Date();
var uhrzeitElement = document.getElementById("uhrzeit");
var stunden = now.getHours();
var minuten = now.getMinutes();
uhrzeitElement.innerHTML = + padZero(stunden) + ':' + padZero(minuten);
}
function padZero(number) { return number < 10 ? '0' + number : number; }
updateClock();
setInterval(updateClock, 1000);
// OpenWeather (Außentemperatur+Icon)
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=52.571291&lon=13.310422&appid=<?= rawurlencode($openWeatherApiKey) ?>&units=metric&language=german`;
// Wetter-Icons
const cloudy = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<defs>
<filter id="blur" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="2.5"/>
<feOffset dx="0" dy="2" result="off"/>
<feComponentTransfer><feFuncA type="linear" slope="0.15"/></feComponentTransfer>
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g filter="url(#blur)">
<path d="M22 46h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 20 29a9 9 0 0 0 2 17z" fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<path d="M18 46h22a9 9 0 0 0 0-18c-.7 0-1.4.1-2 .2A11 11 0 0 0 17 31a8 8 0 0 0 1 15z" fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.95"/>
</g>
</svg>`;
const sun = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round">
<circle cx="32" cy="32" r="10" fill="#FFD34D" stroke="#fff"/>
<path d="M32 6v8M32 50v8M6 32h8M50 32h8M13.5 13.5l5.7 5.7M44.8 44.8l5.7 5.7M50.5 13.5l-5.7 5.7M19.2 44.8l-5.7 5.7"/>
</g>
</svg>`;
const moon = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<path d="M41 8a22 22 0 1 0 15 35A18 18 0 0 1 41 8z" fill="#DDE7FF" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
</svg>`;
const cloudy_day = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g>
<g transform="translate(4,0)">
<circle cx="22" cy="22" r="9" fill="#FFD34D" stroke="#fff" stroke-width="2"/>
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round">
<path d="M22 6v6M22 32v6M6 22h6M32 22h6M10.5 10.5l4.2 4.2M29.3 29.3l4.2 4.2M33.5 10.5l-4.2 4.2M14.7 29.3l-4.2 4.2"/>
</g>
</g>
<g transform="translate(8,10)">
<path d="M18 36h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 16 19a9 9 0 0 0 2 17z"
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<path d="M22 36h22a9 9 0 0 0 0-18c-.7 0-1.4.1-2 .2A11 11 0 0 0 21 21a8 8 0 0 0 1 15z"
fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.95"/>
</g>
</g>
</svg>`;
const cloudy_night = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g>
<path d="M38 10a16 16 0 1 0 11 26A13 13 0 0 1 38 10z" fill="#DDE7FF" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<g transform="translate(10,18)">
<path d="M14 28h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 12 11a9 9 0 0 0 2 17z"
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<path d="M18 28h22a9 9 0 0 0 0-18c-.7 0-1.4.1-2 .2A11 11 0 0 0 17 13a8 8 0 0 0 1 15z"
fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.95"/>
</g>
</g>
</svg>`;
const rain_shower = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g>
<path d="M20 36h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 18 19a9 9 0 0 0 2 17z"
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<g stroke="#fff" stroke-width="2" stroke-linecap="round">
<path d="M24 42l-3 6" />
<path d="M34 42l-3 6" />
<path d="M44 42l-3 6" />
</g>
</g>
</svg>`;
const rain = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g>
<path d="M20 34h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 18 17a9 9 0 0 0 2 17z"
fill="#57A0EE" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<g stroke="#fff" stroke-width="2" stroke-linecap="round">
<path d="M22 40l-3 8" />
<path d="M32 40l-3 8" />
<path d="M42 40l-3 8" />
<path d="M52 40l-3 8" />
</g>
</g>
</svg>`;
const thunder = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g>
<path d="M18 34h28a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 16 17a9 9 0 0 0 2 17z"
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<path d="M32 36l-6 12h7l-3 10 12-16h-7l3-6z"
fill="#FFD34D" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
</g>
</svg>`;
const snowy = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g>
<path d="M20 34h26a10 10 0 0 0 0-20c-.8 0-1.6.1-2.4.3A13 13 0 0 0 18 17a9 9 0 0 0 2 17z"
fill="#91C0F8" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
<g fill="#fff">
<circle cx="24" cy="44" r="2"/>
<circle cx="34" cy="44" r="2"/>
<circle cx="44" cy="44" r="2"/>
<circle cx="29" cy="52" r="2"/>
<circle cx="39" cy="52" r="2"/>
</g>
</g>
</svg>`;
const mist = `<svg xmlns="http://www.w3.org/2000/svg" width="49" height="49" viewBox="6 0 50 50" aria-hidden="true">
<g fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" opacity="0.95">
<path d="M14 24h36"/>
<path d="M10 32h44"/>
<path d="M14 40h36"/>
<path d="M18 48h28"/>
</g>
</svg>`;
async function getWeather() {
try {
var response = await fetch(apiUrl);
var data = await response.json();
if (data.cod === 200) {
var weatherIcon = data.weather[0].icon;
var temperature = data.main.temp;
var weatherElement = document.getElementById('wetter');
if (["04d","04n","03d","03n"].includes(weatherIcon)) {
weatherElement.innerHTML = '<div>' + cloudy + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (weatherIcon === "02d") {
weatherElement.innerHTML = '<div>' + cloudy_day + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (weatherIcon === "02n") {
weatherElement.innerHTML = '<div>' + cloudy_night + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (weatherIcon === "01d") {
weatherElement.innerHTML = '<div>' + sun + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (weatherIcon === "01n") {
weatherElement.innerHTML = '<div>' + moon + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (["09d","09n","10n"].includes(weatherIcon)) {
weatherElement.innerHTML = '<div>' + rain_shower + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (weatherIcon === "10d") {
weatherElement.innerHTML = '<div>' + rain + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (["11d","11n"].includes(weatherIcon)) {
weatherElement.innerHTML = '<div>' + thunder + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (["13d","13n"].includes(weatherIcon)) {
weatherElement.innerHTML = '<div>' + snowy + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
} else if (["50d","50n"].includes(weatherIcon)) {
weatherElement.innerHTML = '<div>' + mist + `</div><div class="text-outdoor"><center>${Math.round(temperature)}°C</center></div>`;
}
} else {
console.error('Fehler beim Abrufen des Wetters:', data.message);
}
} catch (error) {
console.error('Fehler beim Abrufen des Wetters:', error.message);
}
}
getWeather();
setInterval(getWeather, 30000);
// INNENTEMPERATUR: jetzt über Home Assistant (Server-Proxy)
function updateTemperature() {
const entityId = 'sensor.temp_temperatur';
fetch('ha_temperature.php?entity_id=' + encodeURIComponent(entityId))
.then(r => r.json())
.then(data => {
if (data.error) {
console.error('HA Fehler:', data);
return;
}
document.getElementById('temperature_txt').innerText = `${data.value}${data.unit || '°C'}`;
})
.catch(err => console.error('Fetch Fehler:', err));
}
updateTemperature();
setInterval(updateTemperature, 10000);
function updateContent() {
fetch('update_content.php')
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Fehler:', data.error);
return;
}
document.querySelector('.bible').innerText = data.bibelwort;
document.querySelector('.verse').innerText = data.bibelvers;
document.querySelector('.GB').innerText = data.eingang_nummer;
document.querySelector('.GB-title').innerText = data.eingang_titel;
setupScrolling();
})
.catch(error => {
console.error('Fehler beim Abrufen der Daten:', error);
});
}
updateContent();
setInterval(updateContent, 30000);
function setupScrolling() {
const SPEED_V = 7; // News vertikal
const SPEED_H = 10; // Aushänge horizontal
const containerFlex = document.querySelector('.aushang-flex');
if (containerFlex) {
const images = containerFlex.querySelectorAll('img');
const measureAndApplyH = () => {
let totalWidth = 0;
images.forEach(img => {
const styles = window.getComputedStyle(img);
const mr = parseFloat(styles.marginRight) || 0;
totalWidth += (img.offsetWidth || 0) + mr;
});
const containerWidth = containerFlex.offsetWidth || 0;
const scrollDistanceH = Math.max(0, totalWidth - containerWidth);
containerFlex.style.setProperty('--scroll-distance-h', `-${scrollDistanceH}px`);
const durationH = Math.max(1, scrollDistanceH / SPEED_H);
if (scrollDistanceH === 0) {
containerFlex.style.animation = 'none';
} else {
containerFlex.style.animationName = 'scroll-h';
containerFlex.style.animationDuration = `${durationH}s`;
containerFlex.style.animationTimingFunction = 'linear';
containerFlex.style.animationIterationCount = 'infinite';
}
};
let pending = 0;
images.forEach(img => {
if (!img.complete || img.naturalWidth === 0) {
pending++;
img.addEventListener('load', () => {
pending--;
if (pending === 0) measureAndApplyH();
}, { once: true });
}
});
if (pending === 0) measureAndApplyH();
}
const containerNews = document.querySelector('.news-container');
const scrollBox = document.querySelector('.news-scroll');
if (containerNews && scrollBox) {
const totalHeight = scrollBox.scrollHeight || 0;
const visibleHeight = containerNews.offsetHeight || 0;
const scrollDistanceV = Math.max(0, totalHeight - visibleHeight);
scrollBox.style.setProperty('--scroll-distance-v', `-${scrollDistanceV}px`);
const durationV = Math.max(1, scrollDistanceV / SPEED_V);
if (scrollDistanceV === 0) {
scrollBox.style.animation = 'none';
} else {
scrollBox.style.animationName = 'scroll-v';
scrollBox.style.animationDuration = `${durationV}s`;
scrollBox.style.animationTimingFunction = 'linear';
scrollBox.style.animationIterationCount = 'infinite';
}
}
}
window.addEventListener('load', () => {
const news = document.querySelector('.news-scroll');
const aushang = document.querySelector('.aushang-flex');
if (news) news.style.animation = 'none';
if (aushang) aushang.style.animation = 'none';
setTimeout(setupScrolling, 50);
});
window.addEventListener('resize', () => {
const news = document.querySelector('.news-scroll');
const aushang = document.querySelector('.aushang-flex');
if (news) news.style.animation = 'none';
if (aushang) aushang.style.animation = 'none';
setTimeout(setupScrolling, 100);
});
</script>
</body>
</html>