1245 lines
63 KiB
Plaintext
1245 lines
63 KiB
Plaintext
<?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 ? '' : '&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 = '';
|
||
$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, ',', '.'); ?> €</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 = '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>
|