Files
shop-old/tba-search.php.bak_20260730_lazy
T

1245 lines
63 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Krempl PostgreSQL Search Test Interface
* Search for devices and show matching ersatzteile
*/
require_once __DIR__ . '/core/postgres/KremplDB.php';
$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
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);
$results = $db->searchGeraete($search_term, 20);
if ($show_shop_items) {
foreach ($results as &$device) {
$device['shop_items'] = $db->getAvailableItemsForGeraet($device['geraet_id'], 200);
}
unset($device);
}
$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']);
}
}
// --- 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.
if ($results) {
$nav_ids = [];
foreach ($results as $d) {
foreach (array_slice($d['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.
// ==================================================================
if (!function_exists('v2_confidence')) {
/**
* Anzeigename eines Geraets. modell_bezeichnung ist bei 490.522 von 1.675.828
* Geraeten LEER (29%) -- vor allem bei BSH (SIEMENS/BOSCH), wo die Typnummer
* stattdessen in nr steht (z.B. "WT46W560BY/01"). Ohne Fallback rendern die
* Kacheln komplett namenlos.
*/
function v2_gname($d) {
foreach (['modell_bezeichnung', 'bezeichnung_original', 'nr'] as $k) {
if (isset($d[$k]) && trim((string)$d[$k]) !== '') return trim($d[$k]);
}
return '(ohne Bezeichnung)';
}
/**
* Produktionsdatum -> Jahr. In produktionsstart/-ende stecken ZWEI Formate:
* YYYYMMDD z.B. "19790101" (AEG Lavatherm 400)
* YYMM z.B. "8810" (SIEMENS WT46W560BY/01 = Oktober 1988)
* Bei YYMM gilt: >= 70 -> 19YY, sonst 20YY.
*/
function v2_jahr4($v) {
$s = preg_replace('/\D/', '', (string)$v);
if ($s === '') return '';
if (strlen($s) >= 6) return substr($s, 0, 4); // YYYYMM(DD)
if (strlen($s) === 4) { // YYMM
$yy = (int)substr($s, 0, 2);
return (string)($yy >= 70 ? 1900 + $yy : 2000 + $yy);
}
if (strlen($s) === 2) { // nur YY
$yy = (int)$s;
return (string)($yy >= 70 ? 1900 + $yy : 2000 + $yy);
}
return '';
}
/** Monat/Jahr fuer die Variantenliste, z.B. "10/1988". Leer, wenn nicht ableitbar. */
function v2_monat($v) {
$s = preg_replace('/\D/', '', (string)$v);
$j = v2_jahr4($s);
if ($j === '') return '';
if (strlen($s) >= 6) $m = substr($s, 4, 2); // YYYYMM(DD)
elseif (strlen($s) === 4) $m = substr($s, 2, 2); // YYMM
else return $j;
$m = (int)$m;
return ($m >= 1 && $m <= 12) ? sprintf('%02d/%s', $m, $j) : $j;
}
/** Shop-URL zu einer items.id -- leer, wenn kein Shop-Artikel bekannt ist. */
function v2_shopurl($item_id) {
return $item_id ? 'https://intelectra.de/index.php?item_id=' . (int)$item_id : '';
}
/**
* Chip fuer ein Ersatzteil/Artikel. Gibt einen <a> aus, wenn ein Shop-Artikel
* bekannt ist, sonst ein <span> -- damit nichts ins Leere klickt.
*/
function v2_chip($label, $sub, $item_id, $buyclass = false) {
$cls = 'v2-chip' . ($buyclass ? ' v2-buy' : '');
$inner = '<b>' . htmlspecialchars($label) . '</b>'
. ($sub !== '' ? ' · ' . htmlspecialchars($sub) : '');
$url = v2_shopurl($item_id);
if ($url === '') {
return '<span class="' . $cls . '">' . $inner . '</span>';
}
return '<a class="' . $cls . ' v2-chip-link" target="_blank" rel="noopener" href="'
. htmlspecialchars($url) . '">' . $inner . '</a>';
}
/**
* Artikel-ZEILE statt der alten Chip-"Blase": Bild, Name, Art-Nr, Preis.
* Wird in der Bestaetigungskarte und in den Geraete-Kacheln verwendet, damit
* die Teile-Liste dieselbe Form hat wie die Artikelliste weiter unten.
* Ohne Shop-Artikel-ID wird ein div statt eines Links ausgegeben.
*/
function v2_artikel_row($it) {
$url = v2_shopurl($it['id'] ?? null);
$img = !empty($it['default_image_file_name'])
? 'https://intelectra.de/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;
$nml = v2_nml_info((string)($it['name'] ?? ''));
// nml zusaetzlich ueber die Warengruppe erkennen: die ist verlaesslicher als
// der Namensmarker (erkennt 2.207 Faelle, die der Regex uebersieht).
$is_nml = $nml['is_nml'] || v2_is_nml_wg($it['wg_name'] ?? '');
$h = $url !== ''
? '<a class="v2-row" target="_blank" rel="noopener" href="' . htmlspecialchars($url) . '">'
: '<div class="v2-row v2-row-nolink">';
$h .= $img !== ''
? '<img class="v2-row-img" src="' . htmlspecialchars($img) . '" alt="" loading="lazy" onerror="this.style.visibility=\'hidden\'">'
: '<span class="v2-row-img v2-row-noimg">–</span>';
$h .= '<span class="v2-row-txt">'
. '<span class="v2-row-name">' . htmlspecialchars($nml['clean']) . '</span>'
. '<span class="v2-row-nr">Art-Nr ' . htmlspecialchars((string)($it['number'] ?? ''));
if ($is_nml) {
$h .= ' · <b style="color:#e65100">nicht mehr lieferbar</b>';
if (!empty($nml['nachfolger'])) {
$h .= ' · Nachfolger ' . htmlspecialchars($nml['nachfolger']);
}
}
$h .= '</span></span>';
$h .= '<span class="v2-row-price">';
if ($bb > 0 && $pb > 0 && $bb < $pb) {
$h .= '<b style="color:#c62828">' . number_format($bb, 2, ',', '.') . ' €</b>'
. '<s>' . number_format($pb, 2, ',', '.') . ' €</s>';
} elseif ($pb > 0) {
$h .= '<b>' . number_format($pb, 2, ',', '.') . ' €</b>';
} elseif ($is_nml) {
// Bei nml steht der Preis in der DB auf 0 -- "Preis auf Anfrage" waere hier
// eine falsche Erwartung, der Artikel ist gar nicht bestellbar.
$h .= '<span class="v2-row-noprice">—</span>';
} else {
$h .= '<span class="v2-row-noprice">Preis auf Anfrage</span>';
}
$h .= '</span>';
return $h . ($url !== '' ? '</a>' : '</div>');
}
/** Warengruppen-Name deutet auf "nicht mehr lieferbar" hin? */
function v2_is_nml_wg($wg) {
return (bool)preg_match('/lieferbar|NMLE|Nachfolge|kein Ersatz/i', (string)$wg);
}
/**
* nml-Status eines Shop-Artikels aus BEIDEN Quellen: Warengruppe und Namensmarker.
*
* Die Warengruppe ist der verlaesslichere Marker -- gemessen am 30.07.2026:
* ueber WG erkannt 9.848 Artikel, ueber (nml)-Regex 7.694, davon erkennt nur die
* WG 2.207 Faelle (Klartext "Nicht mehr lieferbar +++ kein Ersatz" ohne Suffix)
* und nur der Name 51. Zusammen also ~9.899 von 16.923 aktiven.
* Die nml-Warengruppen sind 9929, 10649, 10746, 10750; ihr Name "weitere Treffer:
* Nachfolge-Artikel" (10649) ist irrefuehrend -- dort liegen ausgelaufene Artikel
* (1.241 von 1.274 tragen selbst einen nml-Marker), nicht die Nachfolger.
* Ein Nachfolger laesst sich weiterhin nur aus dem NAMEN lesen, daher bleibt die
* Unterscheidung g_succ/g_dead am Namenstext haengen.
*/
function v2_nml_full($it) {
$info = v2_nml_info((string)($it['name'] ?? ''));
if (!$info['is_nml'] && v2_is_nml_wg($it['wg_name'] ?? '')) {
$info['is_nml'] = true;
}
return $info;
}
/**
* Artikel-Liste als Zeilen. Ab $threshold Treffern wird nach Warengruppe
* gruppiert -- genau wie die alte Shop-Suche, weil eine flache Liste mit 30+
* Positionen unbrauchbar ist. nml-Warengruppen ("Nicht mehr Lieferbar +++ ...",
* "NMLE-ohne WG", "weitere Treffer: Nachfolge-Artikel") wandern ans Ende.
*/
function v2_artikel_liste(array $items, $threshold = 12, $max = null) {
if (!$items) return '';
// nml-Artikel ans Ende: sie sind nicht bestellbar und sollen die Liste nicht
// anfuehren. Muss VOR dem Abschneiden passieren, sonst landen kaufbare Teile
// im versteckten Rest, waehrend nml-Positionen die sichtbaren Plaetze belegen.
$nml_flag = function ($it) {
return (v2_is_nml_wg($it['wg_name'] ?? '')
|| v2_nml_info((string)($it['name'] ?? ''))['is_nml']) ? 1 : 0;
};
usort($items, function ($a, $b) use ($nml_flag) {
return $nml_flag($a) - $nml_flag($b);
});
// In den eingeklappten Geraete-Kacheln wird begrenzt ($max), sonst rendert eine
// Suche wie "WT46W560BY" 10 Kacheln x bis 200 Artikel = mehrere Tausend Zeilen.
// Die aktive Bestaetigungskarte zeigt alles ($max = null). Der Rest wird NICHT
// verworfen, sondern aufklappbar angehaengt -- vorher war "+ N weitere" ein
// toter Text und der Kunde erfuhr nie, was er noch kaufen koennte.
$restitems = [];
if ($max !== null && count($items) > $max) {
$restitems = array_slice($items, $max);
$items = array_slice($items, 0, $max);
}
$more = '';
if ($restitems) {
$more = '<details class="v2-rowmore"><summary>+ ' . count($restitems)
. ' weitere Artikel für dieses Gerät anzeigen</summary><div class="v2-rows">';
foreach ($restitems as $it) { $more .= v2_artikel_row($it); }
$more .= '</div></details>';
}
if (count($items) <= $threshold) {
$h = '<div class="v2-rows">';
foreach ($items as $it) { $h .= v2_artikel_row($it); }
return $h . '</div>' . $more;
}
$groups = [];
foreach ($items as $it) {
$wg = trim((string)($it['wg_name'] ?? '')) ?: 'Ohne Warengruppe';
$groups[$wg][] = $it;
}
// Echte Warengruppen zuerst (die groesste oben), nml-Gruppen ans Ende.
uksort($groups, function ($a, $b) use (&$groups) {
$na = v2_is_nml_wg($a) ? 1 : 0;
$nb = v2_is_nml_wg($b) ? 1 : 0;
if ($na !== $nb) return $na - $nb;
return count($groups[$b]) <=> count($groups[$a]);
});
$h = '';
foreach ($groups as $wg => $its) {
$cls = v2_is_nml_wg($wg) ? ' v2-wg-nml' : '';
$h .= '<h4 class="v2-wg' . $cls . '">' . htmlspecialchars($wg)
. ' <span>' . count($its) . '</span></h4><div class="v2-rows">';
foreach ($its as $it) { $h .= v2_artikel_row($it); }
$h .= '</div>';
}
return $h . $more;
}
function v2_confidence($score) {
$s = (int)$score;
if ($s >= 100) return ['Exakter Treffer', '#2e7d32', '#e8f5e9', '✓'];
if ($s >= 90) return ['Sehr wahrscheinlich dein Gerät','#388e3c', '#edf7ee', '✓'];
if ($s >= 75) return ['Könnte passen', '#f9a825', '#fff8e1', '≈'];
return ['Ähnlich – bitte prüfen', '#9e9e9e', '#f5f5f5', '?'];
}
// nml/Nachfolger steckt im Artikelnamen: "(nml)"/"(nmle)" + "Nachfolger <Nr>"
function v2_nml_info($name) {
$is_nml = (bool)preg_match('/\(nml[e]?\)/i', $name);
$nachf = null;
// Nachfolger-Nummer muss mind. eine Ziffer enthalten (schließt "Nachfolger Original" aus)
if (preg_match('/Nachfolger\s+([A-Za-z0-9\-\/\.]*\d[A-Za-z0-9\-\/\.]*)/i', $name, $m)) {
$nachf = $m[1];
}
$kein_ersatz = (stripos($name, 'kein Ersatz') !== false);
// Anzeigename säubern: (nml)-Marker + "Nachfolger ... Original(teil)" raus
$clean = preg_replace('/\s*\(nml[e]?\)\s*/i', ' ', $name);
$clean = preg_replace('/\s*Nachfolger\s+[A-Za-z0-9\-\/\.]*\d[A-Za-z0-9\-\/\.]*\s*/i', ' ', $clean);
$clean = preg_replace('/\s*(Originalteil|Original)\s*$/i', '', $clean);
$clean = trim(preg_replace('/\s{2,}/', ' ', $clean));
return ['is_nml'=>$is_nml, 'nachfolger'=>$nachf, 'kein_ersatz'=>$kein_ersatz, 'clean'=>($clean ?: $name)];
}
}
// 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
if ($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}
.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>
<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
// Query-String-Bausteine fuer die Hersteller-Links
$v2_qs_shop = $show_shop_items ? '' : '&amp;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; ?>&amp;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 ? '' : '&amp;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 = '';
$v2_wg = array_filter([trim($det['wgtext1'] ?? ''), trim($det['wgtext2'] ?? '')]);
$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 (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 = 'https://intelectra.de/index.php?item_id=' . (int)$it['id'];
$img = !empty($it['default_image_file_name'])
? 'https://intelectra.de/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, ',', '.'); ?>&nbsp;€</span>
<span style="font-size:12px;color:#999;text-decoration:line-through;font-weight:400"><?php echo number_format($pb, 2, ',', '.'); ?>&nbsp;€</span>
<?php elseif ($pb > 0): ?>
<?php echo number_format($pb, 2, ',', '.'); ?>&nbsp;€
<?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 = 'https://intelectra.de/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>
function toggleDevice(deviceId) {
const card = document.getElementById('device-' + deviceId);
if (card) {
card.classList.toggle('collapsed');
}
}
</script>
</body>
</html>