Mobile UX: Formulare und Anzeige optimiert

- Beschreibungsfelder bei Leistung/Mehraufwand/Entfällt auf
  separate Zeile für Mobile (bessere Bedienbarkeit)
- Grund/Beschreibung wird auf Mobile klein unter dem
  Produktnamen angezeigt (spart Platz)
- Spalte "Beschreibung" auf Mobile ausgeblendet
- Action-Buttons sticky am unteren Bildschirmrand
- Tabs horizontal scrollbar statt umbruch
- Verbesserte Touch-Targets und Abstände

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-08 19:10:37 +01:00
parent 2b3514f762
commit d0e867aba3
2 changed files with 154 additions and 40 deletions

View file

@ -946,7 +946,7 @@ elseif ($object->id > 0) {
print '<th>'.$langs->trans("LeistungTimeEnd").'</th>';
print '<th class="center">'.$langs->trans("Duration").'</th>';
print '<th>'.$langs->trans("DefaultService").'</th>';
print '<th>'.$langs->trans("Description").'</th>';
print '<th class="mobile-hide">'.$langs->trans("Description").'</th>';
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
print '<th class="center" width="40"></th>'; // Edit
print '<th class="center" width="40"></th>'; // Delete
@ -1009,8 +1009,13 @@ elseif ($object->id > 0) {
} else {
print '<span class="opacitymedium">'.$langs->trans("NotSet").'</span>';
}
// Mobile: Beschreibung unter Leistungsposition anzeigen
if (!empty($leistung->description)) {
print '<div class="mobile-inline-desc"><small class="opacitymedium">'.dol_trunc(strip_tags($leistung->description), 60).'</small></div>';
}
print '</td>';
print '<td>'.dol_htmlentitiesbr($leistung->description).'</td>';
// Beschreibung (auf Mobile ausgeblendet)
print '<td class="mobile-hide">'.dol_htmlentitiesbr($leistung->description).'</td>';
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
// Edit Button
print '<td class="center">';
@ -1081,9 +1086,9 @@ elseif ($object->id > 0) {
$form->select_produits($defaultServiceId, 'fk_product', 1, 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'maxwidth200');
print '</td>';
// Beschreibung (Tagesbeschreibung - größeres Textarea, 5 Zeilen)
print '<td>';
print '<textarea name="leistung_description" class="flat" rows="5" style="width: 300px; resize: vertical;" placeholder="'.$langs->trans("Description").'"></textarea>';
// Beschreibung (Desktop: in Zeile, Mobile: ausgeblendet)
print '<td class="mobile-hide">';
print '<textarea name="leistung_description" class="flat" rows="3" style="width: 200px; resize: vertical;" placeholder="'.$langs->trans("Description").'"></textarea>';
print '</td>';
// Action (colspan=2 für beide Button-Spalten)
@ -1093,6 +1098,13 @@ elseif ($object->id > 0) {
print '</form>';
print '</tr>';
// Mobile: Beschreibung in separater Zeile
print '<tr class="oddeven mobile-description-row">';
print '<td colspan="8">';
print '<textarea name="leistung_description" form="form_leistung" class="flat" rows="3" style="width: 100%; resize: vertical;" placeholder="'.$langs->trans("Description").'"></textarea>';
print '</td>';
print '</tr>';
}
// Summenzeile anzeigen
@ -1332,7 +1344,7 @@ elseif ($object->id > 0) {
print '<tr class="liste_titre">';
print '<th>'.$langs->trans("Product").'</th>';
print '<th class="center" style="width:80px;">'.$langs->trans("Qty").'</th>';
print '<th style="width:200px;">'.$langs->trans("Reason").'</th>';
print '<th class="mobile-hide" style="width:200px;">'.$langs->trans("Reason").'</th>';
print '<th class="center" style="width:40px;"></th>'; // Save
print '<th class="center" style="width:40px;"></th>'; // Delete
print '</tr>';
@ -1365,6 +1377,10 @@ elseif ($object->id > 0) {
print '<span class="opacitymedium">'.$displayText.'</span>';
}
print ' <span class="badge badge-secondary">'.$langs->trans("Entfaellt").'</span>';
// Mobile: Grund unter Produktname anzeigen
if (!empty($prod->description)) {
print '<div class="mobile-inline-desc"><small class="opacitymedium">'.dol_trunc(strip_tags($prod->description), 50).'</small></div>';
}
print '</td>';
// Menge
@ -1377,8 +1393,8 @@ elseif ($object->id > 0) {
print '</form>';
print '</td>';
// Grund/Beschreibung
print '<td style="width:200px;">';
// Grund/Beschreibung (auf Mobile ausgeblendet)
print '<td class="mobile-hide" style="width:200px;">';
if (!empty($prod->description)) {
print dol_htmlentitiesbr($prod->description);
} else {
@ -1448,7 +1464,7 @@ elseif ($object->id > 0) {
print '</tr>';
print '<tr class="oddeven">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products" id="form_entfaellt">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="add_entfaellt">';
@ -1534,8 +1550,8 @@ elseif ($object->id > 0) {
print '<input type="number" name="entfaellt_qty" id="entfaellt_qty_input" class="flat" style="width:70px; text-align:center;" value="1" min="1">';
print '</td>';
// Grund
print '<td style="width:200px;">';
// Grund (Desktop: in Zeile, Mobile: ausgeblendet)
print '<td class="mobile-hide" style="width:200px;">';
print '<input type="text" name="entfaellt_description" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
print '</td>';
@ -1549,6 +1565,13 @@ elseif ($object->id > 0) {
print '</form>';
print '</tr>';
// Mobile: Grund in separater Zeile
print '<tr class="oddeven mobile-description-row">';
print '<td colspan="5">';
print '<input type="text" name="entfaellt_description" form="form_entfaellt" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
print '</td>';
print '</tr>';
// JavaScript für dynamische Max-Menge
print '<script>
function updateMaxQty(selectElement) {
@ -1585,7 +1608,7 @@ elseif ($object->id > 0) {
print '<tr class="liste_titre">';
print '<th>'.$langs->trans("Product").'</th>';
print '<th class="center" style="width:80px;">'.$langs->trans("Qty").'</th>';
print '<th style="width:200px;">'.$langs->trans("Reason").'</th>';
print '<th class="mobile-hide" style="width:200px;">'.$langs->trans("Reason").'</th>';
print '<th class="center" style="width:40px;"></th>'; // Save
print '<th class="center" style="width:40px;"></th>'; // Delete
print '</tr>';
@ -1620,6 +1643,11 @@ elseif ($object->id > 0) {
print '<span class="opacitymedium">'.$displayText.'</span>';
}
print ' <span class="badge badge-warning">'.$langs->trans("Mehraufwand").'</span>';
// Mobile: Grund unter Produktname anzeigen
$reason = ($prod->fk_product > 0) ? $prod->description : '';
if (!empty($reason)) {
print '<div class="mobile-inline-desc"><small class="opacitymedium">'.dol_trunc(strip_tags($reason), 50).'</small></div>';
}
print '</td>';
// Menge (qty_done) - editierbar
@ -1635,8 +1663,8 @@ elseif ($object->id > 0) {
}
print '</td>';
// Grund (nur wenn Produkt gesetzt, sonst ist description der Produktname)
print '<td style="width:200px;">';
// Grund (auf Mobile ausgeblendet, nur wenn Produkt gesetzt)
print '<td class="mobile-hide" style="width:200px;">';
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
// Grund nur anzeigen/editieren wenn ein Produkt gewählt wurde
$reason = ($prod->fk_product > 0) ? $prod->description : '';
@ -1680,7 +1708,7 @@ elseif ($object->id > 0) {
print '</tr>';
print '<tr class="oddeven">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products" id="form_mehraufwand">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="add_mehraufwand">';
@ -1696,8 +1724,8 @@ elseif ($object->id > 0) {
print '<input type="number" name="mehraufwand_qty" class="flat" style="width:70px; text-align:center;" value="1" min="1">';
print '</td>';
// Grund
print '<td style="width:200px;">';
// Grund (Desktop: in Zeile, Mobile: ausgeblendet)
print '<td class="mobile-hide" style="width:200px;">';
print '<input type="text" name="mehraufwand_reason" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
print '</td>';
@ -1710,6 +1738,13 @@ elseif ($object->id > 0) {
print '</form>';
print '</tr>';
// Mobile: Grund in separater Zeile
print '<tr class="oddeven mobile-description-row">';
print '<td colspan="5">';
print '<input type="text" name="mehraufwand_reason" form="form_mehraufwand" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
print '</td>';
print '</tr>';
}
print '</table>';

View file

@ -1,9 +1,20 @@
/**
* Stundenzettel Mobile CSS
* Responsive Styles für Touch-Geräte
* Version 1.2.0
* Version 1.2.1
*/
/* ============================================
DESKTOP: Mobile-Elemente ausblenden
============================================ */
.mod-stundenzettel .mobile-description-row {
display: none;
}
.mod-stundenzettel .mobile-inline-desc {
display: none;
}
/* ============================================
MOBILE STYLES (max-width: 768px)
============================================ */
@ -78,10 +89,10 @@
padding: 8px 4px !important;
}
/* Zeit-Inputs */
/* Zeit-Inputs kompakter */
.mod-stundenzettel input[type="time"] {
width: 80px !important;
padding: 8px !important;
width: 70px !important;
padding: 6px 4px !important;
}
/* Textarea volle Breite */
@ -98,7 +109,7 @@
.mod-stundenzettel select.minwidth200,
.mod-stundenzettel select.minwidth300 {
min-width: 150px !important;
min-width: 120px !important;
max-width: 100% !important;
}
@ -109,10 +120,10 @@
/* Icons größer und mit Padding für Touch */
.mod-stundenzettel td .fas,
.mod-stundenzettel td .far {
font-size: 1.3em;
padding: 8px;
min-width: 35px;
min-height: 35px;
font-size: 1.2em;
padding: 6px;
min-width: 32px;
min-height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
@ -125,31 +136,35 @@
}
/* ============================================
TABS - Kompakter
TABS - Kompakter und horizontal scrollbar
============================================ */
.mod-stundenzettel .tabs {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
gap: 2px;
}
.mod-stundenzettel .tabsElem a {
padding: 8px 10px !important;
font-size: 12px !important;
}
/* Tabs umbrechen wenn nötig */
.mod-stundenzettel .tabs {
flex-wrap: wrap;
white-space: nowrap;
}
/* ============================================
SECTION-TITEL
============================================ */
.mod-stundenzettel h3 {
font-size: 16px;
font-size: 15px;
padding: 8px 10px;
margin: 12px 0 8px 0;
margin: 10px 0 6px 0;
}
/* Abschnitts-Header in Tabellen */
.mod-stundenzettel tr.liste_titre th[colspan] {
font-size: 14px;
padding: 10px 8px !important;
font-size: 13px;
padding: 8px 6px !important;
}
/* ============================================
@ -160,14 +175,19 @@
flex-wrap: wrap;
gap: 8px;
padding: 10px 5px;
position: sticky;
bottom: 0;
background: #fff;
z-index: 100;
box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
}
.mod-stundenzettel .tabsAction a.butAction,
.mod-stundenzettel .tabsAction a.butActionDelete {
flex: 1 1 calc(50% - 8px);
text-align: center;
padding: 12px 8px !important;
font-size: 13px !important;
padding: 14px 10px !important;
font-size: 14px !important;
white-space: nowrap;
}
@ -175,11 +195,16 @@
BANNER/HEADER - Kompakter
============================================ */
.mod-stundenzettel .arearef {
padding: 8px !important;
padding: 6px !important;
}
.mod-stundenzettel .refid {
font-size: 14px;
font-size: 13px;
}
/* Banner-Tabelle kompakter */
.mod-stundenzettel .refidno {
font-size: 12px;
}
/* ============================================
@ -195,6 +220,7 @@
.mod-stundenzettel .select2-selection__rendered {
line-height: 38px !important;
font-size: 14px !important;
}
/* ============================================
@ -232,6 +258,59 @@
.mod-stundenzettel tr.liste_total td {
font-size: 13px;
padding: 8px 4px !important;
font-weight: bold;
}
/* ============================================
LEISTUNGEN TABELLE - Spalten optimieren
============================================ */
/* Beschreibungs-Spalte kompakter (Wörter umbrechen) */
.mod-stundenzettel table.noborder td:nth-child(6) {
max-width: 100px;
word-break: break-word;
font-size: 12px;
}
/* Textarea in Edit-Mode kompakter */
.mod-stundenzettel tr.oddeven textarea {
min-height: 50px;
width: 150px !important;
}
/* ============================================
MOBILE FORMULARE - Separate Beschreibungszeile
============================================ */
/* Mobile-Zeilen anzeigen */
.mod-stundenzettel .mobile-description-row {
display: table-row;
background: #f8f9fa !important;
}
.mod-stundenzettel .mobile-description-row td {
padding: 8px !important;
border-top: none !important;
}
.mod-stundenzettel .mobile-description-row input,
.mod-stundenzettel .mobile-description-row textarea {
width: 100% !important;
box-sizing: border-box;
}
/* Desktop-Spalten auf Mobile ausblenden */
.mod-stundenzettel .mobile-hide {
display: none !important;
}
/* ============================================
INLINE BESCHREIBUNG (unter Produktname)
============================================ */
.mod-stundenzettel .mobile-inline-desc {
display: block;
margin-top: 4px;
font-size: 11px;
line-height: 1.3;
color: #666;
}
}