This commit is contained in:
2026-01-23 23:15:06 +01:00
parent 140f9b4288
commit a9445b305f
13 changed files with 314 additions and 287 deletions

View File

@@ -1 +1,11 @@
<?php // TODO
<?php
$env = getenv('APP_ENV') ?: 'staging';
$env = strtolower($env);
if ($env === 'production') {
$path = __DIR__ . '/production/db.php';
} else {
$path = __DIR__ . '/staging/db.php';
}
return require $path;

View File

@@ -1 +1,8 @@
<?php // TODO
<?php
return [
'db_host' => 'localhost',
'db_name' => 'd0453540',
'db_user' => 'd0453540',
'db_pass' => 'P6jGRrSaX8QSiBMEJBL7',
'db_charset' => 'utf8mb4',
];

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,237 @@
<div class="max-w-6xl mx-auto py-6 space-y-6 px-4">
<header class="flex items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold tracking-tight text-slate-900">3D-Druck Materialmatrix</h1>
<p class="text-xs text-slate-500">Schnell prüfen, welche Filamente auf welchen Druckern laufen.</p>
</div>
<span id="status" class="text-xs text-slate-500"></span>
</header>
<div class="bg-white/80 backdrop-blur rounded-lg shadow flex gap-6 p-4 min-h-[420px]">
<!-- Sidebar -->
<aside class="w-72 space-y-5 border-r pr-4 border-slate-100">
<div>
<h2 class="text-sm font-semibold text-slate-700 mb-2">Drucker auswählen</h2>
<label class="block text-xs font-medium mb-1 text-slate-600">Einzelansicht</label>
<select id="printerSelect" class="w-full border rounded px-2 py-1 mb-2 text-sm">
<option value=""> wird geladen </option>
</select>
<p class="text-xs text-slate-500">
Zeigt die Kompatibilität nur für diesen Drucker.
</p>
</div>
<div>
<label class="block text-xs font-medium mb-1 text-slate-600">Vergleich (mehrere)</label>
<select id="printerCompare" multiple class="w-full border rounded px-2 py-1 h-32 text-sm"></select>
<p class="text-xs text-slate-500">
Strg/ gedrückt halten, um mehrere zu wählen.
</p>
</div>
<div class="text-xs text-slate-400">
Tipp: Im Vergleich werden die ausgewählten Drucker rechts als separate Spalten eingefärbt.
</div>
</aside>
<!-- Main -->
<main class="flex-1 flex flex-col gap-3">
<div class="overflow-auto max-h-[70vh] rounded border bg-white" id="tableWrap">
<table class="min-w-full text-sm" id="matTable">
<thead class="bg-slate-50">
<tr id="tableHead">
<th class="px-3 py-2 text-left">Material</th>
<th class="px-3 py-2 text-left">Eigenschaften</th>
<th class="px-3 py-2 text-left">Tg °C</th>
<th class="px-3 py-2 text-left">Düse</th>
<th class="px-3 py-2 text-left">Platte</th>
<th class="px-3 py-2 text-left">Zusatz</th>
<th class="px-3 py-2 text-left">Anwendung</th>
<th class="px-3 py-2 text-left">Kinder</th>
<th class="px-3 py-2 text-left">Emission</th>
</tr>
</thead>
<tbody id="matBody"></tbody>
</table>
</div>
<div id="errorBox" class="hidden rounded bg-rose-50 border border-rose-200 text-rose-700 text-sm px-3 py-2"></div>
<!-- Hinweisblock unten -->
<section id="disclaimer" class="mt-2 rounded-lg border border-slate-200 bg-slate-50 p-4 text-xs text-slate-700 leading-snug">
<p>
<strong>Hinweis:</strong> Dieses Projekt wird privat betrieben und befindet sich im Aufbau.
Es sind noch nicht alle Drucker und Materialien eingetragen.
Alle Angaben erfolgen nach bestem Wissen, jedoch <u>ohne Gewähr auf Vollständigkeit oder Richtigkeit</u>.
</p>
</section>
</main>
</div>
</div>
<script>
const API_BASE = './api';
const printerSelect = document.getElementById('printerSelect');
const printerCompare = document.getElementById('printerCompare');
const matBody = document.getElementById('matBody');
const tableHead = document.getElementById('tableHead');
const statusEl = document.getElementById('status');
const errorBox = document.getElementById('errorBox');
function showError(msg) {
errorBox.textContent = msg;
errorBox.classList.remove('hidden');
}
function clearError() {
errorBox.classList.add('hidden');
}
async function fetchJSON(url) {
const res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + url);
return await res.json();
}
async function loadPrinters() {
try {
statusEl.textContent = 'Lade Drucker …';
const data = await fetchJSON(`${API_BASE}/printers.php`);
printerSelect.innerHTML = '';
printerCompare.innerHTML = '';
if (!data.length) {
printerSelect.innerHTML = '<option value="">(keine Drucker gefunden)</option>';
statusEl.textContent = 'Keine Drucker gefunden';
return;
}
data.forEach(p => {
printerSelect.appendChild(new Option(p.name, p.id));
printerCompare.appendChild(new Option(p.name, p.id));
});
// ersten Drucker anzeigen
loadSinglePrinter(data[0].id);
statusEl.textContent = 'Drucker geladen';
} catch (err) {
console.error(err);
showError('Konnte Drucker nicht laden. Prüfe public/api/printers.php.');
}
}
async function loadSinglePrinter(id) {
if (!id) return;
clearError();
statusEl.textContent = 'Lade Materialien …';
try {
const data = await fetchJSON(`${API_BASE}/printer-materials.php?id=${id}`);
renderTable([data]);
statusEl.textContent = 'Fertig';
} catch (err) {
console.error(err);
showError('Konnte Materialien für Drucker nicht laden.');
statusEl.textContent = 'Fehler';
}
}
async function loadMultiplePrinters(ids) {
if (!ids.length) return;
clearError();
statusEl.textContent = 'Lade Vergleich …';
try {
const datasets = await Promise.all(ids.map(id => fetchJSON(`${API_BASE}/printer-materials.php?id=${id}`)));
renderTable(datasets);
statusEl.textContent = 'Vergleich geladen';
} catch (err) {
console.error(err);
showError('Konnte einen der gewählten Drucker nicht laden.');
statusEl.textContent = 'Fehler';
}
}
function renderTable(datasets) {
// Kopf neu aufbauen
const baseHead = `
<th class="px-3 py-2 text-left">Material</th>
<th class="px-3 py-2 text-left">Eigenschaften</th>
<th class="px-3 py-2 text-left">Tg °C</th>
<th class="px-3 py-2 text-left">Düse</th>
<th class="px-3 py-2 text-left">Platte</th>
<th class="px-3 py-2 text-left">Zusatz</th>
<th class="px-3 py-2 text-left">Anwendung</th>
<th class="px-3 py-2 text-left">Kinder</th>
<th class="px-3 py-2 text-left">Emission</th>
`;
let printerCols = '';
datasets.forEach(ds => {
printerCols += `<th class="px-3 py-2 text-left bg-slate-100" data-printer="${ds.printer.id}">${ds.printer.name}</th>`;
});
tableHead.innerHTML = baseHead + printerCols;
const materials = datasets[0]?.materials ?? [];
matBody.innerHTML = '';
materials.forEach((m, idx) => {
const tr = document.createElement('tr');
tr.className = idx % 2 === 0 ? 'bg-white' : 'bg-slate-50/60';
const kid = m.kid_safety === 'safe' ? '🌿' : (m.kid_safety === 'limited' ? '🟡' : '🔴');
const em = m.emission === 'low' ? '✅' : (m.emission === 'medium' ? '⚠️' : '⛔');
let html = `
<td class="px-3 py-2 font-medium">${m.code}<div class="text-xs text-slate-500">${m.short_desc ?? ''}</div></td>
<td class="px-3 py-2">${m.properties ?? ''}</td>
<td class="px-3 py-2">${m.tg_celsius ?? ''}</td>
<td class="px-3 py-2">${m.nozzle_req ?? ''}</td>
<td class="px-3 py-2">${m.plate_req ?? ''}</td>
<td class="px-3 py-2">${m.extra_req ?? ''}</td>
<td class="px-3 py-2">${m.application ?? ''}</td>
<td class="px-3 py-2">${kid}</td>
<td class="px-3 py-2">${em}</td>
`;
// Drucker-Spalten
datasets.forEach(ds => {
const match = ds.materials.find(x => x.id === m.id || x.code === m.code);
if (!match || !match.support_level) {
html += `<td class="px-3 py-2" data-printer="${ds.printer.id}"><span class="px-2 py-1 rounded bg-slate-200 text-xs">unbekannt</span></td>`;
} else {
let badge = '';
if (match.support_level === 'full') {
badge = '<span class="px-2 py-1 rounded bg-green-100 text-green-800 text-xs">✓ voll</span>';
} else if (match.support_level === 'partial') {
badge = '<span class="px-2 py-1 rounded bg-amber-100 text-amber-800 text-xs">⚠ teilw.</span>';
} else if (match.support_level === 'with_addon') {
badge = '<span class="px-2 py-1 rounded bg-sky-100 text-sky-800 text-xs">⚙ Zusatz</span>';
} else {
badge = '<span class="px-2 py-1 rounded bg-rose-100 text-rose-800 text-xs">✗ nein</span>';
}
const note = match.partial_reason
? `<div class="text-xs text-slate-400">${match.partial_reason}</div>`
: (match.extra_info ? `<div class="text-xs text-slate-400">${match.extra_info}</div>` : '');
html += `<td class="px-3 py-2" data-printer="${ds.printer.id}">${badge}${note}</td>`;
}
});
tr.innerHTML = html;
matBody.appendChild(tr);
});
}
// Events
printerSelect.addEventListener('change', e => {
const id = e.target.value;
if (id) {
loadSinglePrinter(id);
// Vergleich leeren
printerCompare.selectedIndex = -1;
}
});
printerCompare.addEventListener('change', e => {
const ids = Array.from(e.target.selectedOptions).map(o => o.value);
if (ids.length) {
loadMultiplePrinters(ids);
}
});
// Start
loadPrinters();
</script>

