1144 lines
58 KiB
Plaintext
1144 lines
58 KiB
Plaintext
<?php
|
||
/**
|
||
* Krempl PostgreSQL Search Test Interface
|
||
* Search for devices and show matching ersatzteile
|
||
*/
|
||
|
||
require_once __DIR__ . '/core/postgres/KremplDB.php';
|
||
require_once __DIR__ . '/core/postgres/tba_render.php';
|
||
|
||
/**
|
||
* AJAX: Shop-Artikel eines Geraets nachladen.
|
||
*
|
||
* Muss vor jeder HTML-Ausgabe stehen. Hintergrund: vorher lud die Seite die Artikel fuer
|
||
* ALLE 20 Treffer vor, obwohl 19 davon hinter "Nicht mein Geraet" eingeklappt sind --
|
||
* das waren 40 MySQL-Queries auf Vorrat und der Grund fuer ~16 s Laufzeit. Jetzt laedt nur
|
||
* der Top-Treffer vor, die uebrigen Kacheln holen ihre Liste beim Aufklappen hier ab.
|
||
* An der Legacy-Shop-DB wird dabei nichts geaendert, sie wird nur seltener gefragt.
|
||
*/
|
||
if (isset($_GET['ajax']) && $_GET['ajax'] === 'items') {
|
||
header('Content-Type: text/html; charset=UTF-8');
|
||
header('Cache-Control: no-store');
|
||
$gid = isset($_GET['geraet_id']) ? (int)$_GET['geraet_id'] : 0;
|
||
if ($gid <= 0) {
|
||
http_response_code(400);
|
||
echo '<div class="v2-lazy-msg">Ungültige Geräte-ID.</div>';
|
||
exit;
|
||
}
|
||
try {
|
||
$db = new KremplDB(true);
|
||
$buy = $db->getAvailableItemsForGeraet($gid, 200);
|
||
if ($buy) {
|
||
echo '<h4>Verfügbare Artikel für dieses Gerät (' . count($buy) . '):</h4>';
|
||
echo v2_artikel_liste($buy, 12, 12);
|
||
} else {
|
||
// Zwei sehr unterschiedliche Faelle, die man nicht in einen Satz packen darf:
|
||
// a) der Hersteller fuehrt zu diesem Geraet gar keine Ersatzteile (z.B. Beko
|
||
// CN332100 BEKO_RUSSIA, 0 Eintraege in ersatzteil_mapping) -- da ist nichts zu holen.
|
||
// b) es gibt Ersatzteile, aber keines davon ist ein aktiver Shop-Artikel -- das
|
||
// lohnt eine Rueckfrage beim Grosshandel.
|
||
// Der Zusatz-Query laeuft nur im Leerfall.
|
||
$parts = $db->getErsatzteileForGeraet($gid, 1);
|
||
if (!$parts) {
|
||
echo '<div class="v2-lazy-msg">Zu diesem Gerät sind in den Herstellerdaten '
|
||
. 'keine Ersatzteile hinterlegt.</div>';
|
||
} else {
|
||
echo '<div class="v2-lazy-msg">Für dieses Gerät sind Ersatzteile bekannt, aber '
|
||
. 'derzeit keines davon im Shop verfügbar.</div>';
|
||
}
|
||
}
|
||
} catch (Exception $e) {
|
||
http_response_code(500);
|
||
echo '<div class="v2-lazy-msg">Artikel konnten nicht geladen werden.</div>';
|
||
}
|
||
exit;
|
||
}
|
||
|
||
$search_term = isset($_GET['q']) ? trim($_GET['q']) : '';
|
||
$results = [];
|
||
$item_results = [];
|
||
$search_time = 0;
|
||
$item_search_time = 0;
|
||
$error_message = '';
|
||
// "Nur verfuegbare Shop-Artikel" ist jetzt der IST-Zustand (Default AN): das verkleinert die
|
||
// Trefferliste auf real bestellbare Teile, und getAvailableItemsForGeraet() liefert dabei
|
||
// items.id mit -- also sind die Chips direkt in den Shop verlinkbar. Abschaltbar mit
|
||
// ?shop_items=0 (die Checkbox sendet das ueber ein vorgeschaltetes hidden-Feld).
|
||
$show_shop_items = !isset($_GET['shop_items']) || $_GET['shop_items'] !== '0';
|
||
$v2_guided = false; // gefuehrte Ansicht aktiv (ein klarer Top-Treffer)
|
||
$v2_top_detail = null; // Volldaten des Top-Geraets aus geraete
|
||
$v2_brandpick = []; // marke => anzahl, wenn erst der Hersteller geklaert werden muss
|
||
$v2_marke = isset($_GET['marke']) ? trim($_GET['marke']) : ''; // gewaehlter Hersteller
|
||
$v2_variants = []; // BSH-Bauzustaende /01 /02 ..., wenn nur der Zeitraum unterscheidet
|
||
$v2_variant_base = ''; // gemeinsame Typnummer ohne Suffix, z.B. "WT46W560BY"
|
||
$v2_shopmap = []; // navision_id => Shop-Artikel, fuer die Links an den Chips
|
||
$v2_partcounts = []; // geraet_id => Anzahl Ersatzteile (fuer die eingeklappten Kacheln)
|
||
|
||
try {
|
||
// MySQL wird in beiden Fällen gebraucht (rechte Spalte = Artikel-Suche)
|
||
$db = new KremplDB(true);
|
||
|
||
if ($search_term !== '') {
|
||
// Links: Geräte-Suche (PostgreSQL)
|
||
$start_time = microtime(true);
|
||
// Ersatzteile je Treffer nur im Debug-Modus (?shop_items=0) mitladen -- dort zeigen die
|
||
// Kacheln Ersatzteil-Chips statt des Lazy-Aufklappers. Im Normalfall kostet das 20
|
||
// ueberfluessige PG-Queries, deren Ergebnis nur der Top-Treffer braucht.
|
||
$results = $db->searchGeraete($search_term, 20, !$show_shop_items);
|
||
$search_time = (microtime(true) - $start_time) * 1000;
|
||
|
||
// Rechts: Artikel-/Ersatzteil-Suche (MySQL)
|
||
$start_time2 = microtime(true);
|
||
$item_results = $db->searchItems($search_term, 20);
|
||
$item_search_time = (microtime(true) - $start_time2) * 1000;
|
||
|
||
// --- Hersteller-Filter (kommt aus der Hersteller-Rueckfrage) ---
|
||
if ($v2_marke !== '') {
|
||
$results = array_values(array_filter($results, function ($d) use ($v2_marke) {
|
||
return isset($d['marke']) && trim($d['marke']) === $v2_marke;
|
||
}));
|
||
}
|
||
|
||
// --- Stufe 1: muss erst der Hersteller geklaert werden? ---
|
||
// Dasselbe Modell laeuft oft unter mehreren Konzern-/Versandhausmarken. Dann haben
|
||
// alle Top-Treffer denselben Score und denselben Modellnamen -- eine Bestaetigung
|
||
// waere geraten. Der Hersteller steht dem Kunden aber auf dem Typenschild zur
|
||
// Verfuegung, also fragen wir zuerst danach. Beispiel MUFF21VLQS: 10x Score 100
|
||
// ueber AEG, Electrolux, Frigidaire, Gibson.
|
||
if (count($results) > 1) {
|
||
$top_score = (int)$results[0]['score'];
|
||
$topgroup = array_filter($results, function ($d) use ($top_score) {
|
||
return (int)$d['score'] === $top_score;
|
||
});
|
||
$brands = [];
|
||
$models = [];
|
||
foreach ($topgroup as $d) {
|
||
$m = trim($d['marke'] ?? '');
|
||
if ($m !== '') { $brands[$m] = ($brands[$m] ?? 0) + 1; }
|
||
$models[mb_strtolower(trim($d['modell_bezeichnung'] ?? ''))] = true;
|
||
}
|
||
// Nur fragen, wenn es wirklich EIN Modell bei MEHREREN Marken ist.
|
||
if (count($brands) > 1 && count($models) === 1) {
|
||
arsort($brands);
|
||
$v2_brandpick = $brands;
|
||
}
|
||
|
||
// --- Stufe 1b: Varianten-Auswahl (BSH-Bauzustaende) ---
|
||
// SIEMENS/BOSCH haengen "/01", "/02" ... an die Typnummer in nr. Laesst der Kunde
|
||
// das Suffix weg ("WT46W560BY"), kommen alle Bauzustaende mit gleichem Score
|
||
// zurueck und unterscheiden sich NUR im Produktionszeitraum. Dann fragen wir
|
||
// danach, statt zehn optisch identische Zeilen zu zeigen.
|
||
if (!$v2_brandpick) {
|
||
$basis = [];
|
||
foreach ($topgroup as $d) {
|
||
$n = trim($d['nr'] ?? '');
|
||
$basis[($p = strpos($n, '/')) !== false ? substr($n, 0, $p) : $n] = true;
|
||
}
|
||
if (count($basis) === 1 && count($topgroup) > 1 && count($brands) <= 1) {
|
||
$v2_variant_base = (string)array_key_first($basis);
|
||
// Nur wenn die Treffer wirklich Suffix-Varianten sind, nicht zufaellig
|
||
// gleichnamige Geraete: mindestens einer muss ein "/" tragen.
|
||
$has_suffix = false;
|
||
foreach ($topgroup as $d) { if (strpos((string)($d['nr'] ?? ''), '/') !== false) { $has_suffix = true; break; } }
|
||
if ($has_suffix) {
|
||
$det_batch = $db->getGeraetDetailsBatch(array_column($topgroup, 'geraet_id'));
|
||
foreach ($topgroup as $d) {
|
||
$det = $det_batch[$d['geraet_id']] ?? [];
|
||
$v2_variants[] = [
|
||
'nr' => (string)($d['nr'] ?? ''),
|
||
'id' => $d['geraet_id'],
|
||
'von' => $det['produktionsstart'] ?? '',
|
||
'bis' => $det['produktionsende'] ?? '',
|
||
'typ_de'=> $det['typ_de'] ?? '',
|
||
'marke' => $d['marke'] ?? '',
|
||
];
|
||
}
|
||
usort($v2_variants, function ($a, $b) { return strcmp($a['nr'], $b['nr']); });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Stufe 2: gibt es EINEN klaren Top-Treffer? ---
|
||
// Nur dann darf man "Das ist dein Geraet" behaupten. Bedingung: Top-Score >= 95
|
||
// UND mindestens 10 Punkte Abstand zum Zweiten. Beispiel "Lavatherm 400": 100 vs. 75.
|
||
// Entfaellt, solange die Hersteller-Frage offen ist.
|
||
if (!$v2_brandpick && !$v2_variants && count($results) > 0) {
|
||
$v2_top_score_raw = (int)$results[0]['score'];
|
||
$v2_second_raw = isset($results[1]) ? (int)$results[1]['score'] : 0;
|
||
if ($v2_top_score_raw >= 95 && ($v2_top_score_raw - $v2_second_raw) >= 10) {
|
||
$v2_guided = true;
|
||
$v2_top_detail = $db->getGeraetDetails($results[0]['geraet_id']);
|
||
}
|
||
}
|
||
|
||
// --- Ersatzteile + Shop-Artikel NUR fuer den Top-Treffer vorladen ---
|
||
// Bewusst erst hier, nach Hersteller-Filter und Stufen-Ermittlung: vorher stand an
|
||
// Position 0 evtl. ein anderes Geraet. Die uebrigen Kacheln sind eingeklappt und
|
||
// laden per AJAX nach (siehe Endpoint am Dateianfang) -- sie brauchen bis dahin nur
|
||
// die Anzahl, die ein einzelner Batch-Query liefert statt 20 Einzelabfragen.
|
||
if (isset($results[0])) {
|
||
$results[0]['ersatzteile'] = $db->getErsatzteileForGeraet($results[0]['geraet_id']);
|
||
if ($show_shop_items) {
|
||
$results[0]['shop_items'] = $db->getAvailableItemsForGeraet($results[0]['geraet_id'], 200);
|
||
}
|
||
}
|
||
$v2_partcounts = count($results) > 1
|
||
? $db->getErsatzteilCountsBatch(array_column($results, 'geraet_id'))
|
||
: [];
|
||
|
||
// --- Shop-Links fuer die Ersatzteil-Chips ---
|
||
// Die Chips kommen aus PostgreSQL und kennen nur navision_id. Damit sie -- wie die
|
||
// Artikel weiter unten -- in den Shop verlinken, wird einmalig gesammelt aufgeloest.
|
||
// Nur die tatsaechlich gerenderten (max. 10 je Geraet), sonst waeren es Tausende.
|
||
// Nur fuer den Top-Treffer: dessen Ersatzteil-Chips sind die einzigen, die ohne
|
||
// Lazy-Load gerendert werden. Vorher sammelte das die navision_ids ALLER 20 Geraete
|
||
// (bis 200 Stueck) fuer einen IN-Query, dessen Ergebnis niemand brauchte.
|
||
if (isset($results[0])) {
|
||
$nav_ids = [];
|
||
foreach (array_slice($results[0]['ersatzteile'] ?? [], 0, 10) as $p) {
|
||
if (!empty($p['navision_id'])) { $nav_ids[] = $p['navision_id']; }
|
||
}
|
||
if ($nav_ids) { $v2_shopmap = $db->getShopItemsByNavisionIds($nav_ids); }
|
||
}
|
||
}
|
||
} catch (Exception $e) {
|
||
$error_message = $e->getMessage();
|
||
}
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Krempl PostgreSQL Search Test</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1600px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.split-results {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 24px;
|
||
margin-top: 20px;
|
||
}
|
||
.split-results > div {
|
||
min-width: 0;
|
||
}
|
||
.split-results h2 {
|
||
font-size: 16px;
|
||
color: #444;
|
||
background: #f4f4f4;
|
||
padding: 10px 14px;
|
||
border-left: 4px solid #4CAF50;
|
||
margin-bottom: 14px;
|
||
}
|
||
.split-results .right-pane h2 {
|
||
border-left-color: #1976D2;
|
||
}
|
||
.item-card {
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
padding: 10px 12px;
|
||
background: #fafafa;
|
||
font-size: 13px;
|
||
}
|
||
.item-card .item-name {
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
.item-card .item-meta {
|
||
color: #666;
|
||
font-size: 12px;
|
||
}
|
||
.item-card .item-meta strong {color:#1976D2;}
|
||
.inventory-positive {color:#2e7d32;font-weight:bold;}
|
||
.inventory-zero {color:#aaa;}
|
||
.inventory-null {color:#aaa;font-style:italic;}
|
||
@media (max-width: 900px) {
|
||
.split-results { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
h1 {
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.search-box {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 12px 15px;
|
||
font-size: 16px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 4px;
|
||
transition: border-color 0.3s;
|
||
}
|
||
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: #4CAF50;
|
||
}
|
||
|
||
.search-btn {
|
||
padding: 10px 20px;
|
||
background: #4CAF50;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: background 0.3s;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.search-btn:hover {
|
||
background: #45a049;
|
||
}
|
||
|
||
#shopItemsToggle {
|
||
cursor: pointer;
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
.search-info {
|
||
color: #666;
|
||
margin-bottom: 20px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.device-card {
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
background: #fafafa;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.device-card.collapsed .ersatzteile-section {
|
||
display: none;
|
||
}
|
||
|
||
.device-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: start;
|
||
padding: 15px;
|
||
margin-bottom: 0;
|
||
border-bottom: 2px solid #e0e0e0;
|
||
}
|
||
|
||
.score-badge {
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
user-select: none;
|
||
}
|
||
|
||
.score-badge:hover {
|
||
opacity: 0.9;
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
.toggle-arrow {
|
||
display: inline-block;
|
||
margin-left: 8px;
|
||
transition: transform 0.3s;
|
||
font-size: 10px;
|
||
}
|
||
|
||
.device-card.collapsed .toggle-arrow {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
.device-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.device-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.device-title a {
|
||
color: #333;
|
||
text-decoration: none;
|
||
transition: color 0.2s;
|
||
}
|
||
|
||
.device-title a:hover {
|
||
color: #4CAF50;
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.device-details {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.device-details span {
|
||
display: inline-block;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.score-badge {
|
||
display: inline-block;
|
||
padding: 5px 12px;
|
||
border-radius: 20px;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.score-100 { background: #4CAF50; color: white; }
|
||
.score-95 { background: #66BB6A; color: white; }
|
||
.score-90 { background: #81C784; color: white; }
|
||
.score-80 { background: #FFC107; color: white; }
|
||
.score-75 { background: #FFD54F; color: #333; }
|
||
.score-60 { background: #FFE082; color: #333; }
|
||
.score-default { background: #BDBDBD; color: white; }
|
||
|
||
.ersatzteile-section {
|
||
padding: 15px;
|
||
}
|
||
|
||
.ersatzteile-title {
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
margin-top: 10px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.ersatzteile-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
background: white;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.ersatzteile-table th {
|
||
background: #f0f0f0;
|
||
padding: 8px;
|
||
text-align: left;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
}
|
||
|
||
.ersatzteile-table td {
|
||
padding: 8px;
|
||
border-top: 1px solid #e0e0e0;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.ersatzteile-table tr:hover {
|
||
background: #f9f9f9;
|
||
}
|
||
|
||
.navision-id {
|
||
font-weight: bold;
|
||
color: #1976D2;
|
||
}
|
||
|
||
.no-results {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #999;
|
||
}
|
||
|
||
.match-type {
|
||
display: inline-block;
|
||
font-size: 11px;
|
||
padding: 2px 6px;
|
||
background: #e0e0e0;
|
||
border-radius: 3px;
|
||
color: #666;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.error-message {
|
||
background: #ffebee;
|
||
border: 1px solid #ef5350;
|
||
color: #c62828;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.error-message strong {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Krempl PostgreSQL Search Test</h1>
|
||
|
||
<div class="search-box">
|
||
<form method="GET" action="" id="searchForm">
|
||
<input
|
||
type="text"
|
||
name="q"
|
||
class="search-input"
|
||
placeholder="Gerätesuche (z.B. AWB 921 PH, ZCS 2100B, 2100B)"
|
||
value="<?php echo htmlspecialchars($search_term); ?>"
|
||
autofocus
|
||
/>
|
||
<div style="margin-top: 10px; display: flex; align-items: center; gap: 15px;">
|
||
<button type="submit" class="search-btn">Suchen</button>
|
||
</div>
|
||
<?php // "Nur verfuegbare Shop-Artikel" ist der Ist-Zustand und nicht mehr
|
||
// umschaltbar in der UI. Zum Debuggen weiterhin per ?shop_items=0. ?>
|
||
</form>
|
||
</div>
|
||
|
||
<?php if ($error_message): ?>
|
||
<div class="error-message">
|
||
<strong>Fehler bei der Datenbankverbindung:</strong>
|
||
<?php echo htmlspecialchars($error_message); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($search_term !== '' && !$error_message): ?>
|
||
<?php
|
||
// ==================================================================
|
||
// GEFUEHRTE ERGEBNIS-ANSICHT
|
||
// Dreistufiger Trichter: Hersteller -> Bauzustand -> Bestaetigung.
|
||
// ==================================================================
|
||
// Treffer in "stark" (>=75) und "schwach" (<75) teilen
|
||
$v2_strong = []; $v2_weak = [];
|
||
foreach ($results as $d) { ((int)$d['score'] >= 75) ? $v2_strong[] = $d : $v2_weak[] = $d; }
|
||
// Artikel klassifizieren: kaufbar / ausgelaufen-mit-Nachfolger / tot
|
||
$g_buy = []; $g_succ = []; $g_dead = [];
|
||
foreach ($item_results as $it) {
|
||
$it['_nml'] = v2_nml_full($it);
|
||
if (!$it['_nml']['is_nml']) $g_buy[] = $it; // regulär
|
||
elseif ($it['_nml']['nachfolger']) $g_succ[] = $it; // ausgelaufen, Nachfolger da
|
||
else $g_dead[] = $it; // ausgelaufen, kein Ersatz
|
||
}
|
||
$v2_top_score = isset($results[0]) ? (int)$results[0]['score'] : 0;
|
||
// Intent-Einordnung
|
||
// Der Leerfall MUSS zuerst geprueft werden: sonst greift der else-Zweig und verspricht
|
||
// "Mehrere moegliche Treffer", obwohl 0 Geraete und 0 Artikel gefunden wurden
|
||
// (z.B. bei einem Tippfehler wie "spannrolle boisch").
|
||
$v2_leer = (count($results) === 0 && count($item_results) === 0);
|
||
if ($v2_leer) $v2_headline = 'Dazu haben wir nichts gefunden.';
|
||
elseif ($v2_top_score >= 90) $v2_headline = 'Wir haben dein Gerät gefunden.';
|
||
elseif (count($v2_strong) > 0) $v2_headline = 'Das könnte dein Gerät sein:';
|
||
elseif (count($item_results)) $v2_headline = 'Passende Artikel gefunden:';
|
||
else $v2_headline = 'Mehrere mögliche Treffer – grenz gern ein.';
|
||
?>
|
||
<style>
|
||
.v2{margin-top:24px;font-size:15px}
|
||
.v2 .v2-flag{display:inline-block;background:#111;color:#fff;font-size:11px;font-weight:700;
|
||
letter-spacing:.5px;padding:3px 10px;border-radius:4px;text-transform:uppercase}
|
||
.v2 .v2-head{font-size:22px;font-weight:700;color:#222;margin:14px 0 4px}
|
||
.v2 .v2-sub{color:#777;font-size:13px;margin-bottom:22px}
|
||
.v2-dev{border:1px solid #e3e3e3;border-radius:10px;margin-bottom:16px;overflow:hidden;
|
||
box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||
.v2-dev-top{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;
|
||
padding:16px 18px;background:#fff}
|
||
.v2-dev-name{font-size:18px;font-weight:700;color:#1a1a1a;line-height:1.3}
|
||
.v2-dev-meta{color:#666;font-size:13px;margin-top:5px}
|
||
.v2-dev-meta b{color:#444}
|
||
.v2-conf{white-space:nowrap;font-size:12px;font-weight:700;padding:6px 12px;border-radius:20px}
|
||
.v2-parts{padding:14px 18px;border-top:1px solid #f0f0f0;background:#fcfcfc}
|
||
.v2-parts h4{font-size:13px;color:#555;margin-bottom:9px;font-weight:600}
|
||
.v2-chip{display:inline-block;background:#fff;border:1px solid #e0e0e0;border-radius:16px;
|
||
padding:4px 11px;margin:0 5px 6px 0;font-size:12px;color:#444}
|
||
.v2-chip b{color:#1565c0}
|
||
.v2-buy{background:#e8f5e9;border-color:#c8e6c9}
|
||
/* Warengruppen-Ueberschrift in der Artikel-Liste */
|
||
.v2-wg{font-size:12px;font-weight:700;color:#37474f;text-transform:uppercase;letter-spacing:.5px;
|
||
margin:16px 0 8px;padding-bottom:5px;border-bottom:1px solid #e0e6e9}
|
||
.v2-wg:first-of-type{margin-top:12px}
|
||
.v2-wg span{display:inline-block;background:#eceff1;color:#546e7a;border-radius:10px;
|
||
padding:1px 7px;font-size:11px;margin-left:6px;letter-spacing:0}
|
||
.v2-wg-nml{color:#a1887f;border-bottom-color:#efebe9}
|
||
.v2-wg-nml span{background:#efebe9;color:#8d6e63}
|
||
/* Artikel-ZEILEN (ersetzen die Chip-Blasen bei den kaufbaren Teilen) */
|
||
.v2-rows{margin-top:14px;display:flex;flex-direction:column;gap:6px}
|
||
.v2-row{display:flex;align-items:center;gap:12px;padding:9px 12px;border:1px solid #e8e8e8;
|
||
border-radius:8px;background:#fff;text-decoration:none;color:inherit}
|
||
a.v2-row{transition:border-color .12s,box-shadow .12s}
|
||
a.v2-row:hover{border-color:#2e7d32;box-shadow:0 1px 5px rgba(46,125,50,.15)}
|
||
.v2-row-nolink{background:#fbfbfb}
|
||
.v2-row-img{width:42px;height:42px;flex:0 0 42px;object-fit:contain;background:#fafafa;
|
||
border:1px solid #eee;border-radius:6px}
|
||
.v2-row-noimg{display:flex;align-items:center;justify-content:center;color:#ccc;font-size:15px}
|
||
.v2-row-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
||
.v2-row-name{font-size:14px;font-weight:600;color:#222;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.v2-row-nr{font-size:12px;color:#888}
|
||
.v2-row-price{flex:0 0 auto;text-align:right;white-space:nowrap;font-size:14px}
|
||
.v2-row-price b{display:block;color:#1a1a1a}
|
||
.v2-row-price s{font-size:11px;color:#aaa}
|
||
.v2-row-noprice{font-size:12px;color:#aaa}
|
||
/* Lazy-Nachladen der Artikel einer eingeklappten Geraete-Kachel */
|
||
.v2-lazy>summary{cursor:pointer;color:#1565c0;font-size:13px;font-weight:600;padding:5px 2px;
|
||
list-style:none}
|
||
.v2-lazy>summary::-webkit-details-marker{display:none}
|
||
.v2-lazy>summary::before{content:"▸ ";color:#90a4ae}
|
||
.v2-lazy[open]>summary::before{content:"▾ "}
|
||
.v2-lazy>summary:hover{text-decoration:underline}
|
||
.v2-lazy-hint{font-size:12px;color:#9e9e9e;padding:2px 2px 0}
|
||
.v2-lazy-msg{font-size:13px;color:#78909c;padding:10px 2px}
|
||
.v2-spin{display:inline-block;width:13px;height:13px;border:2px solid #cfd8dc;
|
||
border-top-color:#1565c0;border-radius:50%;animation:v2spin .7s linear infinite;
|
||
vertical-align:-2px;margin-right:8px}
|
||
@keyframes v2spin{to{transform:rotate(360deg)}}
|
||
.v2-rowmore{margin-top:8px}
|
||
.v2-rowmore>summary{cursor:pointer;color:#1565c0;font-size:13px;font-weight:600;padding:6px 2px;
|
||
list-style:none}
|
||
.v2-rowmore>summary::-webkit-details-marker{display:none}
|
||
.v2-rowmore>summary::before{content:"▸ ";color:#90a4ae}
|
||
.v2-rowmore[open]>summary::before{content:"▾ "}
|
||
.v2-rowmore>summary:hover{text-decoration:underline}
|
||
/* verlinkte Chips: nur die mit Shop-Artikel sind klickbar */
|
||
a.v2-chip-link{text-decoration:none;transition:border-color .12s,box-shadow .12s}
|
||
a.v2-chip-link:hover{border-color:#1565c0;box-shadow:0 1px 4px rgba(21,101,192,.18)}
|
||
a.v2-chip-link.v2-buy:hover{border-color:#2e7d32;box-shadow:0 1px 4px rgba(46,125,50,.18)}
|
||
.v2-more{margin:6px 0 22px}
|
||
.v2-more summary{cursor:pointer;color:#1565c0;font-size:14px;padding:8px 0}
|
||
.v2-shop{margin-top:30px}
|
||
.v2-shop h3{font-size:16px;color:#333;margin-bottom:12px;border-left:4px solid #1976D2;padding-left:10px}
|
||
.v2-item{display:flex;justify-content:space-between;align-items:center;gap:14px;
|
||
border:1px solid #eee;border-radius:8px;padding:12px 15px;margin-bottom:8px;background:#fff}
|
||
.v2-item-name{font-weight:600;color:#222}
|
||
.v2-item-meta{color:#888;font-size:12px;margin-top:3px}
|
||
.v2-stock{white-space:nowrap;font-size:13px;font-weight:700}
|
||
.v2-in{color:#2e7d32}.v2-out{color:#c62828}.v2-na{color:#aaa;font-style:italic;font-weight:400}
|
||
.v2-cart{background:#4CAF50;color:#fff;border:none;border-radius:6px;padding:8px 14px;
|
||
font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap}
|
||
.v2-cart:disabled{background:#ccc;cursor:not-allowed}
|
||
.v2-hint{background:#fffdf3;border:1px dashed #e6d9a8;border-radius:8px;padding:10px 14px;
|
||
font-size:12px;color:#8a7a3a;margin-top:20px}
|
||
/* --- Gefuehrte Bestaetigung: EIN klarer Top-Treffer --- */
|
||
.v2-cf{border:2px solid #2e7d32;border-radius:12px;padding:22px 24px;margin:4px 0 18px;
|
||
background:linear-gradient(180deg,#f4fbf4 0%,#fff 60%);box-shadow:0 2px 8px rgba(46,125,50,.08)}
|
||
.v2-cf-q{font-size:13px;font-weight:700;color:#2e7d32;text-transform:uppercase;letter-spacing:.6px}
|
||
.v2-cf-name{font-size:26px;font-weight:700;color:#1a1a1a;line-height:1.25;margin:8px 0 2px}
|
||
.v2-cf-marke{font-size:15px;color:#555;font-weight:600}
|
||
.v2-cf-facts{display:flex;flex-wrap:wrap;gap:8px;margin:16px 0 4px}
|
||
.v2-cf-fact{background:#fff;border:1px solid #dfe8df;border-radius:8px;padding:8px 13px;font-size:13px;color:#33691e}
|
||
.v2-cf-fact b{color:#1b5e20;display:block;font-size:11px;text-transform:uppercase;
|
||
letter-spacing:.4px;color:#7a8f7a;font-weight:700;margin-bottom:2px}
|
||
.v2-cf-act{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:20px;
|
||
padding-top:16px;border-top:1px solid #e4efe4}
|
||
.v2-cf-yes{background:#2e7d32;color:#fff;border:none;border-radius:8px;padding:12px 22px;
|
||
font-size:15px;font-weight:700;text-decoration:none;display:inline-block}
|
||
.v2-cf-no{color:#1565c0;font-size:14px;text-decoration:underline;cursor:pointer;background:none;border:none;padding:0}
|
||
.v2-alt{margin:0 0 22px}
|
||
.v2-alt>summary{cursor:pointer;color:#1565c0;font-size:14px;padding:10px 0;font-weight:600}
|
||
.v2-alt[open]>summary{margin-bottom:10px}
|
||
/* Hersteller-Buendelung innerhalb der Alternativen */
|
||
.v2-mgroup{font-size:13px;font-weight:700;color:#455a64;text-transform:uppercase;letter-spacing:.6px;
|
||
margin:18px 0 10px;padding-bottom:6px;border-bottom:2px solid #eceff1}
|
||
.v2-mgroup:first-of-type{margin-top:6px}
|
||
.v2-mgroup span{display:inline-block;background:#eceff1;color:#546e7a;border-radius:10px;
|
||
padding:1px 8px;font-size:11px;margin-left:6px;letter-spacing:0}
|
||
/* Stufe 1: Hersteller-Rueckfrage bei Multi-Marken-Modellen */
|
||
.v2-cf-brand{border-color:#1565c0;background:linear-gradient(180deg,#f2f7fd 0%,#fff 60%);
|
||
box-shadow:0 2px 8px rgba(21,101,192,.08)}
|
||
.v2-cf-brand .v2-cf-q{color:#1565c0}
|
||
.v2-brands{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0 4px}
|
||
.v2-brand{display:inline-flex;align-items:center;gap:8px;background:#fff;border:2px solid #cfe0f5;
|
||
border-radius:10px;padding:12px 18px;font-size:15px;font-weight:700;color:#0d47a1;
|
||
text-decoration:none;transition:border-color .12s,background .12s}
|
||
.v2-brand:hover{border-color:#1565c0;background:#f5faff}
|
||
.v2-brand span{background:#e3eefb;color:#1565c0;border-radius:10px;padding:1px 8px;
|
||
font-size:12px;font-weight:600}
|
||
.v2-filter{background:#eef4fb;border:1px solid #cfe0f5;border-radius:8px;padding:9px 14px;
|
||
font-size:13px;color:#0d47a1;margin:0 0 16px;display:flex;gap:12px;align-items:center;flex-wrap:wrap}
|
||
.v2-filter a{color:#1565c0}
|
||
/* Stufe 1b: BSH-Bauzustaende /01 /02 ... nur durch den Zeitraum unterscheidbar */
|
||
.v2-cf-var{border-color:#6a1b9a;background:linear-gradient(180deg,#f9f4fc 0%,#fff 60%);
|
||
box-shadow:0 2px 8px rgba(106,27,154,.08)}
|
||
.v2-cf-var .v2-cf-q{color:#6a1b9a}
|
||
.v2-vars{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px;margin-top:18px}
|
||
.v2-var{display:flex;align-items:center;gap:10px;background:#fff;border:2px solid #e6d9f0;
|
||
border-radius:10px;padding:11px 15px;text-decoration:none;transition:border-color .12s,background .12s}
|
||
.v2-var:hover{border-color:#6a1b9a;background:#fcf9fe}
|
||
.v2-var b{font-size:16px;font-weight:700;color:#4a148c;min-width:38px}
|
||
.v2-var span{font-size:13px;color:#666}
|
||
</style>
|
||
|
||
<div class="v2">
|
||
<?php
|
||
// Kaufbare Artikel richtig zaehlen: count($g_buy) allein war falsch -- $g_buy kommt
|
||
// aus der Artikel-TEXTsuche, und bei einer Geraetenummer wie "WT46W560BY/01" findet
|
||
// die in items.name naturgemaess nichts (zeigte 0 statt 8). Was real erreichbar ist,
|
||
// sind die Shop-Artikel des Top-Geraets aus der Geraetezuordnung PLUS die
|
||
// Textsuche-Treffer -- ueber items.id zusammengefuehrt, damit nichts doppelt zaehlt.
|
||
$v2_aids = [];
|
||
foreach (($results[0]['shop_items'] ?? []) as $si) { if (!empty($si['id'])) { $v2_aids[$si['id']] = true; } }
|
||
foreach ($g_buy as $it) { if (!empty($it['id'])) { $v2_aids[$it['id']] = true; } }
|
||
$v2_artikel_n = count($v2_aids);
|
||
?>
|
||
<div class="v2-head"><?php echo htmlspecialchars($v2_headline); ?></div>
|
||
<?php if ($v2_leer): ?>
|
||
<?php // Keine Zaehlzeile mit "0 · 0" -- die sagt nichts und liest sich wie ein Fehler. ?>
|
||
<div class="v2-sub">
|
||
Prüf bitte die Schreibweise, oder gib die Typnummer vom Typenschild ein
|
||
(z.B. <b>WT46W560BY</b> oder <b>Lavatherm 400</b>).
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="v2-sub">
|
||
<?php echo count($v2_strong); ?> <?php echo count($v2_strong) === 1 ? 'passendes Gerät' : 'passende Geräte'; ?>
|
||
· <?php echo $v2_artikel_n; ?> kaufbare Artikel
|
||
<?php if (count($g_succ)) echo ' · '.count($g_succ).' mit Nachfolger'; ?>
|
||
<?php if (count($g_dead)) echo ' · '.count($g_dead).' ausgelaufen'; ?>
|
||
<?php if (count($v2_weak)) echo ' · '.count($v2_weak).' weitere mögliche Geräte'; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
// Query-String-Bausteine fuer die Hersteller-Links
|
||
$v2_qs_shop = $show_shop_items ? '' : '&shop_items=0';
|
||
$v2_qs_base = '?q=' . urlencode($search_term) . $v2_qs_shop;
|
||
?>
|
||
|
||
<?php if ($v2_marke !== ''): ?>
|
||
<div class="v2-filter">
|
||
<span>Hersteller: <b><?php echo htmlspecialchars($v2_marke); ?></b></span>
|
||
<a href="<?php echo $v2_qs_base; ?>">↺ alle Hersteller zeigen</a>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($v2_brandpick): ?>
|
||
<div class="v2-cf v2-cf-brand">
|
||
<div class="v2-cf-q">Mehrere Hersteller führen dieses Modell</div>
|
||
<div class="v2-cf-name"><?php echo htmlspecialchars(v2_gname($results[0])); ?></div>
|
||
<div class="v2-cf-marke">Welcher Hersteller steht auf deinem Gerät?</div>
|
||
<div class="v2-brands">
|
||
<?php foreach ($v2_brandpick as $m => $n): ?>
|
||
<a class="v2-brand" href="<?php echo $v2_qs_base; ?>&marke=<?php echo urlencode($m); ?>">
|
||
<?php echo htmlspecialchars($m); ?><span><?php echo (int)$n; ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<div style="font-size:12px;color:#78909c;margin-top:14px">
|
||
Der Hersteller steht meist auf dem Typenschild oder als Logo auf der Blende.
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($v2_variants): ?>
|
||
<div class="v2-cf v2-cf-var">
|
||
<div class="v2-cf-q">Dieses Modell gibt es in mehreren Bauzuständen</div>
|
||
<div class="v2-cf-name"><?php echo htmlspecialchars($v2_variant_base); ?></div>
|
||
<div class="v2-cf-marke">
|
||
<?php
|
||
$vm = trim($v2_variants[0]['marke'] ?? '');
|
||
$vt = trim($v2_variants[0]['typ_de'] ?? '');
|
||
echo htmlspecialchars(trim($vm.' '.$vt));
|
||
?>
|
||
· <?php echo count($v2_variants); ?> Varianten
|
||
</div>
|
||
<div class="v2-cf-marke" style="margin-top:14px;font-weight:400;color:#666">
|
||
Wann wurde dein Gerät gebaut? Die Nummer auf dem Typenschild endet auf
|
||
<b>/01</b>, <b>/02</b> … – wenn du sie findest, ist das der schnellste Weg.
|
||
</div>
|
||
<div class="v2-vars">
|
||
<?php foreach ($v2_variants as $v):
|
||
$von = v2_monat($v['von']); $bis = v2_monat($v['bis']);
|
||
if ($von && $bis && $von !== $bis) $zr = $von.' – '.$bis;
|
||
elseif ($von) $zr = 'ab '.$von;
|
||
elseif ($bis) $zr = 'bis '.$bis;
|
||
else $zr = 'Zeitraum unbekannt';
|
||
$suf = (($p = strpos($v['nr'], '/')) !== false) ? substr($v['nr'], $p) : '–';
|
||
?>
|
||
<a class="v2-var" href="?q=<?php echo urlencode($v['nr']); ?><?php echo $show_shop_items ? '' : '&shop_items=0'; ?>">
|
||
<b><?php echo htmlspecialchars($suf); ?></b>
|
||
<span><?php echo htmlspecialchars($zr); ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($v2_guided && $v2_top_detail):
|
||
$det = $v2_top_detail;
|
||
// produktionsstart/-ende liegen als YYYYMMDD-String vor (z.B. "19790101")
|
||
$js = v2_jahr4($det['produktionsstart'] ?? '');
|
||
$je = v2_jahr4($det['produktionsende'] ?? '');
|
||
if ($js && $je && $js !== $je) $v2_zeit = $js.'–'.$je;
|
||
elseif ($js) $v2_zeit = 'ab '.$js;
|
||
elseif ($je) $v2_zeit = 'bis '.$je;
|
||
else $v2_zeit = '';
|
||
// Bauart aus zusatz + Warengruppentexten. zusatz zuerst, weil es die
|
||
// aussagekraeftigste Angabe ist ("Kondenstrockner") und bei 923.555 von
|
||
// 1.675.828 Geraeten gefuellt ist -- deutlich oefter als wgtext2.
|
||
// "-" ist in diesen Feldern ein Platzhalter und KEIN Wert: ohne diese Pruefung
|
||
// stand bei WT46W560BY/01 wortwoertlich "- · -" als Bauart in der Karte.
|
||
$v2_wg = [];
|
||
foreach (['zusatz', 'wgtext1', 'wgtext2'] as $v2_wgk) {
|
||
$v2_wgv = trim((string)($det[$v2_wgk] ?? ''));
|
||
if ($v2_wgv !== '' && $v2_wgv !== '-') { $v2_wg[] = $v2_wgv; }
|
||
}
|
||
$v2_wg = array_values(array_unique($v2_wg));
|
||
$v2_nparts = count($results[0]['ersatzteile'] ?? []);
|
||
?>
|
||
<div class="v2-cf">
|
||
<div class="v2-cf-q">Ist das dein Gerät?</div>
|
||
<div class="v2-cf-name"><?php echo htmlspecialchars(v2_gname($det)); ?></div>
|
||
<div class="v2-cf-marke"><?php echo htmlspecialchars($det['marke']); ?></div>
|
||
<div class="v2-cf-facts">
|
||
<?php if (!empty($det['typ_de'])): ?>
|
||
<div class="v2-cf-fact"><b>Gerätetyp</b><?php echo htmlspecialchars($det['typ_de']); ?></div>
|
||
<?php endif; ?>
|
||
<?php if ($v2_wg): ?>
|
||
<div class="v2-cf-fact"><b>Bauart</b><?php echo htmlspecialchars(implode(' · ', $v2_wg)); ?></div>
|
||
<?php endif; ?>
|
||
<?php if ($v2_zeit): ?>
|
||
<div class="v2-cf-fact"><b>Baujahr</b><?php echo htmlspecialchars($v2_zeit); ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php
|
||
// Ersatzteile des Top-Geraets direkt hier -- der alte v2-dev-Block fuer
|
||
// denselben Treffer entfaellt dadurch (war doppelt).
|
||
$v2_top_parts = $results[0]['ersatzteile'] ?? [];
|
||
$v2_top_buy = $results[0]['shop_items'] ?? null;
|
||
?>
|
||
<?php if ($v2_top_buy && count($v2_top_buy)): ?>
|
||
<div class="v2-parts" style="background:transparent;border-top:1px solid #e4efe4;margin-top:16px;padding:14px 0 0">
|
||
<h4>Verfügbare Artikel für dieses Gerät (<?php echo count($v2_top_buy); ?>):</h4>
|
||
<?php echo v2_artikel_liste($v2_top_buy); ?>
|
||
</div>
|
||
<?php elseif (count($v2_top_parts)): ?>
|
||
<div class="v2-parts" style="background:transparent;border-top:1px solid #e4efe4;margin-top:16px;padding:14px 0 0">
|
||
<h4><?php echo count($v2_top_parts); ?> passende Ersatzteile — Auswahl:</h4>
|
||
<?php foreach (array_slice($v2_top_parts,0,10) as $p): ?>
|
||
<?php echo v2_chip($p['originalnummer'], trim($p['marke'] ?? ''),
|
||
$v2_shopmap[trim($p['navision_id'] ?? '')]['id'] ?? null); ?>
|
||
<?php endforeach; ?>
|
||
<?php if (count($v2_top_parts) > 10): ?>
|
||
<span class="v2-chip" style="border-style:dashed;color:#999;">+<?php echo count($v2_top_parts)-10; ?> weitere</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="v2-cf-act">
|
||
<?php if (count($g_buy)): ?>
|
||
<a href="#v2-artikel" class="v2-cf-yes">Ja – Artikel im Shop zeigen (<?php echo count($g_buy); ?>)</a>
|
||
<?php endif; ?>
|
||
<?php // "Nicht mein Gerät" stand doppelt: hier UND als <summary> des
|
||
// Alternativen-Blocks direkt darunter. Das <summary> genuegt. ?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
// Gefuehrter Modus: der Top-Treffer steckt komplett in der Bestaetigungskarte
|
||
// (inkl. Ersatzteile), erscheint hier also NICHT mehr -- sonst stand er doppelt.
|
||
// Alle weiteren Treffer (starke ab Index 1 + schwache) liegen hinter einem Klick
|
||
// und werden dort nach Hersteller gebuendelt: dasselbe Modell laeuft oft unter
|
||
// mehreren Konzern- bzw. Versandhausmarken (Lavatherm: AEG, Quelle, Neckermann,
|
||
// Asko, Electrolux, ...). Marken mit den meisten Treffern zuerst.
|
||
$v2_alt_open = false;
|
||
$v2_last_mgroup = null;
|
||
$v2_alt_total = 0;
|
||
$v2_list = $v2_strong;
|
||
$v2_mcount = [];
|
||
if ($v2_guided && $v2_top_detail) {
|
||
$v2_rest = array_merge(array_slice($v2_strong, 1), $v2_weak);
|
||
$v2_alt_total = count($v2_rest);
|
||
$by_marke = [];
|
||
foreach ($v2_rest as $d) {
|
||
$m = (isset($d['marke']) && trim($d['marke']) !== '') ? trim($d['marke']) : 'ohne Herstellerangabe';
|
||
$by_marke[$m][] = $d;
|
||
}
|
||
uasort($by_marke, function ($a, $b) { return count($b) <=> count($a); });
|
||
$v2_list = [];
|
||
foreach ($by_marke as $m => $ds) {
|
||
$v2_mcount[$m] = count($ds);
|
||
foreach ($ds as $d) { $d['_mgroup'] = $m; $v2_list[] = $d; }
|
||
}
|
||
}
|
||
?>
|
||
<?php foreach ($v2_list as $i => $d):
|
||
list($clabel,$ccol,$cbg,$cicon) = v2_confidence($d['score']);
|
||
$parts = $d['ersatzteile'] ?? [];
|
||
$buy = $d['shop_items'] ?? null; // nur wenn shop_items-Toggle an
|
||
if (!$v2_alt_open && ($v2_guided && $v2_top_detail)) {
|
||
echo '<details class="v2-alt" id="v2-alternativen"><summary>Nicht mein Gerät – '
|
||
. $v2_alt_total . ' weitere Treffer anzeigen</summary>';
|
||
$v2_alt_open = true;
|
||
}
|
||
// Bei der Varianten-Karte stehen dieselben Geraete schon als Buttons oben --
|
||
// die ausfuehrliche Liste (mit Ersatzteilen) deshalb ebenfalls einklappen.
|
||
if (!$v2_alt_open && $v2_variants) {
|
||
echo '<details class="v2-alt" id="v2-alternativen"><summary>Alle '
|
||
. count($v2_variants) . ' Bauzustände mit Ersatzteilen anzeigen</summary>';
|
||
$v2_alt_open = true;
|
||
}
|
||
// Dito bei der Hersteller-Rueckfrage: erst die Marke klaeren, die Rohliste
|
||
// (oft 20 optisch identische Kacheln) gehoert dahinter.
|
||
if (!$v2_alt_open && $v2_brandpick) {
|
||
echo '<details class="v2-alt" id="v2-alternativen"><summary>Alle '
|
||
. count($results) . ' Treffer ungefiltert anzeigen</summary>';
|
||
$v2_alt_open = true;
|
||
}
|
||
if (isset($d['_mgroup']) && $d['_mgroup'] !== $v2_last_mgroup) {
|
||
$v2_last_mgroup = $d['_mgroup'];
|
||
echo '<h4 class="v2-mgroup">'.htmlspecialchars($v2_last_mgroup)
|
||
. ' <span>'.(int)($v2_mcount[$v2_last_mgroup] ?? 0).'</span></h4>';
|
||
}
|
||
?>
|
||
<div class="v2-dev">
|
||
<div class="v2-dev-top">
|
||
<div>
|
||
<div class="v2-dev-name"><?php echo htmlspecialchars(trim($d['marke'].' '.v2_gname($d))); ?></div>
|
||
<div class="v2-dev-meta">
|
||
<b>Typ:</b> <?php echo htmlspecialchars($d['typ']); ?>
|
||
· <b>Nr:</b> <?php echo htmlspecialchars($d['nr']); ?>
|
||
<?php if (!empty($d['produktionsstart']) || !empty($d['produktionsende'])): ?>
|
||
· <b>Baujahr:</b> <?php echo htmlspecialchars(trim(($d['produktionsstart']??'').'–'.($d['produktionsende']??''),'–')); ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<span class="v2-conf" style="color:<?php echo $ccol; ?>;background:<?php echo $cbg; ?>;">
|
||
<?php echo $cicon.' '.htmlspecialchars($clabel); ?>
|
||
</span>
|
||
</div>
|
||
|
||
<?php if ($buy && count($buy)): ?>
|
||
<div class="v2-parts">
|
||
<h4>Verfügbare Artikel für dieses Gerät (<?php echo count($buy); ?>):</h4>
|
||
<?php // in den Kacheln begrenzt, die aktive Karte zeigt alles ?>
|
||
<?php echo v2_artikel_liste($buy, 12, 12); ?>
|
||
</div>
|
||
<?php elseif ($show_shop_items): ?>
|
||
<?php // Diese Kachel ist eingeklappt -- ihre Shop-Artikel werden erst
|
||
// beim Aufklappen per AJAX geholt. Ein Vorladen fuer alle 20
|
||
// Treffer war die Ursache der langen Laufzeit. ?>
|
||
<div class="v2-parts">
|
||
<details class="v2-lazy" data-geraet="<?php echo (int)$d['geraet_id']; ?>">
|
||
<summary>Verfügbare Artikel im Shop anzeigen</summary>
|
||
<div class="v2-lazy-target"></div>
|
||
</details>
|
||
<?php // Anzahl aus dem Batch-COUNT -- die Liste selbst wird erst beim
|
||
// Aufklappen geholt, deshalb liegt hier kein $parts vor.
|
||
$pc = $v2_partcounts[$d['geraet_id']] ?? count($parts); ?>
|
||
<?php if ($pc): ?>
|
||
<div class="v2-lazy-hint"><?php echo $pc; ?> Ersatzteile laut Herstellerdaten hinterlegt</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php elseif (count($parts)): ?>
|
||
<div class="v2-parts">
|
||
<h4><?php echo count($parts); ?> passende Ersatzteile — Auswahl:</h4>
|
||
<?php foreach (array_slice($parts,0,10) as $p): ?>
|
||
<?php echo v2_chip($p['originalnummer'], trim($p['marke'] ?? ''),
|
||
$v2_shopmap[trim($p['navision_id'] ?? '')]['id'] ?? null); ?>
|
||
<?php endforeach; ?>
|
||
<?php if (count($parts) > 10): ?>
|
||
<span class="v2-chip" style="border-style:dashed;color:#999;">+<?php echo count($parts)-10; ?> weitere</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<?php
|
||
// Im gefuehrten Modus stecken die schwachen Treffer bereits in $v2_list (nach
|
||
// Hersteller gebuendelt), der separate Schwach-Block entfaellt dort komplett.
|
||
?>
|
||
<?php if (!$v2_guided && count($v2_weak)): ?>
|
||
<details class="v2-more">
|
||
<summary>Weitere mögliche Geräte anzeigen (<?php echo count($v2_weak); ?>) – schwächere Treffer</summary>
|
||
<?php foreach ($v2_weak as $d): list($clabel,$ccol,$cbg,$cicon)=v2_confidence($d['score']); ?>
|
||
<div class="v2-dev" style="opacity:.75">
|
||
<div class="v2-dev-top">
|
||
<div>
|
||
<div class="v2-dev-name" style="font-size:15px"><?php echo htmlspecialchars(trim($d['marke'].' '.v2_gname($d))); ?></div>
|
||
<div class="v2-dev-meta"><b>Typ:</b> <?php echo htmlspecialchars($d['typ']); ?> · <b>Nr:</b> <?php echo htmlspecialchars($d['nr']); ?></div>
|
||
</div>
|
||
<span class="v2-conf" style="color:<?php echo $ccol; ?>;background:<?php echo $cbg; ?>;"><?php echo $cicon.' '.htmlspecialchars($clabel); ?></span>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</details>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
// Alternativen-Block schliessen (nur wenn er ueberhaupt geoeffnet wurde).
|
||
if ($v2_alt_open) { echo '</details>'; }
|
||
?>
|
||
|
||
<?php $keep_shop = $show_shop_items ? '' : '&shop_items=0'; ?>
|
||
|
||
<?php if (count($g_buy)): ?>
|
||
<div class="v2-shop" id="v2-artikel">
|
||
<h3>🛒 Diese Artikel kannst du direkt kaufen</h3>
|
||
<?php foreach ($g_buy as $it):
|
||
// KEIN Lager-Kriterium mehr: die Kundin fuehrt im Shop kein Lager
|
||
// (kein einziger Artikel hat inventory > 0 -- 10.160x 0, 8.724x NULL).
|
||
// Bestellbar steuert sie ueber items.active, und searchItems() filtert
|
||
// bereits mit WHERE i.active = 1 -> alles hier ist bestellbar.
|
||
// Shop-Artikel und Bilder liegen nur auf LIVE -> absolute URLs
|
||
$url = tba_url('index.php?item_id=' . (int)$it['id']);
|
||
$img = !empty($it['default_image_file_name'])
|
||
? tba_url('web/intelectra_shop/images/article/'.rawurlencode($it['default_image_file_name']))
|
||
: '';
|
||
$pb = isset($it['price_brutto']) ? (float)$it['price_brutto'] : 0;
|
||
$bb = !empty($it['bargain_brutto']) ? (float)$it['bargain_brutto'] : 0;
|
||
?>
|
||
<div class="v2-item">
|
||
<a href="<?php echo $url; ?>" target="_blank" rel="noopener"
|
||
style="display:flex;align-items:center;gap:14px;min-width:0;text-decoration:none;color:inherit">
|
||
<?php if ($img): ?>
|
||
<img src="<?php echo $img; ?>" alt="" loading="lazy"
|
||
style="width:64px;height:64px;object-fit:contain;background:#fff;border:1px solid #eee;border-radius:6px;flex:0 0 64px"
|
||
onerror="this.style.display='none'">
|
||
<?php else: ?>
|
||
<div style="width:64px;height:64px;flex:0 0 64px;background:#f5f5f5;border:1px dashed #ddd;border-radius:6px;
|
||
display:flex;align-items:center;justify-content:center;color:#bbb;font-size:11px">kein Bild</div>
|
||
<?php endif; ?>
|
||
<div style="min-width:0">
|
||
<div class="v2-item-name" style="text-decoration:underline;text-decoration-color:#cfd8dc"><?php echo htmlspecialchars($it['_nml']['clean']); ?></div>
|
||
<div class="v2-item-meta">Art-Nr <?php echo htmlspecialchars($it['number']); ?></div>
|
||
</div>
|
||
</a>
|
||
<div style="text-align:right;flex:0 0 auto">
|
||
<div style="font-size:17px;font-weight:700;color:#1a1a1a;margin-bottom:2px">
|
||
<?php if ($bb > 0 && $pb > 0 && $bb < $pb): ?>
|
||
<span style="color:#c62828"><?php echo number_format($bb, 2, ',', '.'); ?> €</span>
|
||
<span style="font-size:12px;color:#999;text-decoration:line-through;font-weight:400"><?php echo number_format($pb, 2, ',', '.'); ?> €</span>
|
||
<?php elseif ($pb > 0): ?>
|
||
<?php echo number_format($pb, 2, ',', '.'); ?> €
|
||
<?php else: ?>
|
||
<span style="font-size:12px;color:#999;font-weight:400">Preis auf Anfrage</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php if ($pb > 0): ?><div style="font-size:10px;color:#aaa;margin-bottom:4px">inkl. MwSt.</div><?php endif; ?>
|
||
<a href="<?php echo $url; ?>" target="_blank" rel="noopener"
|
||
class="v2-cart" style="margin-top:6px;text-decoration:none;display:inline-block">
|
||
Zum Artikel
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (count($g_succ)): ?>
|
||
<div class="v2-shop">
|
||
<h3 style="border-left-color:#fb8c00">🔄 Ausgelaufen – aber es gibt einen Nachfolger</h3>
|
||
<?php foreach ($g_succ as $it):
|
||
$nf = $it['_nml']['nachfolger'];
|
||
// (nmle) = ausgelaufener Artikel MIT Nachfolger-Verweis. Der alte Artikel
|
||
// existiert im Shop weiterhin und darf deshalb anklickbar sein.
|
||
$url_alt = tba_url('index.php?item_id=' . (int)$it['id']);
|
||
?>
|
||
<div class="v2-item" style="background:#fff8f0;border-color:#ffe0b2">
|
||
<div>
|
||
<a href="<?php echo $url_alt; ?>" target="_blank" rel="noopener" style="text-decoration:none">
|
||
<div class="v2-item-name" style="color:#8a6d3b;text-decoration:line-through;text-decoration-color:#d9b38c">
|
||
<?php echo htmlspecialchars($it['_nml']['clean']); ?>
|
||
</div>
|
||
</a>
|
||
<div class="v2-item-meta">Art-Nr <?php echo htmlspecialchars($it['number']); ?> · nicht mehr lieferbar</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="font-size:12px;color:#e65100;font-weight:600;margin-bottom:6px">Nachfolger: <?php echo htmlspecialchars($nf); ?></div>
|
||
<a href="?q=<?php echo urlencode($nf).$keep_shop; ?>"
|
||
class="v2-cart" style="background:#fb8c00;text-decoration:none;display:inline-block">
|
||
→ Nachfolger anzeigen
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (count($g_dead)): ?>
|
||
<details class="v2-more" style="margin-top:20px">
|
||
<summary>Nicht mehr lieferbar, kein Ersatz (<?php echo count($g_dead); ?>) – ausgeblendet</summary>
|
||
<?php foreach ($g_dead as $it): ?>
|
||
<div class="v2-item" style="opacity:.55">
|
||
<div>
|
||
<div class="v2-item-name" style="text-decoration:line-through"><?php echo htmlspecialchars($it['_nml']['clean']); ?></div>
|
||
<div class="v2-item-meta">Art-Nr <?php echo htmlspecialchars($it['number']); ?></div>
|
||
</div>
|
||
<div class="v2-stock v2-out" style="white-space:nowrap">✕ entfällt</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</details>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!count($g_buy) && !count($g_succ) && !count($g_dead)): ?>
|
||
<!-- keine Artikel -->
|
||
<?php endif; ?>
|
||
|
||
</div>
|
||
|
||
<?php else: ?>
|
||
<div class="no-results">
|
||
Gib einen Suchbegriff ein (z.B. "AWB 921 PH", "ZCS 2100B", "Kohlefilter Bauknecht")
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<script>
|
||
/**
|
||
* Laedt die Shop-Artikel einer Geraete-Kachel erst beim Aufklappen nach.
|
||
*
|
||
* Vorher lud die Seite die Artikel fuer alle 20 Treffer vor, obwohl 19 eingeklappt sind.
|
||
* Das toggle-Event bubbelt nicht, deshalb wird es in der Capture-Phase abgefangen -- so
|
||
* greift es auch fuer Kacheln, die in einem uebergeordneten <details> stecken.
|
||
*/
|
||
document.addEventListener('toggle', function (e) {
|
||
var d = e.target;
|
||
if (!d.classList || !d.classList.contains('v2-lazy') || !d.open) return;
|
||
if (d.dataset.loaded === '1') return;
|
||
d.dataset.loaded = '1';
|
||
|
||
var box = d.querySelector('.v2-lazy-target');
|
||
if (!box) return;
|
||
box.innerHTML = '<div class="v2-lazy-msg"><span class="v2-spin"></span>Artikel werden geladen …</div>';
|
||
|
||
fetch('?ajax=items&geraet_id=' + encodeURIComponent(d.dataset.geraet), {
|
||
credentials: 'same-origin'
|
||
})
|
||
.then(function (r) {
|
||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||
return r.text();
|
||
})
|
||
.then(function (html) { box.innerHTML = html; })
|
||
.catch(function () {
|
||
// Flag zuruecksetzen, damit ein zweiter Versuch moeglich bleibt.
|
||
d.dataset.loaded = '';
|
||
box.innerHTML = '<div class="v2-lazy-msg">Artikel konnten nicht geladen werden – '
|
||
+ 'bitte zuklappen und erneut öffnen.</div>';
|
||
});
|
||
}, true);
|
||
</script>
|
||
</body>
|
||
</html>
|