@@ -1,237 +1,69 @@
< div class = " max-w-6xl mx-auto py-6 space-y-6 px-4 " >
< header class = " flex items-center justify-between gap-4 " >
< ? php
$app = app ();
$app -> assets () -> addStyle ( '/assets/app.css' , 'early' );
$app -> assets () -> addScript ( '/assets/app.js' , 'footer' , true );
?>
<section class="mm-shell">
<header class="mm-header">
<div>
< h1 class = " text-2xl font-bold tracking-tight text-slate-900 " > 3 D - Druck Materialmatrix</ h1 >
< p class = " text-xs text-slate-500 " > Schnell prüfen , welche Filamente auf welchen Druckern laufen .</ p >
<p class="mm-kicker"> Materialmatrix</p>
<h1 class="mm-title"><?= htmlspecialchars(t('common.title'), ENT_QUOTES) ?></h1>
<p class="mm-subtitle">Schnell prüfen, welche Filamente auf welchen Druckern laufen.</p>
</div>
< span id = " status " class = " text-xs text-slate-500 " ></ span >
<div class="mm-status" id="status">Bereit</div>
</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 " >
<div class="mm-card">
<aside class="mm-sidebar">
<div class="mm-panel">
<h2>Drucker auswählen</h2>
<label for="printerSelect">Einzelansicht</label>
<select id="printerSelect">
<option value="">– wird geladen – </option>
</select>
< p class = " text-xs text-slate-500 " >
Zeigt die Kompatibilität nur für diesen Drucker .
</ p >
<p>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 class="mm-panel">
< label for="printerCompare"> Vergleich ( mehrere)</label>
< select id=" printerCompare" multiple></select>
<p>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 class="mm-tip">
Tipp: Im Vergleich werden die ausgewählten Drucker rechts als separate Spalten hervorgehoben.
</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 " >
<main class="mm-main">
<div class="mm-table-wrap" id="tableWrap">
<table id="matTable" class="mm-table">
<thead>
<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 >
<th>Material</th>
<th>Eigenschaften</th>
<th>Tg °C</th>
<th>Düse</th>
<th>Platte</th>
<th>Zusatz</th>
<th>Anwendung</th>
<th>Kinder</th>
<th>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>
<div id="errorBox" class="mm-error" hidden ></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 .
<section class="mm-disclaimer">
<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 >
</section>