View File

@@ -1 +0,0 @@

View File

@@ -1 +1,9 @@
<?php // TODO
<?php
// Basic layout end.
?>
<footer class="mt-10 border-t border-slate-200 py-6 text-center text-xs text-slate-500">
3D-Druck Materialmatrix · intern
</footer>
<?php require __DIR__ . '/matomo.php'; ?>
</body>
</html>

View File

@@ -1 +1,29 @@
<?php // TODO
<?php
// Basic layout start.
?><!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>3D-Druck Materialmatrix</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background: radial-gradient(circle at top, #e2e8f0 0%, #f8fafc 45%, #e2e8f0 90%);
}
thead th {
position: sticky;
top: 0;
backdrop-filter: blur(4px);
}
th[data-printer],
td[data-printer] {
background: rgba(148, 163, 184, 0.05);
}
#disclaimer {
box-shadow: inset 0 1px 0 rgba(148, 163, 184, 0.25);
}
</style>
</head>
<body class="bg-slate-100 min-h-screen">
<?php require __DIR__ . '/nav.php'; ?>

View File

@@ -1 +1,2 @@
<?php // TODO
<?php
// Matomo tracking can be added here if needed.

View File

@@ -1 +1,11 @@
<?php // TODO
<?php
$activePage = $activePage ?? '';
?>
<header class="bg-white/80 backdrop-blur border-b border-slate-200">
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
<div class="text-sm font-semibold text-slate-800">3D-Druck Materialmatrix</div>
<nav class="text-xs text-slate-500">
<span class="<?= $activePage === 'dashboard' ? 'text-slate-900 font-semibold' : '' ?>">Dashboard</span>
</nav>
</div>
</header>

