Version 3.4.0 - Kategorie-Auswahl, Icons, Sicherheitsfixes
- Kategorie-Select (Gebäude/Standort vs Element/Gerät) beim Erstellen - Select2 mit FontAwesome-Icons und Farbkodierung für Typ-Auswahl - GLOBAL-Gebäudetypen aus Admin Element-Typen ausgeblendet (eigener Tab) - Aktions-Buttons rechtsbündig in der Typ-Verwaltung - Sicherheits-Fixes: Berechtigungsprüfungen, Path-Traversal, Transaktionen - Version auf 3.4.0 aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
37ae45e8f2
commit
f2f3393b12
13 changed files with 285 additions and 24 deletions
|
|
@ -13,6 +13,9 @@ Das KundenKarte-Modul erweitert Dolibarr um zwei wichtige Funktionen fuer Kunden
|
|||
### Technische Anlagen (Anlagen)
|
||||
- Hierarchische Baumstruktur fuer technische Installationen
|
||||
- Flexible Systemkategorien (z.B. Strom, Internet, Kabel, Sat)
|
||||
- Kategorie-Auswahl beim Erstellen: Gebaeude/Standort oder Element/Geraet
|
||||
- Typ-Select mit FontAwesome-Icons und Farbkodierung (Select2)
|
||||
- Gebaeude-Typen gruppiert nach Ebene (Gebaeude, Etage, Fluegel, Raum, Bereich)
|
||||
- Konfigurierbare Element-Typen mit individuellen Feldern
|
||||
- Datei-Upload mit Bild-Vorschau und PDF-Anzeige
|
||||
- Separate Verwaltung pro Kunde oder pro Kontakt/Adresse (z.B. verschiedene Gebaeude)
|
||||
|
|
@ -63,6 +66,8 @@ Im Admin-Bereich (Home > Setup > Module > KundenKarte) koennen Sie:
|
|||
- **Anlagen-Systeme**: System-Kategorien anlegen (z.B. Strom, Internet)
|
||||
- **Element-Typen**: Geraetetypen definieren (z.B. Zaehler, Router, Wallbox)
|
||||
- **Typ-Felder**: Individuelle Felder pro Geraetetyp konfigurieren
|
||||
- **Gebaeudetypen**: Strukturtypen (Haus, Etage, Raum etc.) fuer die Gebaeude-Hierarchie
|
||||
- **Kabeltypen**: Verbindungsmedien (NYM, NYY, CAT etc.) mit Spezifikationen
|
||||
- **Equipment-Typen**: Schaltplan-Komponenten (z.B. Sicherungsautomaten, FI-Schalter) mit Breite (TE), Farbe und Terminal-Konfiguration
|
||||
- **Phasenschienen-Typen**: Sammelschienen/Phasenschienen-Vorlagen (L1, L2, L3, N, PE, 3P+N etc.) mit Farben und Linien-Konfiguration
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,7 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
$selAll = (empty($anlageType->fk_system)) ? ' selected' : '';
|
||||
print '<option value="0"'.$selAll.'>'.$langs->trans('AllSystems').'</option>';
|
||||
foreach ($systems as $sys) {
|
||||
if ($sys->code === 'GLOBAL') continue; // Gebaeude-Typen haben eigenen Tab
|
||||
$sel = ($anlageType->fk_system == $sys->rowid) ? ' selected' : '';
|
||||
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
||||
}
|
||||
|
|
@ -634,6 +635,7 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
print '<select name="system" class="flat" onchange="this.form.submit();">';
|
||||
print '<option value="0">'.$langs->trans('All').'</option>';
|
||||
foreach ($systems as $sys) {
|
||||
if ($sys->code === 'GLOBAL') continue; // Gebaeude-Typen haben eigenen Tab
|
||||
$sel = ($systemFilter == $sys->rowid) ? ' selected' : '';
|
||||
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
||||
}
|
||||
|
|
@ -648,7 +650,7 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
print '</div>';
|
||||
|
||||
// List
|
||||
$types = $anlageType->fetchAllBySystem($systemFilter, 0);
|
||||
$types = $anlageType->fetchAllBySystem($systemFilter, 0, 1);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
|
|
@ -658,7 +660,7 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
print '<th class="center">'.$langs->trans('CanHaveChildren').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Position').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Status').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
||||
print '<th class="right">'.$langs->trans('Actions').'</th>';
|
||||
print '</tr>';
|
||||
|
||||
foreach ($types as $type) {
|
||||
|
|
@ -692,8 +694,8 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
}
|
||||
print '</td>';
|
||||
|
||||
print '<td class="center nowraponall">';
|
||||
print '<div style="display:inline-flex;gap:8px;">';
|
||||
print '<td class="right nowraponall">';
|
||||
print '<div style="display:inline-flex;gap:8px;justify-content:flex-end;">';
|
||||
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$type->id.'&system='.$systemFilter.'" title="'.$langs->trans('Edit').'"><i class="fas fa-pen"></i></a>';
|
||||
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=copy&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'" title="'.$langs->trans('Copy').'"><i class="fas fa-copy"></i></a>';
|
||||
if (!$type->is_system) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ dol_include_once('/kundenkarte/class/anlage.class.php');
|
|||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Berechtigungsprüfung
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Permission denied']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
|
||||
if ($anlageId <= 0) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ dol_include_once('/kundenkarte/class/anlage.class.php');
|
|||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Berechtigungsprüfung
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Permission denied']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
|
||||
if ($anlageId <= 0) {
|
||||
|
|
|
|||
3
ajax/equipment_type_block_image.php
Normal file → Executable file
3
ajax/equipment_type_block_image.php
Normal file → Executable file
|
|
@ -148,6 +148,9 @@ switch ($action) {
|
|||
break;
|
||||
}
|
||||
|
||||
// Path-Traversal-Schutz: nur Dateiname ohne Verzeichnisanteile
|
||||
$selectedImage = basename($selectedImage);
|
||||
|
||||
// Validate that the image exists
|
||||
$imagePath = $uploadDir . $selectedImage;
|
||||
if (!file_exists($imagePath)) {
|
||||
|
|
|
|||
64
class/anlageconnection.class.php
Normal file → Executable file
64
class/anlageconnection.class.php
Normal file → Executable file
|
|
@ -58,6 +58,17 @@ class AnlageConnection extends CommonObject
|
|||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
$now = dol_now();
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
// installation_date als DATE-Feld (YYYY-MM-DD String) sicher escapen
|
||||
$installDateSQL = "NULL";
|
||||
if ($this->installation_date) {
|
||||
$installDateSQL = "'".$this->db->escape($this->installation_date)."'";
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, fk_source, fk_target, label,";
|
||||
$sql .= "fk_medium_type, medium_type_text, medium_spec, medium_length, medium_color,";
|
||||
|
|
@ -74,21 +85,28 @@ class AnlageConnection extends CommonObject
|
|||
$sql .= ", ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
||||
$sql .= ", ".($this->medium_color ? "'".$this->db->escape($this->medium_color)."'" : "NULL");
|
||||
$sql .= ", ".($this->route_description ? "'".$this->db->escape($this->route_description)."'" : "NULL");
|
||||
$sql .= ", ".($this->installation_date ? "'".$this->db->escape($this->installation_date)."'" : "NULL");
|
||||
$sql .= ", ".$installDateSQL;
|
||||
$sql .= ", ".(int)($this->status ?: 1);
|
||||
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||
$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||
$sql .= ", NOW()";
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
$sql .= ", ".(int)$user->id;
|
||||
$sql .= ")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||
return $this->id;
|
||||
} else {
|
||||
$error++;
|
||||
$this->error = $this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +175,16 @@ class AnlageConnection extends CommonObject
|
|||
*/
|
||||
public function update($user)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
// installation_date als DATE-Feld (YYYY-MM-DD String) sicher escapen
|
||||
$installDateSQL = "NULL";
|
||||
if ($this->installation_date) {
|
||||
$installDateSQL = "'".$this->db->escape($this->installation_date)."'";
|
||||
}
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||
$sql .= " fk_source = ".(int)$this->fk_source;
|
||||
$sql .= ", fk_target = ".(int)$this->fk_target;
|
||||
|
|
@ -167,7 +195,7 @@ class AnlageConnection extends CommonObject
|
|||
$sql .= ", medium_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
||||
$sql .= ", medium_color = ".($this->medium_color ? "'".$this->db->escape($this->medium_color)."'" : "NULL");
|
||||
$sql .= ", route_description = ".($this->route_description ? "'".$this->db->escape($this->route_description)."'" : "NULL");
|
||||
$sql .= ", installation_date = ".($this->installation_date ? "'".$this->db->escape($this->installation_date)."'" : "NULL");
|
||||
$sql .= ", installation_date = ".$installDateSQL;
|
||||
$sql .= ", status = ".(int)$this->status;
|
||||
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||
$sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||
|
|
@ -175,11 +203,17 @@ class AnlageConnection extends CommonObject
|
|||
$sql .= " WHERE rowid = ".(int)$this->id;
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
return 1;
|
||||
} else {
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->error = $this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,15 +225,25 @@ class AnlageConnection extends CommonObject
|
|||
*/
|
||||
public function delete($user)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE rowid = ".(int)$this->id;
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
return 1;
|
||||
} else {
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->error = $this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -269,9 +269,10 @@ class AnlageType extends CommonObject
|
|||
*
|
||||
* @param int $systemId System ID (0 = all)
|
||||
* @param int $activeOnly Only active types
|
||||
* @param int $excludeGlobal 1 = GLOBAL-Typen ausschliessen (fuer Admin-Ansicht)
|
||||
* @return array Array of AnlageType objects
|
||||
*/
|
||||
public function fetchAllBySystem($systemId = 0, $activeOnly = 1)
|
||||
public function fetchAllBySystem($systemId = 0, $activeOnly = 1, $excludeGlobal = 0)
|
||||
{
|
||||
$results = array();
|
||||
|
||||
|
|
@ -279,9 +280,18 @@ class AnlageType extends CommonObject
|
|||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
|
||||
$sql .= " WHERE 1 = 1";
|
||||
if ($excludeGlobal) {
|
||||
// GLOBAL-Typen (Gebaeude) ausschliessen (Admin-Ansicht)
|
||||
$sql .= " AND (s.code IS NULL OR s.code != 'GLOBAL')";
|
||||
}
|
||||
if ($systemId > 0) {
|
||||
// Show types for this system AND GLOBAL types (types from GLOBAL system are available everywhere)
|
||||
$sql .= " AND (t.fk_system = ".((int) $systemId)." OR s.code = 'GLOBAL')";
|
||||
if (!$excludeGlobal) {
|
||||
// Typen dieses Systems UND GLOBAL-Typen (fuer Tabs-Ansicht)
|
||||
$sql .= " AND (t.fk_system = ".((int) $systemId)." OR s.code = 'GLOBAL')";
|
||||
} else {
|
||||
// Nur Typen dieses Systems (fuer Admin-Ansicht)
|
||||
$sql .= " AND t.fk_system = ".((int) $systemId);
|
||||
}
|
||||
}
|
||||
if ($activeOnly) {
|
||||
$sql .= " AND t.active = 1";
|
||||
|
|
|
|||
3
class/buildingtype.class.php
Normal file → Executable file
3
class/buildingtype.class.php
Normal file → Executable file
|
|
@ -63,6 +63,7 @@ class BuildingType extends CommonObject
|
|||
{
|
||||
global $conf;
|
||||
|
||||
$now = dol_now();
|
||||
$this->ref = trim($this->ref);
|
||||
$this->label = trim($this->label);
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ class BuildingType extends CommonObject
|
|||
$sql .= ", ".(int)($this->can_have_children !== null ? $this->can_have_children : 1);
|
||||
$sql .= ", ".(int)($this->position ?: 0);
|
||||
$sql .= ", ".(int)($this->active !== null ? $this->active : 1);
|
||||
$sql .= ", NOW()";
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
$sql .= ", ".(int)$user->id;
|
||||
$sql .= ")";
|
||||
|
||||
|
|
|
|||
4
class/terminalbridge.class.php
Normal file → Executable file
4
class/terminalbridge.class.php
Normal file → Executable file
|
|
@ -48,6 +48,8 @@ class TerminalBridge extends CommonObject
|
|||
*/
|
||||
public function create($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
$now = dol_now();
|
||||
|
||||
|
|
@ -63,7 +65,7 @@ class TerminalBridge extends CommonObject
|
|||
$sql .= " terminal_side, terminal_row, color, bridge_type, label,";
|
||||
$sql .= " status, date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= ((int) $this->entity ?: 1);
|
||||
$sql .= ((int) ($conf->entity));
|
||||
$sql .= ", ".((int) $this->fk_anlage);
|
||||
$sql .= ", ".((int) $this->fk_carrier);
|
||||
$sql .= ", ".((int) $this->start_te);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules
|
|||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '3.3.2';
|
||||
$this->version = '3.4.0';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,11 @@ NoInstallations = Keine Installationen vorhanden
|
|||
SelectSystem = System auswaehlen
|
||||
AllSystems = Alle Systeme
|
||||
AllSystemsHint = Leer lassen fuer alle Systeme
|
||||
Category = Kategorie
|
||||
SelectCategory = Kategorie auswaehlen
|
||||
SelectType = Typ auswaehlen
|
||||
SelectParent = Uebergeordnetes Element
|
||||
TechnicalElement = Element / Geraet
|
||||
Root = Stamm
|
||||
|
||||
# Customer System Management
|
||||
|
|
|
|||
|
|
@ -44,8 +44,11 @@ EditElement = Edit element
|
|||
DeleteElement = Delete element
|
||||
NoInstallations = No installations
|
||||
SelectSystem = Select system
|
||||
Category = Category
|
||||
SelectCategory = Select category
|
||||
SelectType = Select type
|
||||
SelectParent = Parent element
|
||||
TechnicalElement = Element / Device
|
||||
Root = Root
|
||||
|
||||
# Customer System Management
|
||||
|
|
@ -208,3 +211,12 @@ Close = Close
|
|||
Confirm = Confirm
|
||||
Yes = Yes
|
||||
No = No
|
||||
|
||||
# Building Types
|
||||
BuildingStructure = Building / Location
|
||||
BuildingLevelBuilding = Building
|
||||
BuildingLevelFloor = Floor / Level
|
||||
BuildingLevelWing = Wing
|
||||
BuildingLevelCorridor = Corridor / Hallway
|
||||
BuildingLevelRoom = Room
|
||||
BuildingLevelArea = Area / Zone
|
||||
|
|
|
|||
173
tabs/anlagen.php
173
tabs/anlagen.php
|
|
@ -652,14 +652,80 @@ if (empty($customerSystems)) {
|
|||
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('Label').'</td>';
|
||||
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($labelValue).'" required></td></tr>';
|
||||
|
||||
// Type
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
||||
// Kategorie (Gebäude/Standort vs Element/Gerät)
|
||||
$currentCategory = '';
|
||||
if (($isEdit || $isCopy) && !empty($anlage->fk_anlage_type)) {
|
||||
// Kategorie des aktuellen Typs ermitteln
|
||||
foreach ($types as $t) {
|
||||
if ($t->id == $anlage->fk_anlage_type) {
|
||||
$currentCategory = ($t->system_code === 'GLOBAL') ? 'building' : 'element';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$postedCategory = GETPOST('element_category', 'alpha');
|
||||
if ($postedCategory) $currentCategory = $postedCategory;
|
||||
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('Category').'</td>';
|
||||
print '<td><select name="element_category" class="flat minwidth200" id="select_category">';
|
||||
print '<option value="">'.$langs->trans('SelectCategory').'</option>';
|
||||
print '<option value="building"'.($currentCategory === 'building' ? ' selected' : '').'>'.$langs->trans('BuildingStructure').'</option>';
|
||||
print '<option value="element"'.($currentCategory === 'element' ? ' selected' : '').'>'.$langs->trans('TechnicalElement').'</option>';
|
||||
print '</select></td></tr>';
|
||||
|
||||
// Type (gefiltert nach Kategorie)
|
||||
print '<tr id="row_type"><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
||||
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
|
||||
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
||||
|
||||
// Typen nach Kategorie gruppieren
|
||||
$buildingTypes = array();
|
||||
$elementTypes = array();
|
||||
foreach ($types as $t) {
|
||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||
print '<option value="'.$t->id.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||
if ($t->system_code === 'GLOBAL') {
|
||||
$buildingTypes[] = $t;
|
||||
} else {
|
||||
$elementTypes[] = $t;
|
||||
}
|
||||
}
|
||||
|
||||
// Gebäude-Typen nach level_type gruppieren (position-basiert)
|
||||
if (!empty($buildingTypes)) {
|
||||
$lastGroup = '';
|
||||
foreach ($buildingTypes as $t) {
|
||||
// Gruppierung nach Position: 10-99=Gebäude, 100-199=Etage, 200-299=Flügel, 300-399=Flur, 400-599=Raum, 600+=Außen
|
||||
if ($t->position < 100) $group = $langs->trans('BuildingLevelBuilding');
|
||||
elseif ($t->position < 200) $group = $langs->trans('BuildingLevelFloor');
|
||||
elseif ($t->position < 300) $group = $langs->trans('BuildingLevelWing');
|
||||
elseif ($t->position < 400) $group = $langs->trans('BuildingLevelCorridor');
|
||||
elseif ($t->position < 600) $group = $langs->trans('BuildingLevelRoom');
|
||||
else $group = $langs->trans('BuildingLevelArea');
|
||||
|
||||
if ($group !== $lastGroup) {
|
||||
if ($lastGroup !== '') print '</optgroup>';
|
||||
print '<optgroup label="'.dol_escape_htmltag($group).'" class="type-category-building">';
|
||||
$lastGroup = $group;
|
||||
}
|
||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||
print '<option value="'.$t->id.'" data-category="building" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||
}
|
||||
if ($lastGroup !== '') print '</optgroup>';
|
||||
}
|
||||
|
||||
// Element-Typen
|
||||
if (!empty($elementTypes)) {
|
||||
print '<optgroup label="'.$langs->trans('TechnicalElement').'" class="type-category-element">';
|
||||
foreach ($elementTypes as $t) {
|
||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||
print '<option value="'.$t->id.'" data-category="element" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||
}
|
||||
print '</optgroup>';
|
||||
}
|
||||
|
||||
print '</select>';
|
||||
if (empty($types)) {
|
||||
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').'</span>';
|
||||
|
|
@ -692,6 +758,105 @@ if (empty($customerSystems)) {
|
|||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
|
||||
// JavaScript: Kategorie-Filter + Select2 mit Icons
|
||||
print '<script>
|
||||
$(document).ready(function() {
|
||||
var $catSelect = $("#select_category");
|
||||
var $typeSelect = $("#select_type");
|
||||
|
||||
// Alle Options und Optgroups als HTML-String sichern
|
||||
var allOptionsHtml = $typeSelect.html();
|
||||
|
||||
// Select2 Template-Funktion mit Icons
|
||||
function formatTypeOption(option) {
|
||||
if (!option.id) return option.text; // Placeholder
|
||||
var $opt = $(option.element);
|
||||
var icon = $opt.data("icon");
|
||||
var color = $opt.data("color") || "#666";
|
||||
if (icon) {
|
||||
return $("<span><i class=\"fa " + icon + "\" style=\"color:" + color + ";width:20px;margin-right:8px;text-align:center;\"></i>" + option.text + "</span>");
|
||||
}
|
||||
return option.text;
|
||||
}
|
||||
|
||||
// Select2 initialisieren
|
||||
function initSelect2() {
|
||||
// Falls bereits initialisiert, zerstören
|
||||
if ($typeSelect.hasClass("select2-hidden-accessible")) {
|
||||
$typeSelect.select2("destroy");
|
||||
}
|
||||
$typeSelect.select2({
|
||||
templateResult: formatTypeOption,
|
||||
templateSelection: formatTypeOption,
|
||||
placeholder: "'.dol_escape_js($langs->trans('SelectType')).'",
|
||||
allowClear: true,
|
||||
width: "300px",
|
||||
dropdownAutoWidth: true
|
||||
});
|
||||
}
|
||||
|
||||
function filterTypes() {
|
||||
var category = $catSelect.val();
|
||||
var currentVal = $typeSelect.val();
|
||||
|
||||
// Select2 zerstören vor DOM-Änderungen
|
||||
if ($typeSelect.hasClass("select2-hidden-accessible")) {
|
||||
$typeSelect.select2("destroy");
|
||||
}
|
||||
|
||||
// Alle Options zurücksetzen
|
||||
$typeSelect.html(allOptionsHtml);
|
||||
|
||||
if (!category) {
|
||||
$typeSelect.prop("disabled", true);
|
||||
$("#row_type").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nicht passende Options entfernen
|
||||
$typeSelect.find("option[data-category]").each(function() {
|
||||
if ($(this).data("category") !== category) {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Leere Optgroups entfernen
|
||||
$typeSelect.find("optgroup").each(function() {
|
||||
if ($(this).find("option").length === 0) {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
$typeSelect.prop("disabled", false);
|
||||
$("#row_type").show();
|
||||
|
||||
// Wert wiederherstellen falls noch vorhanden
|
||||
if (currentVal && $typeSelect.find("option[value=\"" + currentVal + "\"]").length) {
|
||||
$typeSelect.val(currentVal);
|
||||
} else {
|
||||
$typeSelect.val("");
|
||||
}
|
||||
|
||||
// Select2 neu initialisieren
|
||||
initSelect2();
|
||||
}
|
||||
|
||||
$catSelect.on("change", function() {
|
||||
$typeSelect.val("");
|
||||
filterTypes();
|
||||
$typeSelect.trigger("change");
|
||||
});
|
||||
|
||||
// Initial filtern
|
||||
if ($catSelect.val()) {
|
||||
filterTypes();
|
||||
} else {
|
||||
$typeSelect.prop("disabled", true);
|
||||
$("#row_type").hide();
|
||||
}
|
||||
});
|
||||
</script>';
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
|
|
|
|||
Loading…
Reference in a new issue