View File

@@ -1,270 +1,2 @@
<?php
header('Content-Type: text/html; charset=utf-8');
echo <<<'HTML'
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>3D-Druck Materialmatrix</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background: radial-gradient(circle at top, #e2e8f0 0%, #f8fafc 45%, #e2e8f0 90%);
}
thead th {
position: sticky;
top: 0;
backdrop-filter: blur(4px);
}
th[data-printer],
td[data-printer] {
background: rgba(148, 163, 184, 0.05);
}
#disclaimer {
box-shadow: inset 0 1px 0 rgba(148, 163, 184, 0.25);
}
</style>
</head>
<body class="bg-slate-100 min-h-screen">
<div class="max-w-6xl mx-auto py-6 space-y-6">
<header class="flex items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold tracking-tight text-slate-900">3D-Druck MaSATGINGterialmatrix</h1>
<p class="text-xs text-slate-500">Schnell prüfen, welche Filamente auf welchen Druckern laufen.</p>
</div>
<span id="status" class="text-xs text-slate-500"></span>
</header>
<div class="bg-white/80 backdrop-blur rounded-lg shadow flex gap-6 p-4 min-h-[420px]">
<!-- Sidebar -->
<aside class="w-72 space-y-5 border-r pr-4 border-slate-100">
<div>
<h2 class="text-sm font-semibold text-slate-700 mb-2">Drucker auswählen</h2>
<label class="block text-xs font-medium mb-1 text-slate-600">Einzelansicht</label>
<select id="printerSelect" class="w-full border rounded px-2 py-1 mb-2 text-sm">
<option value=""> wird geladen </option>
</select>
<p class="text-xs text-slate-500">
Zeigt die Kompatibilität nur für diesen Drucker.
</p>
</div>
<div>
<label class="block text-xs font-medium mb-1 text-slate-600">Vergleich (mehrere)</label>
<select id="printerCompare" multiple class="w-full border rounded px-2 py-1 h-32 text-sm"></select>
<p class="text-xs text-slate-500">
Strg/⌘ gedrückt halten, um mehrere zu wählen.
</p>
</div>
<div class="text-xs text-slate-400">
Tipp: Im Vergleich werden die ausgewählten Drucker rechts als separate Spalten eingefärbt.
</div>
</aside>
<!-- Main -->
<main class="flex-1 flex flex-col gap-3">
<div class="overflow-auto max-h-[70vh] rounded border bg-white" id="tableWrap">
<table class="min-w-full text-sm" id="matTable">
<thead class="bg-slate-50">
<tr id="tableHead">
<th class="px-3 py-2 text-left">Material</th>
<th class="px-3 py-2 text-left">Eigenschaften</th>
<th class="px-3 py-2 text-left">Tg °C</th>
<th class="px-3 py-2 text-left">Düse</th>
<th class="px-3 py-2 text-left">Platte</th>
<th class="px-3 py-2 text-left">Zusatz</th>
<th class="px-3 py-2 text-left">Anwendung</th>
<th class="px-3 py-2 text-left">Kinder</th>
<th class="px-3 py-2 text-left">Emission</th>
</tr>
</thead>
<tbody id="matBody"></tbody>
</table>
</div>
<div id="errorBox" class="hidden rounded bg-rose-50 border border-rose-200 text-rose-700 text-sm px-3 py-2"></div>
<!-- Hinweisblock unten -->
<section id="disclaimer" class="mt-2 rounded-lg border border-slate-200 bg-slate-50 p-4 text-xs text-slate-700 leading-snug">
<p>
<strong>Hinweis:</strong> Dieses Projekt wird privat betrieben und befindet sich im Aufbau.
Es sind noch nicht alle Drucker und Materialien eingetragen.
Alle Angaben erfolgen nach bestem Wissen, jedoch <u>ohne Gewähr auf Vollständigkeit oder Richtigkeit</u>.
</p>
</section>
</main>
</div>
</div>
<script>
const API_BASE = './api';
const printerSelect = document.getElementById('printerSelect');
const printerCompare = document.getElementById('printerCompare');
const matBody = document.getElementById('matBody');
const tableHead = document.getElementById('tableHead');
const statusEl = document.getElementById('status');
const errorBox = document.getElementById('errorBox');
function showError(msg) {
errorBox.textContent = msg;
errorBox.classList.remove('hidden');
}
function clearError() {
errorBox.classList.add('hidden');
}
async function fetchJSON(url) {
const res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + url);
return await res.json();
}
async function loadPrinters() {
try {
statusEl.textContent = 'Lade Drucker …';
const data = await fetchJSON(`${API_BASE}/printers.php`);
printerSelect.innerHTML = '';
printerCompare.innerHTML = '';
if (!data.length) {
printerSelect.innerHTML = '<option value="">(keine Drucker gefunden)</option>';
statusEl.textContent = 'Keine Drucker gefunden';
return;
}
data.forEach(p => {
printerSelect.appendChild(new Option(p.name, p.id));
printerCompare.appendChild(new Option(p.name, p.id));
});
// ersten Drucker anzeigen
loadSinglePrinter(data[0].id);
statusEl.textContent = 'Drucker geladen';
} catch (err) {
console.error(err);
showError('Konnte Drucker nicht laden. Prüfe public/api/printers.php.');
}
}
async function loadSinglePrinter(id) {
if (!id) return;
clearError();
statusEl.textContent = 'Lade Materialien …';
try {
const data = await fetchJSON(`${API_BASE}/printer-materials.php?id=${id}`);
renderTable([data]);
statusEl.textContent = 'Fertig';
} catch (err) {
console.error(err);
showError('Konnte Materialien für Drucker nicht laden.');
statusEl.textContent = 'Fehler';
}
}
async function loadMultiplePrinters(ids) {
if (!ids.length) return;
clearError();
statusEl.textContent = 'Lade Vergleich …';
try {
const datasets = await Promise.all(ids.map(id => fetchJSON(`${API_BASE}/printer-materials.php?id=${id}`)));
renderTable(datasets);
statusEl.textContent = 'Vergleich geladen';
} catch (err) {
console.error(err);
showError('Konnte einen der gewählten Drucker nicht laden.');
statusEl.textContent = 'Fehler';
}
}
function renderTable(datasets) {
// Kopf neu aufbauen
const baseHead = `
<th class="px-3 py-2 text-left">Material</th>
<th class="px-3 py-2 text-left">Eigenschaften</th>
<th class="px-3 py-2 text-left">Tg °C</th>
<th class="px-3 py-2 text-left">Düse</th>
<th class="px-3 py-2 text-left">Platte</th>
<th class="px-3 py-2 text-left">Zusatz</th>
<th class="px-3 py-2 text-left">Anwendung</th>
<th class="px-3 py-2 text-left">Kinder</th>
<th class="px-3 py-2 text-left">Emission</th>
`;
let printerCols = '';
datasets.forEach(ds => {
printerCols += `<th class="px-3 py-2 text-left bg-slate-100" data-printer="${ds.printer.id}">${ds.printer.name}</th>`;
});
tableHead.innerHTML = baseHead + printerCols;
const materials = datasets[0]?.materials ?? [];
matBody.innerHTML = '';
materials.forEach((m, idx) => {
const tr = document.createElement('tr');
tr.className = idx % 2 === 0 ? 'bg-white' : 'bg-slate-50/60';
const kid = m.kid_safety === 'safe' ? '🌿' : (m.kid_safety === 'limited' ? '🟡' : '🔴');
const em = m.emission === 'low' ? '✅' : (m.emission === 'medium' ? '⚠️' : '⛔');
let html = `
<td class="px-3 py-2 font-medium">${m.code}<div class="text-xs text-slate-500">${m.short_desc ?? ''}</div></td>
<td class="px-3 py-2">${m.properties ?? ''}</td>
<td class="px-3 py-2">${m.tg_celsius ?? ''}</td>
<td class="px-3 py-2">${m.nozzle_req ?? ''}</td>
<td class="px-3 py-2">${m.plate_req ?? ''}</td>
<td class="px-3 py-2">${m.extra_req ?? ''}</td>
<td class="px-3 py-2">${m.application ?? ''}</td>
<td class="px-3 py-2">${kid}</td>
<td class="px-3 py-2">${em}</td>
`;
// Drucker-Spalten
datasets.forEach(ds => {
const match = ds.materials.find(x => x.id === m.id || x.code === m.code);
if (!match || !match.support_level) {
html += `<td class="px-3 py-2" data-printer="${ds.printer.id}"><span class="px-2 py-1 rounded bg-slate-200 text-xs">unbekannt</span></td>`;
} else {
let badge = '';
if (match.support_level === 'full') {
badge = '<span class="px-2 py-1 rounded bg-green-100 text-green-800 text-xs">✓ voll</span>';
} else if (match.support_level === 'partial') {
badge = '<span class="px-2 py-1 rounded bg-amber-100 text-amber-800 text-xs">⚠ teilw.</span>';
} else if (match.support_level === 'with_addon') {
badge = '<span class="px-2 py-1 rounded bg-sky-100 text-sky-800 text-xs">⚙ Zusatz</span>';
} else {
badge = '<span class="px-2 py-1 rounded bg-rose-100 text-rose-800 text-xs">✗ nein</span>';
}
const note = match.partial_reason
? `<div class="text-xs text-slate-400">${match.partial_reason}</div>`
: (match.extra_info ? `<div class="text-xs text-slate-400">${match.extra_info}</div>` : '');
html += `<td class="px-3 py-2" data-printer="${ds.printer.id}">${badge}${note}</td>`;
}
});
tr.innerHTML = html;
matBody.appendChild(tr);
});
}
// Events
printerSelect.addEventListener('change', e => {
const id = e.target.value;
if (id) {
loadSinglePrinter(id);
// Vergleich leeren
printerCompare.selectedIndex = -1;
}
});
printerCompare.addEventListener('change', e => {
const ids = Array.from(e.target.selectedOptions).map(o => o.value);
if (ids.length) {
loadMultiplePrinters(ids);
}
});
// Start
loadPrinters();
</script>
</body>
</html>
HTML;
require __DIR__ . '/page/dashboard.php';

View File

@@ -1 +1,5 @@
<?php // TODO
<?php
$activePage = 'dashboard';
require __DIR__ . '/../../partials/structure/layout_start.php';
require __DIR__ . '/../../partials/landing/main/material-matrix.php';
require __DIR__ . '/../../partials/structure/layout_end.php';

View File

@@ -1,10 +1,2 @@
<?php
// inc/config.php
// Diese Datei außerhalb des Webroots ablegen!
return [
'db_host' => 'localhost',
'db_name' => 'd0453540',
'db_user' => 'd0453540',
'db_pass' => 'P6jGRrSaX8QSiBMEJBL7',
'db_charset' => 'utf8mb4',
];
return require __DIR__ . '/../config/config.php';